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>
相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
ai2work2 天前
ch23 综合复刻:从零做一个最小可用版本(capstone)
kotlin
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
ai2work2 天前
ch21 签名、校验与发版
kotlin
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui