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

發佈了17 篇原創文章 · 獲贊 1 · 訪問量 5869
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章