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为带透明屏幕的手机样机图。
问题表现
运行后图片未正确适配样机的透明屏幕区域,裁剪结果完全不符合预期。
解决方案
核心问题分析
- Xfermode使用逻辑错误:
SRC_IN模式会保留后绘制的图(源图)与先绘制的图(目标图)的交集区域,但你当前的绘制顺序是先画原图、再画样机图,而样机图的屏幕区域是透明的,导致最终只保留样机的非透明边框部分,而非原图在屏幕区域的内容。 - 缺少离屏缓冲:直接在View画布上使用Xfermode容易和View自身绘制内容冲突,导致效果异常。
- 未处理图片尺寸适配:原图与样机图尺寸不匹配,会导致位置或大小错位。
修复后的自定义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(); } } }
额外优化建议
- 精确适配样机屏幕区域:如果样机的屏幕区域不是充满整个View,可通过遍历样机图的像素获取透明区域的边界Rect,再将用户图片缩放适配到该Rect内。
- 异步处理图片缩放:若用户选择的图片尺寸过大,建议在子线程中完成缩放操作,避免阻塞主线程导致卡顿。
内容的提问来源于stack exchange,提问作者leo mani
相关产品推荐
相关产品推荐

