Android中Canvas操作

转载注明出处:https://blog.csdn.net/skysukai

1、背景

最近的一个项目,需要和图像编辑打交道。而有关图像编辑知识,之前或多或少接触过,始终不成体系。这次项目正好可以系统梳理一次。先放几张UI设计稿,看看要达到的目标:
图1显示当前方位、转向角度
图1显示当前方位、转向角度
图2 擦除无效区域
图2 擦除无效区域
图3 绘制线段
图3 绘制线段
图4 绘制Icon
图4 绘制Icon
除此之外,还有诸如图像刷新、缩放、缩放后平移、居中显示等功能。

2、具体实现

2.1 图像加载、居中显示、刷新及显示当前方位

2.1.1 图像加载

图像加载其实原理很简单,只需要在canvas上绘制bitmap即可:

   Bitmap bitmap = BitmapFactory.decodeResource(resource, R.mipmap.demo).copy(Bitmap.Config.RGB_565, true);
   canvas.drawBitmap(bitmap, 0 ,0 , null);

2.1.2 居中显示

居中显示需要比对bitmap和屏幕的长宽,通过计算得出bitmap的哪条边需要压缩。

protected float mCenterScale; // 图片适应屏幕时的缩放倍数
protected int mCenterHeight, mCenterWidth;// 图片适应屏幕时的大小(View窗口坐标系上的大小)
protected float mCentreTranX, mCentreTranY;// 图片在适应屏幕时,位于居中位置的偏移(View窗口坐标系上的偏移)
protected float mScale = 1; // 在适应屏幕时的缩放基础上的缩放倍数 ( 图片真实的缩放倍数为 mCenterScale*mScale )
protected float mTransX = 0, mTransY = 0; // 图片在适应屏幕且处于居中位置的基础上的偏移量
protected void initBound() {
    int w = mBitmap.getWidth();
    int h = mBitmap.getHeight();
    float nw = w * 1f / getWidth();
    float nh = h * 1f / getHeight();
    if (nw > nh) {
            mCenterScale = 1 / nw;
            mCenterWidth = getWidth();
            mCenterHeight = (int) (h * mCenterScale);
    } else {
            mCenterScale = 1 / nh;
            mCenterWidth = (int) (w * mCenterScale);
            mCenterHeight = getHeight();
    }
    // 使图片居中
    mCentreTranX = (getWidth() - mCenterWidth) / 2f;
    mCentreTranY = (getHeight() - mCenterHeight) / 2f;
    // 居中适应屏幕
    mTransX = mTransY = 0;
    mScale = 1;
}

2.1.3 图片刷新

调用invalidate或者postInvalidate即可触发自定义view的重绘流程,达到刷新界面的效果。

public void refresh() {
   if (Looper.myLooper() == Looper.getMainLooper()) {
       invalidate();
   } else {
       postInvalidate();
   }
}

2.2 图像擦除

图像擦除的其实就是设置paint的颜色,然后在canvas上绘制手指的轨迹。Android系统封装了Path类,可以用Path类来记录手指轨迹。

public class MyView extends View {
  
 private Path mPath = new Path();
 private float mPreX,mPreY;
  
 public MyView(Context context, @Nullable AttributeSet attrs) {
  super(context, attrs);
 }
  
 @Override
 public boolean onTouchEvent(MotionEvent event) {
  switch (event.getAction()) {
   case MotionEvent.ACTION_DOWN: {
    //记录下起点
    mPath.moveTo(event.getX(), event.getY());
    mPreX = event.getX();
    mPreY = event.getY();
    return true;
   }
   case MotionEvent.ACTION_MOVE:
    //记录下终点
    float endX = (mPreX+event.getX())/2;
    float endY = (mPreY+event.getY())/2;
    //使用贝塞尔曲线使手指轨迹更加圆滑
    mPath.quadTo(mPreX,mPreY,endX,endY);
    mPreX = event.getX();
    mPreY = event.getY();
    //触发view不断重绘
    invalidate();
    break;
   default:
    break;
  }
  return super.onTouchEvent(event);
 }
  
 public void reset(){
  mPath.reset();
  invalidate();
 }
  
 @Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  Paint paint = new Paint();
  //设置画笔颜色样式
  paint.setColor(Color.WHITE);
  paint.setStyle(Paint.Style.STROKE);
  //绘制path
  canvas.drawPath(mPath, paint);
 }
}

2.3 绘制线段

绘制线段和图像擦除有相似之处,只是直线无需对线条进行平滑处理。

public class MyView extends View {
  
 private Path mPath = new Path();
 private float mPreX,mPreY;
  
 public MyView(Context context, @Nullable AttributeSet attrs) {
  super(context, attrs);
 }
  
 @Override
 public boolean onTouchEvent(MotionEvent event) {
  switch (event.getAction()) {
   case MotionEvent.ACTION_DOWN: {
    //记录下起点
    mPath.moveTo(event.getX(), event.getY());
    return true;
   }
   case MotionEvent.ACTION_Move:
    //记录下终点
    float endX = (mPreX+event.getX())/2;
    float endY = (mPreY+event.getY())/2;
    //绘制直线
    mPath.lineTo(endX,endY);
    //触发view重绘
    invalidate();
    break;
   default:
    break;
  }
  return super.onTouchEvent(event);
 }
  
 public void reset(){
  mPath.reset();
  invalidate();
 }
  
 @Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  Paint paint = new Paint();
  //设置画笔颜色样式
  paint.setColor(Color.WHITE);
  paint.setStyle(Paint.Style.STROKE);
  //绘制path
  canvas.drawPath(mPath, paint);
 }
}

2.4 绘制ICON

绘制ICON时,需要知道ICON的坐标。ICON绘制完成之后,还需在它旁边绘制text。

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

  //绘制图标
  Bitmap icon = BitmapFactory.decodeResource(resource, R.mipmap.icon).copy(Bitmap.Config.ARGB_8888, true);
  canvas.drawBitmap(icon, x, y, null);
  //设置text背景画笔
  Paint rectPaint = new Paint();
  rectPaint.setColor(Color.GRAY);
  rectPaint.setStyle(Paint.Style.FILL);
  //绘制背景底色
  canvas.drawRect(rectF, rectPaint);
  //设置text画笔颜色
  Paint textPaint = new Paint();
  textPaint.setColor(Color.BLACK);
  textPaint.setTextSize(25);
  textPaint.setStyle(Paint.Style.FILL);
  //设置基线点到底为center
  textPaint.setTextAlign(Paint.Align.CENTER);
  canvas.drawText(mName, rectF.centerX(), getBaseLineY(rectF, textPaint), textPaint);
 }
 
private int getBaseLineY(RectF rectF, Paint paint) {
  Paint.FontMetrics fontMetrics = paint.getFontMetrics();
  float top = fontMetrics.top;//基线到字体上边框的距离
  float bottom = fontMetrics.bottom;//基线到字体下边框的距离

  int baseLineY = (int) (rectF.centerY() - top/2 - bottom/2);//基线中间点的y轴计算公式

  return baseLineY;
}

有关text绘制中baseline的设置稍显复杂,可参考这篇博文传送门

2.5 其他操作

2.5.1 绘制当前方位

图5 方位
图5 当前方位
当前方位类似于地图导航中常用的方位图标,用于表示当前用户的朝向。拿到用户朝向之后,只需以图像中心点旋转画布即可。

	Resources resource = getResources();
	Bitmap orientation = BitmapFactory.decodeResource(resource,R.mipmap.nav).copy(Bitmap.Config.ARGB_8888,true;
	canvas.rotate(mAngle, mLocationX,  mLocationY);//旋转画布
	canvas.drawBitmap(orientation, (float) (mLocationX), (float)(mLocationY) , null);

2.5.2 图像缩放

Android已经提供了ScaleGestureDetectorApi27来监听缩放操作,只需拿到缩放比例,对canvas进行缩放即达到了缩放图像的目的。

canvas.scale(scale, scale); // 缩放画布

2.5.3 图像平移

直接调用canvas的translate方法即可。

canvas.translate(left, top); // 偏移画布

3、结语

以上给出了canvas的一些基本操作。但是,还无法应用到产品中。最后完成的架构还参考了这篇博文

相关参考:https://blog.csdn.net/u012964944/article/details/82703684
相关参考:https://github.com/1993hzw/Doodle
相关参考:https://www.jb51.net/article/162344.htm
相关参考:https://www.2cto.com/kf/201804/740438.html
相关参考:https://www.cnblogs.com/slgkaifa/p/7101297.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值