转载注明出处: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