Android中设置Canvas背景时图片拉伸问题求助
问题描述
我终于实现了在图片上添加用户签名的功能,通过学习Canvas绘图及保存图片与用户绘制内容的相关知识,我用mDrawLayout.setBackground(d);设置Canvas背景,但遇到了严重问题:图片被强制拉伸变形,原本竖版的证件图变成了横向拉伸的状态。
现有代码
DrawingView类
public class DrawingView extends View { public int width; public int height; static Path drawPath; private Paint drawPaint, canvasPaint; static int paintColor = 0xFFFF0000; private float STROKE_WIDTH = 5f; private Canvas drawCanvas; private Bitmap canvasBitmap; private boolean erase=false; public DrawingView(Context context, AttributeSet attrs){ super(context, attrs); setupDrawing(); setErase(erase); } private void setupDrawing(){ drawPath = new Path(); drawPaint = new Paint(); drawPaint.setColor(paintColor); drawPaint.setAntiAlias(true); drawPaint.setStrokeWidth(STROKE_WIDTH); drawPaint.setStyle(Paint.Style.STROKE); drawPaint.setStrokeJoin(Paint.Join.ROUND); drawPaint.setStrokeCap(Paint.Cap.ROUND); canvasPaint = new Paint(Paint.DITHER_FLAG); } public void clearDrawing() { setDrawingCacheEnabled(false); onSizeChanged(width, height, width, height); invalidate(); setDrawingCacheEnabled(true); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); width = w; height = h; canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); drawCanvas = new Canvas(canvasBitmap); } public void setErase(boolean isErase){ erase=isErase; drawPaint = new Paint(); if(erase) { setupDrawing(); int srcColor= 0x00000000; PorterDuff.Mode mode = PorterDuff.Mode.CLEAR; PorterDuffColorFilter porterDuffColorFilter = new PorterDuffColorFilter(srcColor, mode); drawPaint.setColorFilter(porterDuffColorFilter); drawPaint.setColor(srcColor); drawPaint.setXfermode(new PorterDuffXfermode(mode)); } else { setupDrawing(); } } @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint); canvas.drawPath(drawPath, drawPaint); } @Override public boolean onTouchEvent(MotionEvent event) { canvasPaint.setColor(paintColor); float touchX = event.getX(); float touchY = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: drawPath.moveTo(touchX, touchY); break; case MotionEvent.ACTION_MOVE: drawCanvas.drawPath(drawPath, drawPaint); drawPath.lineTo(touchX, touchY); break; case MotionEvent.ACTION_UP: drawPath.lineTo(touchX, touchY); drawCanvas.drawPath(drawPath, drawPaint); drawPath.reset(); break; default: return false; } invalidate(); return true; } public int getPaintAlpha(){ return Math.round((float)STROKE_WIDTH/255*100); } public void setPaintAlpha(int newAlpha){ STROKE_WIDTH=Math.round((float)newAlpha/100*255); drawPaint.setStrokeWidth(newAlpha); } }
SignatureActivity代码
public class SignatureActivity extends AppCompatActivity { SeekBar mThickness; private AnexoTemporario imagePath; private DrawingView mDrawLayout; Button erase, draw; private Paint drawPaint = new Paint(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_signature); mThickness = findViewById(R.id.thickness); mDrawLayout = findViewById(R.id.viewDraw); erase = findViewById(R.id.erase); draw= findViewById(R.id.draw); imagePath = (AnexoTemporario) getIntent().getSerializableExtra("IMAGEURL"); File file = new File(imagePath.getFilePath()); Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath()); Drawable d = new BitmapDrawable(getResources(), bitmap); mDrawLayout.setVisibility(View.VISIBLE); mDrawLayout.setDrawingCacheEnabled(true); mDrawLayout.setEnabled(true); mDrawLayout.setBackground(d); mDrawLayout.setRotation(90); mThickness.setMax(50); mThickness.setProgress(10); mDrawLayout.setPaintAlpha(mThickness.getProgress()); int currLevel = mDrawLayout.getPaintAlpha(); mThickness.setProgress(currLevel); mDrawLayout.invalidate(); erase.setOnClickListener(v -> { saveDrawing(); drawPaint.setColor(Color.TRANSPARENT); mDrawLayout.setErase(true); }); draw.setOnClickListener(v -> mDrawLayout.clearDrawing()); mThickness.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) { } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { mDrawLayout.setPaintAlpha(mThickness.getProgress()); } }); } public void saveDrawing() { Bitmap whatTheUserDrewBitmap = mDrawLayout.getDrawingCache(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); whatTheUserDrewBitmap.compress(Bitmap.CompressFormat.PNG, 100, baos); GetImageTask task = new GetImageTask(whatTheUserDrewBitmap, base64string -> { try { WriteSDCard writeSDCard = new WriteSDCard(this); writeSDCard.writeToSDFile("assinatura.png", base64string.getBytes()); Toast.makeText(this, "Assinatura Salva!", Toast.LENGTH_LONG).show(); Intent intent = new Intent(); intent.putExtra("ASSINATURA", "assinatura.png"); setResult(Activity.RESULT_OK, intent); finish(); } catch (Exception e) { e.printStackTrace(); Toast.makeText(this, "Erro ao tentar salvar assinatura!", Toast.LENGTH_LONG).show(); } }); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } }
XML布局代码
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".canvas.SignatureActivity" > <mobi.stos.gwmobile.canvas.DrawingView android:id="@+id/viewDraw" android:layout_width="match_parent" android:layout_height="0dp" android:scaleType="centerInside" app:layout_constraintBottom_toTopOf="@+id/ll" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <LinearLayout android:id="@+id/linearLayout2" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:weightSum="2" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"> <Button android:id="@+id/erase" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/thickness" android:layout_weight="1" android:text="SALVAR" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" /> <Button android:id="@+id/draw" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/erase" android:layout_weight="1" android:text="APAGAR" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </LinearLayout> <LinearLayout android:id="@+id/ll" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintBottom_toTopOf="@+id/linearLayout2" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginBottom="10dp" android:text="Tamanho da linha" /> <SeekBar android:id="@+id/thickness" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@+id/viewDraw" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/viewDraw" /> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
问题根源与解决方案
问题原因
- View的背景会强制适配View尺寸,无法保持图片比例,加上
setRotation(90)进一步打乱了布局逻辑 scaleType属性仅对ImageView生效,对自定义View的背景无效
修复方案
放弃使用View背景,改为在DrawingView的onDraw中手动绘制背景图,通过Matrix保持图片比例并处理旋转,同时修正触摸坐标适配变换后的画布。
1. 修改DrawingView类
public class DrawingView extends View { // 原有成员变量 public int width; public int height; static Path drawPath; private Paint drawPaint, canvasPaint; static int paintColor = 0xFFFF0000; private float STROKE_WIDTH = 5f; private Canvas drawCanvas; private Bitmap canvasBitmap; private boolean erase=false; // 新增背景图相关变量 private Bitmap backgroundBitmap; private Matrix backgroundMatrix = new Matrix(); public DrawingView(Context context, AttributeSet attrs){ super(context, attrs); setupDrawing(); setErase(erase); } // 新增设置背景图的方法 public void setBackgroundBitmap(Bitmap bitmap) { backgroundBitmap = bitmap; calculateBackgroundMatrix(); invalidate(); } // 计算背景图的适配矩阵(保持比例+旋转) private void calculateBackgroundMatrix() { if (backgroundBitmap == null || width == 0 || height == 0) return; backgroundMatrix.reset(); // 先旋转90度 backgroundMatrix.postRotate(90, backgroundBitmap.getWidth()/2f, backgroundBitmap.getHeight()/2f); // 旋转后宽高互换 float rotatedBitmapWidth = backgroundBitmap.getHeight(); float rotatedBitmapHeight = backgroundBitmap.getWidth(); float viewRatio = (float) width / height; float bitmapRatio = rotatedBitmapWidth / rotatedBitmapHeight; float scale; float translateX = 0; float translateY = 0; // 根据比例适配 if (viewRatio > bitmapRatio) { scale = (float) height / rotatedBitmapHeight; translateX = (width - rotatedBitmapWidth * scale) / 2; } else { scale = (float) width / rotatedBitmapWidth; translateY = (height - rotatedBitmapHeight * scale) / 2; } backgroundMatrix.postScale(scale, scale); backgroundMatrix.postTranslate(translateX, translateY); } private void setupDrawing(){ drawPath = new Path(); drawPaint = new Paint(); drawPaint.setColor(paintColor); drawPaint.setAntiAlias(true); drawPaint.setStrokeWidth(STROKE_WIDTH); drawPaint.setStyle(Paint.Style.STROKE); drawPaint.setStrokeJoin(Paint.Join.ROUND); drawPaint.setStrokeCap(Paint.Cap.ROUND); canvasPaint = new Paint(Paint.DITHER_FLAG); } public void clearDrawing() { setDrawingCacheEnabled(false); onSizeChanged(width, height, width, height); invalidate(); setDrawingCacheEnabled(true); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); width = w; height = h; canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); drawCanvas = new Canvas(canvasBitmap); calculateBackgroundMatrix(); // 尺寸变化时重新计算适配矩阵 } public void setErase(boolean isErase){ erase=isErase; drawPaint = new Paint(); if(erase) { setupDrawing(); int srcColor= 0x00000000; PorterDuff.Mode mode = PorterDuff.Mode.CLEAR; PorterDuffColorFilter porterDuffColorFilter = new PorterDuffColorFilter(srcColor, mode); drawPaint.setColorFilter(porterDuffColorFilter); drawPaint.setColor(srcColor); drawPaint.setXfermode(new PorterDuffXfermode(mode)); } else { setupDrawing(); } } @Override protected void onDraw(Canvas canvas) { // 先绘制背景图 if (backgroundBitmap != null) { canvas.drawBitmap(backgroundBitmap, backgroundMatrix, canvasPaint); } // 再绘制签名 canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint); canvas.drawPath(drawPath, drawPaint); } @Override public boolean onTouchEvent(MotionEvent event) { // 把触摸坐标转换为适配背景图后的坐标 Matrix inverseMatrix = new Matrix(); backgroundMatrix.invert(inverseMatrix); float[] touchPoint = {event.getX(), event.getY()}; inverseMatrix.mapPoints(touchPoint); float touchX = touchPoint[0]; float touchY = touchPoint[1]; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: drawPath.moveTo(touchX, touchY); break; case MotionEvent.ACTION_MOVE: drawCanvas.drawPath(drawPath, drawPaint); drawPath.lineTo(touchX, touchY); break; case MotionEvent.ACTION_UP: drawPath.lineTo(touchX, touchY); drawCanvas.drawPath(drawPath, drawPaint); drawPath.reset(); break; default: return false; } invalidate(); return true; } public int getPaintAlpha(){ return Math.round((float)STROKE_WIDTH/255*100); } public void setPaintAlpha(int newAlpha){ STROKE_WIDTH=Math.round((float)newAlpha/100*255); drawPaint.setStrokeWidth(newAlpha); } }
2. 修改SignatureActivity中的背景设置逻辑
// 替换原有setBackground和setRotation代码 // Drawable d = new BitmapDrawable(getResources(), bitmap); // mDrawLayout.setBackground(d); // mDrawLayout.setRotation(90); mDrawLayout.setBackgroundBitmap(bitmap);
3. 修改XML布局,移除无效的scaleType属性
<mobi.stos.gwmobile.canvas.DrawingView android:id="@+id/viewDraw" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintBottom_toTopOf="@+id/ll" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
内容的提问来源于stack exchange,提问作者Victor Pierre
相关产品推荐
相关产品推荐

