You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android实现图片适配手机样机屏幕并裁剪的问题求助

问题:将图片适配手机样机,仅在透明屏幕区域显示

我需要把用户从图库选择的图片放入带透明屏幕区域的手机样机中,让图片完全适配样机的屏幕区域,遵循边框的圆角曲线,仅在屏幕范围内显示。我编写了自定义ClippedImageView类实现裁剪逻辑,但运行后效果完全不符合预期。

当前实现代码

自定义ClippedImageView类

public class ClippedImageView extends androidx.appcompat.widget.AppCompatImageView {

    private Bitmap mOriginalBitmap;
    private Bitmap mMaskBitmap;

    public ClippedImageView(Context context) {
        super(context);
    }
    
    public ClippedImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    public ClippedImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    
        if (mOriginalBitmap != null && mMaskBitmap != null) {
            // Step 1: Prepare a paint object with Xfermode for clipping
            Paint paint = new Paint();
            paint.setAntiAlias(true);
            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    
            // Step 2: Draw the original image onto the canvas
            canvas.drawBitmap(mOriginalBitmap, 0, 0, null);
    
            // Step 3: Apply the mask (draw the mask on top of the image)
            canvas.drawBitmap(mMaskBitmap, 0, 0, paint);  // This will clip the original image
        }
    }
    
    // Method to set the original image (the content to be shown inside the phone screen)
    public void setOriginalBitmap(Bitmap originalBitmap) {
        this.mOriginalBitmap = originalBitmap;
        invalidate();  // Redraw the view
    }
    
    // Method to set the mobile mask image (the frame with transparent screen)
    public void setMaskBitmap(Bitmap maskBitmap) {
        this.mMaskBitmap = maskBitmap;
        invalidate();  // Redraw the view
    }
    
}

XML布局引用

<com.leomni.studios.screenshots.ClippedImageView 
    android:id="@+id/screen_base" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" />

调用代码

Bitmap maskBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.mobile_temp);                            
((ClippedImageView) screen_base).setOriginalBitmap(bitmap);                            
((ClippedImageView) screen_base).setMaskBitmap(maskBitmap);

注:bitmap为用户从图库选择的图片,maskBitmap为带透明屏幕的手机样机图。

问题表现

运行后图片未正确适配样机的透明屏幕区域,裁剪结果完全不符合预期。


解决方案

核心问题分析

  1. Xfermode使用逻辑错误:SRC_IN模式会保留后绘制的图(源图)与先绘制的图(目标图)的交集区域,但你当前的绘制顺序是先画原图、再画样机图,而样机图的屏幕区域是透明的,导致最终只保留样机的非透明边框部分,而非原图在屏幕区域的内容。
  2. 缺少离屏缓冲:直接在View画布上使用Xfermode容易和View自身绘制内容冲突,导致效果异常。
  3. 未处理图片尺寸适配:原图与样机图尺寸不匹配,会导致位置或大小错位。

修复后的自定义View代码

public class ClippedImageView extends androidx.appcompat.widget.AppCompatImageView {

    private Bitmap mOriginalBitmap;
    private Bitmap mMaskBitmap;
    private Paint mPaint;
    private Bitmap mOffscreenBitmap;

    public ClippedImageView(Context context) {
        super(context);
        init();
    }

    public ClippedImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ClippedImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        // 开启硬件加速兼容Xfermode
        setLayerType(LAYER_TYPE_HARDWARE, mPaint);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 仅在View尺寸变化时创建离屏位图,避免重复创建浪费内存
        if (mOffscreenBitmap != null) {
            mOffscreenBitmap.recycle();
        }
        mOffscreenBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mOriginalBitmap == null || mMaskBitmap == null || mOffscreenBitmap == null) {
            return;
        }

        Canvas offscreenCanvas = new Canvas(mOffscreenBitmap);
        // 清空离屏画布
        offscreenCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);

        // 1. 绘制用户图片,缩放适配到View尺寸(可根据样机屏幕区域调整Rect)
        RectF targetRect = new RectF(0, 0, getWidth(), getHeight());
        Matrix matrix = new Matrix();
        matrix.setRectToRect(
                new RectF(0, 0, mOriginalBitmap.getWidth(), mOriginalBitmap.getHeight()),
                targetRect,
                Matrix.ScaleToFit.CENTER_CROP
        );
        offscreenCanvas.drawBitmap(mOriginalBitmap, matrix, null);

        // 2. 设置Xfermode为SRC_ATOP:保留样机图的非透明边框,透明区域显示下方的用户图片
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
        offscreenCanvas.drawBitmap(mMaskBitmap, 0, 0, mPaint);

        // 3. 将离屏画布内容绘制到View画布
        canvas.drawBitmap(mOffscreenBitmap, 0, 0, null);

        // 重置Xfermode
        mPaint.setXfermode(null);
    }

    public void setOriginalBitmap(Bitmap originalBitmap) {
        this.mOriginalBitmap = originalBitmap;
        invalidate();
    }

    public void setMaskBitmap(Bitmap maskBitmap) {
        this.mMaskBitmap = maskBitmap;
        invalidate();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        // 回收位图避免内存泄漏
        if (mOffscreenBitmap != null) {
            mOffscreenBitmap.recycle();
        }
        if (mOriginalBitmap != null) {
            mOriginalBitmap.recycle();
        }
        if (mMaskBitmap != null) {
            mMaskBitmap.recycle();
        }
    }
}

额外优化建议

  1. 精确适配样机屏幕区域:如果样机的屏幕区域不是充满整个View,可通过遍历样机图的像素获取透明区域的边界Rect,再将用户图片缩放适配到该Rect内。
  2. 异步处理图片缩放:若用户选择的图片尺寸过大,建议在子线程中完成缩放操作,避免阻塞主线程导致卡顿。

内容的提问来源于stack exchange,提问作者leo mani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 00:24:54