轉載註明出處:https://blog.csdn.net/skysukai
1、背景
最近的一個項目,需要和圖像編輯打交道。而有關圖像編輯知識,之前或多或少接觸過,始終不成體系。這次項目正好可以系統梳理一次。先放幾張UI設計稿,看看要達到的目標:
圖1顯示當前方位、轉向角度
圖2 擦除無效區域
圖3 繪製線段
圖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 當前方位
當前方位類似於地圖導航中常用的方位圖標,用於表示當前用戶的朝向。拿到用戶朝向之後,只需以圖像中心點旋轉畫布即可。
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