kotlin - 显示HDR图(heic格式),使用GainMap算法,速度从5秒提升到0.6秒

kotlin - 显示HDR图(heic格式),使用GainMap算法,速度从5秒提升到0.6秒

复制代码
class HdrImageDecoderActivity : AppCompatActivity() , View.OnClickListener{


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.hdr_image_decoder_main)

        findViewById<Button>(R.id.hdr_show_btn1).setOnClickListener(this)
        findViewById<Button>(R.id.hdr_show_btn2).setOnClickListener(this)

    }

    override fun onClick(v: View?) {
        v?:return
        when(v.id){
            R.id.hdr_show_btn1 -> {
                show1()
            }
            R.id.hdr_show_btn2 -> {
                show2()
            }
        }
    }

    private fun show2(){
        LogUtils.i("AAA", "11111")
        val resultImageView = findViewById<ImageView>(R.id.hdr_show_result)
        val resultImageView2 = findViewById<ImageView>(R.id.hdr_show_result2)
        //val filePath = "sdcard/DCIM/AAA/IMG_20251101_154711.HEIC"
        val filePath = "sdcard/DCIM/Camera/IMG_20251101_154711.HEIC"
        //val filePath = "sdcard/DCIM/Camera/IMG_20251026_100717.HEIC"
        //val filePath = "sdcard/DCIM/Camera/IMG_20251102_162507.HEIC"
        val options = BitmapFactory.Options()
        options.inSampleSize = 2
        val bitmap = BitmapFactory.decodeFile(filePath, options)
        resultImageView.setImageBitmap(bitmap)
        LogUtils.i("BBB", "111")
        val gainMapResult = GainMapDecoder.decodeGainMap(filePath)
        LogUtils.i("BBB", "222")
        if(gainMapResult != null){
            val bitmap2 = GainMapProcessor.applyGainMapConservative(gainMapResult.baseImage, gainMapResult.gainMap)
            LogUtils.i("BBB", "333")
            resultImageView2.setImageBitmap(bitmap2)
        }
        /*val bitmap2 = HDRHelper.loadProXdrImage(filePath)
        resultImageView2.setImageBitmap(bitmap2)
        LogUtils.i("AAA", "33333")*/
    }

    private fun show1(){
        val resultImageView = findViewById<ImageView>(R.id.hdr_show_result)
        val resultImageView2 = findViewById<ImageView>(R.id.hdr_show_result2)
        val filePath = "sdcard/DCIM/Camera/IMG_20251026_111918.HEIC"
        LogUtils.i("AAA", "exist = " + File(filePath).exists())
        //HeifLoader.loadHeifImage(filePath, resultImageView)
        val bitmap = HDRHelper.loadProXdrImage(filePath)
        resultImageView.setImageBitmap(bitmap)

        val options = BitmapFactory.Options()
        //options.inSampleSize = 20
        val bitmap2 = BitmapFactory.decodeFile(filePath)
        resultImageView2.setImageBitmap(bitmap2)

    }
}
复制代码
package com.example.androidkotlindemo2.hdr;

/**
 * Author : wn
 * Email : maoning20080809@163.com
 * Date : 2025/11/1 14:47
 * Description :
 */

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.DisplayMetrics;
import android.util.Log;

import androidx.exifinterface.media.ExifInterface;

import com.example.androidkotlindemo2.utils.LogUtils;

public class GainMapDecoder {
    private static final String TAG = "GainMapDecoder";

    public static class GainMapResult {
        public Bitmap baseImage;
        public Bitmap gainMap;
        public float gamma;
        public float hdrCapacityMin;
        public float hdrCapacityMax;
        public float offsetSdr;
        public float offsetHdr;

        public GainMapResult() {
            // 默认值
            this.gamma = 1.0f;
            this.hdrCapacityMin = 0.0f;
            this.hdrCapacityMax = 1.0f;
            this.offsetSdr = 0.0f;
            this.offsetHdr = 0.0f;
        }
    }

    public static GainMapResult decodeGainMap(String imagePath) {
        GainMapResult result = new GainMapResult();

        try {
            // 第一步:获取图片尺寸而不加载到内存
            BitmapFactory.Options sizeOptions = new BitmapFactory.Options();
            sizeOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(imagePath, sizeOptions);

            int imageWidth = sizeOptions.outWidth;
            int imageHeight = sizeOptions.outHeight;

            // 第二步:根据屏幕宽度计算合适的采样率
            DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
            int screenWidth = displayMetrics.widthPixels ;

            // 计算采样率,确保解码后的图片宽度接近屏幕宽度
            int inSampleSize = calculateInSampleSize(imageWidth , screenWidth);
            //int inSampleSize = 2;
            LogUtils.Companion.e("BBB", "decodeGainMap imageWidth = " + imageWidth +" , imageHeight = " + imageHeight +" , inSampleSize = " + inSampleSize +" ,screenWidth = " + screenWidth);


            // 第三步:使用计算出的采样率解码图片
            BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
            decodeOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
            decodeOptions.inSampleSize = inSampleSize;

            result.baseImage = BitmapFactory.decodeFile(imagePath, decodeOptions);

            LogUtils.Companion.e("BBB", "decodeGainMap result imageWidth = " + result.baseImage.getWidth() +" , imageHeight = " + result.baseImage.getHeight());

            if (result.baseImage == null) {
                Log.e(TAG, "Failed to decode base image");
                return null;
            }

            // 尝试从EXIF数据读取GainMap信息
            ExifInterface exif = new ExifInterface(imagePath);

            // 检查是否存在GainMap相关标签
            boolean hasGainMap = hasGainMapMetadata(exif);

            if (hasGainMap || true) {
                Log.d(TAG, "GainMap metadata found, attempting to decode...");
                result.gainMap = extractGainMapFromExif(exif);
                readGainMapParameters(exif, result);
            } else {
                Log.d(TAG, "No GainMap metadata found, using base image only");
            }

        } catch (Exception e) {
            Log.e(TAG, "Error decoding GainMap: " + e.getMessage(), e);
            // 出错时至少返回基础图像
            if (result.baseImage == null) {
                try {
                    result.baseImage = BitmapFactory.decodeFile(imagePath);
                } catch (Exception ex) {
                    Log.e(TAG, "Failed to decode base image as fallback", ex);
                }
            }
        }

        return result;
    }

    /**
     * 计算合适的采样率
     * @param imageWidth 图片原始宽度
     * @param targetWidth 目标宽度(屏幕宽度)
     * @return 采样率,总是2的幂次方
     */
    private static int calculateInSampleSize(int imageWidth, int targetWidth) {
        int inSampleSize = 1;

        if (imageWidth > targetWidth) {
            // 计算理论采样率
            float ratio = (float) imageWidth / targetWidth;
            inSampleSize = Math.round(ratio);

            // 确保采样率是2的幂次方(BitmapFactory的要求)
            inSampleSize = roundToPowerOfTwo(inSampleSize);
        }

        // 设置最小和最大采样率限制
        inSampleSize = Math.max(1, inSampleSize);
        inSampleSize = Math.min(16, inSampleSize); // 防止采样率过大

        return inSampleSize;
    }

    /**
     * 将数值向上取整到最近的2的幂次方
     * 例如:3→4, 5→8, 9→16
     */
    private static int roundToPowerOfTwo(int value) {
        int power = 1;
        while (power < value) {
            power *= 2;
        }
        return power;
    }


    private static boolean hasGainMapMetadata(ExifInterface exif) {
        // 检查常见的GainMap相关EXIF标签
        String makerNote = exif.getAttribute(ExifInterface.TAG_MAKER_NOTE);
        String userComment = exif.getAttribute(ExifInterface.TAG_USER_COMMENT);
        LogUtils.Companion.e("BBB", "hasGainMapMetadata makerNote = " + makerNote +" , userComment = " + userComment);
        return (makerNote != null && makerNote.contains("GainMap")) ||
                (userComment != null && userComment.contains("GainMap")) ||
                exif.getAttribute("GainMapVersion") != null;
    }

    private static Bitmap extractGainMapFromExif(ExifInterface exif) {
        try {
            // 尝试从MakerNote或其他EXIF字段提取GainMap数据
            byte[] gainMapData = exif.getThumbnail();
            if (gainMapData != null && gainMapData.length > 0) {
                return BitmapFactory.decodeByteArray(gainMapData, 0, gainMapData.length);
            }
        } catch (Exception e) {
            Log.e(TAG, "Failed to extract GainMap from EXIF", e);
        }
        return null;
    }

    private static void readGainMapParameters(ExifInterface exif, GainMapResult result) {
        try {
            // 读取GainMap参数
            String gamma = exif.getAttribute("GainMapGamma");
            String hdrMin = exif.getAttribute("GainMapHDRMin");
            String hdrMax = exif.getAttribute("GainMapHDRMax");
            String offsetSdr = exif.getAttribute("GainMapOffsetSDR");
            String offsetHdr = exif.getAttribute("GainMapOffsetHDR");

            if (gamma != null) result.gamma = Float.parseFloat(gamma);
            if (hdrMin != null) result.hdrCapacityMin = Float.parseFloat(hdrMin);
            if (hdrMax != null) result.hdrCapacityMax = Float.parseFloat(hdrMax);
            if (offsetSdr != null) result.offsetSdr = Float.parseFloat(offsetSdr);
            if (offsetHdr != null) result.offsetHdr = Float.parseFloat(offsetHdr);

        } catch (Exception e) {
            Log.e(TAG, "Error reading GainMap parameters", e);
        }
    }


}
复制代码
package com.example.androidkotlindemo2.hdr;

import android.graphics.Bitmap;

import com.example.androidkotlindemo2.utils.LogUtils;

/**
 * Author : wn
 * Email : maoning20080809@163.com
 * Date : 2025/11/1 14:43
 * Description :
 */

public class GainMapProcessor {
    private static final String TAG = "GainMapProcessor";

    /**
     * 应用GainMap算法 - 优化曝光版本
     */
    public static Bitmap applyGainMapAlgorithm(Bitmap baseImage, Bitmap gainMap,
                                               float gamma, float hdrCapacityMin,
                                               float hdrCapacityMax, float offsetSdr,
                                               float offsetHdr) {
        if (baseImage == null) return null;
        if (gainMap == null) return baseImage;

        int width = baseImage.getWidth();
        int height = baseImage.getHeight();

        LogUtils.Companion.d("BBB", "applyGainMapAlgorithm " + baseImage.getWidth() +" , " + baseImage.getHeight() +" , " + gainMap.getWidth() +" , " + gainMap.getHeight());

        // 调整GainMap尺寸以匹配基础图像
        Bitmap scaledGainMap = Bitmap.createScaledBitmap(gainMap, width, height, true);

        // 创建结果Bitmap
        Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

        // 应用GainMap算法
        applyGainMapPixelsOptimized(baseImage, scaledGainMap, result,
                gamma, hdrCapacityMin, hdrCapacityMax,
                offsetSdr, offsetHdr);

        if (scaledGainMap != gainMap) {
            scaledGainMap.recycle();
        }

        return result;
    }

    private static void applyGainMapPixelsOptimized(Bitmap baseImage, Bitmap gainMap,
                                                    Bitmap result, float gamma,
                                                    float hdrCapacityMin, float hdrCapacityMax,
                                                    float offsetSdr, float offsetHdr) {
        int width = baseImage.getWidth();
        int height = baseImage.getHeight();

        int[] basePixels = new int[width * height];
        int[] gainPixels = new int[width * height];
        int[] resultPixels = new int[width * height];

        // 获取像素数据
        baseImage.getPixels(basePixels, 0, width, 0, 0, width, height);
        gainMap.getPixels(gainPixels, 0, width, 0, 0, width, height);

        // 直接复制基础图像到结果,避免暗像素的额外处理
        System.arraycopy(basePixels, 0, resultPixels, 0, basePixels.length);

        // 关键修复:analyzeBrightness 只调用一次!
        float[] brightnessStats = analyzeBrightness(basePixels, width, height);

        int brightCount = 0;
        // 处理每个像素
        for (int i = 0; i < basePixels.length; i++) {
            int basePixel = basePixels[i];

            // 快速亮度计算,避免函数调用开销
            int r = (basePixel >> 16) & 0xFF;
            int g = (basePixel >> 8) & 0xFF;
            int b = basePixel & 0xFF;

            // 快速亮度近似计算
            float pixelBrightness = (r * 0.299f + g * 0.587f + b * 0.114f) / 255.0f;

            // 提高亮度阈值,只处理真正的高光区域
            if (pixelBrightness < 0.85f) { // 从0.8提高到0.85
                continue;
            }
            brightCount++;
            int gainPixel = gainPixels[i];

            // 提取RGB分量
            float baseR = r / 255.0f;
            float baseG = g / 255.0f;
            float baseB = b / 255.0f;

            float gainR = ((gainPixel >> 16) & 0xFF) / 255.0f;
            float gainG = ((gainPixel >> 8) & 0xFF) / 255.0f;
            float gainB = (gainPixel & 0xFF) / 255.0f;

            // 应用自适应GainMap算法
            float[] hdrColor = applyAdaptiveGainMap(
                    baseR, baseG, baseB,
                    gainR, gainG, gainB,
                    pixelBrightness, brightnessStats,
                    gamma, hdrCapacityMin, hdrCapacityMax,
                    offsetSdr, offsetHdr
            );

            // 转换回ARGB并钳制
            int resultR = Math.max(0, Math.min(255, (int)(hdrColor[0] * 255)));
            int resultG = Math.max(0, Math.min(255, (int)(hdrColor[1] * 255)));
            int resultB = Math.max(0, Math.min(255, (int)(hdrColor[2] * 255)));

            resultPixels[i] = (basePixel & 0xFF000000) | (resultR << 16) | (resultG << 8) | resultB;
        }
        LogUtils.Companion.d("BBB", "brightCount = " + brightCount);
        result.setPixels(resultPixels, 0, width, 0, 0, width, height);
    }

    /**
     * 分析图像亮度统计信息
     */
    private static float[] analyzeBrightness(int[] pixels, int width, int height) {
        float totalBrightness = 0;
        float minBrightness = 1.0f;
        float maxBrightness = 0.0f;
        int sampleCount = 0;

        // 采样分析亮度
        for (int i = 0; i < pixels.length; i += 4) {
            int pixel = pixels[i];
            float r = ((pixel >> 16) & 0xFF) / 255.0f;
            float g = ((pixel >> 8) & 0xFF) / 255.0f;
            float b = (pixel & 0xFF) / 255.0f;

            float brightness = calculateLuminance(r, g, b);
            totalBrightness += brightness;
            minBrightness = Math.min(minBrightness, brightness);
            maxBrightness = Math.max(maxBrightness, brightness);
            sampleCount++;
        }

        float avgBrightness = totalBrightness / sampleCount;

        return new float[] {
                avgBrightness,    // 平均亮度
                minBrightness,    // 最小亮度
                maxBrightness,    // 最大亮度
                maxBrightness - minBrightness // 亮度范围
        };
    }

    /**
     * 自适应GainMap应用算法 - 优化曝光
     */
    private static float[] applyAdaptiveGainMap(float baseR, float baseG, float baseB,
                                                float gainR, float gainG, float gainB,
                                                float pixelBrightness, float[] brightnessStats,
                                                float gamma, float hdrCapacityMin,
                                                float hdrCapacityMax, float offsetSdr,
                                                float offsetHdr) {
        // 1. 对基础图像进行伽马解码
        float[] linearBase = srgbToLinear(baseR, baseG, baseB);

        // 2. 计算基础增益值 - 使用更保守的增益计算
        float baseGain = calculateConservativeGainFromGainMap(gainR, gainG, gainB, gamma,
                hdrCapacityMin, hdrCapacityMax);

        // 3. 根据像素亮度自适应调整增益 - 使用更保守的策略
        float adaptiveGain = calculateConservativeAdaptiveGain(baseGain, pixelBrightness, brightnessStats);

        // 4. 应用增益 - 使用更保守的增益应用
        float[] hdrLinear = applyConservativeGainToLinear(linearBase, adaptiveGain,
                pixelBrightness, offsetSdr, offsetHdr);

        // 5. 伽马编码回sRGB空间
        return linearToSrgb(hdrLinear[0], hdrLinear[1], hdrLinear[2]);
    }

    /**
     * 保守的增益计算 - 大幅降低增益强度
     */
    private static float calculateConservativeGainFromGainMap(float gainR, float gainG, float gainB,
                                                              float gamma, float hdrCapacityMin,
                                                              float hdrCapacityMax) {
        // 使用增益图的亮度信息
        float gainLuminance = calculateLuminance(gainR, gainG, gainB);

        // 对增益图进行伽马解码
        float decodedGain = (float) Math.pow(gainLuminance, 1.0 / gamma);

        // 归一化处理 - 使用更窄的范围
        float normalizedGain = (decodedGain - hdrCapacityMin) / (hdrCapacityMax - hdrCapacityMin);
        normalizedGain = Math.max(0.1f, Math.min(0.8f, normalizedGain)); // 上限从1.0降到0.8

        // 转换为线性增益值 - 大幅降低增益范围
        return 1.0f + normalizedGain * 0.6f; // 增益范围:1.0x - 1.6x (原来是1.0x - 2.5x)
    }

    /**
     * 保守的自适应增益调整
     */
    private static float calculateConservativeAdaptiveGain(float baseGain, float pixelBrightness,
                                                           float[] brightnessStats) {
        float avgBrightness = brightnessStats[0];
        float minBrightness = brightnessStats[1];
        float maxBrightness = brightnessStats[2];
        float brightnessRange = brightnessStats[3];

        // 更保守的自适应因子
        float adaptiveFactor;

        if (pixelBrightness < 0.3f) {
            // 暗部区域:轻微增加增益
            adaptiveFactor = 1.0f + (0.3f - pixelBrightness) * 0.3f; // 从1.5降到0.3
        } else if (pixelBrightness > 0.7f) {
            // 高光区域:显著降低增益
            adaptiveFactor = 0.7f + (1.0f - pixelBrightness) * 0.1f; // 从0.2降到0.1
        } else {
            // 中间调:基本不调整
            adaptiveFactor = 1.0f;
        }

        // 根据图像整体亮度进一步调整 - 更保守
        float imageBrightnessFactor;
        if (avgBrightness < 0.3f) {
            // 整体偏暗的图像:轻微增加整体增益
            imageBrightnessFactor = 1.0f + (0.3f - avgBrightness) * 0.3f; // 从1.0降到0.3
        } else if (avgBrightness > 0.7f) {
            // 整体偏亮的图像:降低整体增益
            imageBrightnessFactor = 0.8f; // 从0.7提高到0.8
        } else {
            imageBrightnessFactor = 1.0f;
        }

        return baseGain * adaptiveFactor * imageBrightnessFactor;
    }

    /**
     * 保守的增益应用 - 防止过曝
     */
    private static float[] applyConservativeGainToLinear(float[] linearBase, float gain,
                                                         float pixelBrightness,
                                                         float offsetSdr, float offsetHdr) {
        float[] result = new float[3];

        for (int i = 0; i < 3; i++) {
            float baseChannel = linearBase[i];

            // 更小的偏移量
            float dynamicOffsetSdr = offsetSdr * (1.0f - pixelBrightness) * 0.05f; // 从0.1降到0.05
            float dynamicOffsetHdr = offsetHdr * (1.0f - pixelBrightness) * 0.05f; // 从0.1降到0.05

            // 应用偏移
            float adjustedBase = baseChannel + dynamicOffsetSdr;

            // 应用增益
            float hdrChannel = adjustedBase * gain;

            // 应用HDR偏移
            hdrChannel += dynamicOffsetHdr;

            // 更严格的动态钳制
            float maxValue;
            if (pixelBrightness > 0.9f) {
                maxValue = 1.0f; // 极高光区域严格限制
            } else if (pixelBrightness > 0.8f) {
                maxValue = 1.2f; // 高光区域较严格限制
            } else {
                maxValue = 1.5f; // 其他区域适中限制
            }

            hdrChannel = Math.max(0, Math.min(maxValue, hdrChannel));

            result[i] = hdrChannel;
        }

        return result;
    }

    /**
     * sRGB到线性颜色空间转换
     */
    private static float[] srgbToLinear(float r, float g, float b) {
        return new float[] {
                srgbToLinearSingle(r),
                srgbToLinearSingle(g),
                srgbToLinearSingle(b)
        };
    }

    private static float srgbToLinearSingle(float channel) {
        if (channel <= 0.04045f) {
            return channel / 12.92f;
        } else {
            return (float) Math.pow((channel + 0.055f) / 1.055f, 2.4f);
        }
    }

    /**
     * 计算亮度
     */
    private static float calculateLuminance(float r, float g, float b) {
        return 0.2126f * r + 0.7152f * g + 0.0722f * b;
    }

    /**
     * 线性到sRGB颜色空间转换
     */
    private static float[] linearToSrgb(float r, float g, float b) {
        return new float[] {
                linearToSrgbSingle(r),
                linearToSrgbSingle(g),
                linearToSrgbSingle(b)
        };
    }

    private static float linearToSrgbSingle(float channel) {
        if (channel <= 0.0031308f) {
            return channel * 12.92f;
        } else {
            return (float) (1.055f * Math.pow(channel, 1.0 / 2.4) - 0.055f);
        }
    }

    /**
     * 超保守版本 - 防止过曝
     */
    public static Bitmap applyGainMapConservative(Bitmap baseImage, Bitmap gainMap) {
        // 使用更保守的参数
        float gamma = 1.8f; // 更高的gamma值
        float hdrCapacityMin = 0.3f; // 提高最小值
        float hdrCapacityMax = 0.6f; // 降低最大值
        float offsetSdr = 0.005f; // 减小偏移
        float offsetHdr = 0.002f; // 减小偏移

        return applyGainMapAlgorithm(baseImage, gainMap,
                gamma, hdrCapacityMin, hdrCapacityMax, offsetSdr, offsetHdr);
    }

    /**
     * 轻度增强版本 - 平衡效果和自然度
     */
    public static Bitmap applyGainMapBalanced(Bitmap baseImage, Bitmap gainMap) {
        float gamma = 1.5f;
        float hdrCapacityMin = 0.25f;
        float hdrCapacityMax = 0.65f;
        float offsetSdr = 0.008f;
        float offsetHdr = 0.004f;

        return applyGainMapAlgorithm(baseImage, gainMap,
                gamma, hdrCapacityMin, hdrCapacityMax, offsetSdr, offsetHdr);
    }
}
复制代码
hdr_image_decoder_main.xml布局
复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/hdr_show_btn1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textAllCaps="false"
            android:textSize="22sp"
            android:text="图片1"/>

        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/hdr_show_btn2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:layout_gravity="center_horizontal"
            android:textAllCaps="false"
            android:textSize="22sp"
            android:text="图片2 - GainMap算法"/>
    </LinearLayout>


    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/hdr_show_result"
        android:layout_width="match_parent"
        android:scaleType="centerCrop"
        android:layout_height="260dp"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="18dp"
        android:layout_marginLeft="20dp"
        android:textSize="28sp"
        android:textColor="@color/black"
        android:text="GainMap图"/>

    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/hdr_show_result2"
        android:layout_width="match_parent"
        android:scaleType="centerCrop"
        android:layout_height="260dp"/>

</LinearLayout>
相关推荐
雨白3 小时前
协程进阶:协作、互斥与共享状态管理
android·kotlin
用户41659673693553 小时前
深度剖析 Android Context:启动 Activity 与 View 创建的“内幕”
android
方白羽3 小时前
Android 唯一UUID方案
android·app
一个小狼娃3 小时前
Android集成Unity避坑指南
android·游戏·unity
川石课堂软件测试4 小时前
Python | 高阶函数基本应用及Decorator装饰器
android·开发语言·数据库·python·功能测试·mysql·单元测试
行走的陀螺仪4 小时前
Flutter 开发环境配置教程
android·前端·flutter·ios
前端与小赵5 小时前
uni-app开发安卓app时控制屏幕常亮不息屏
android·gitee·uni-app
百锦再5 小时前
第8章 模块系统
android·java·开发语言·python·ai·rust·go
QuantumLeap丶6 小时前
《Flutter全栈开发实战指南:从零到高级》- 12 -状态管理Bloc
android·flutter·ios