Canvas高级使用以实际操作
Paint画笔大家一般都知道,就是画笔,设置一些样式,粗细,颜色等等属性。
Canvas字面翻译就是画布,本质其实是一个绘制图形的工具类。
注意:canvas不是具体执行绘制的对象,具体执行的是GPU
坐标体系:
默认的坐标零点位于屏幕左上角
向下为Y轴正方向,向右为X轴正方向
canvas坐标体系一经确定不会改变的。
绘制坐标系:特性在修改坐标后,这个过程中是不可逆的。
主要方法
1、Canvas.save()
可以理解为保存画布,作用是将之前的所有已绘制的图像保存起来,让后续的操作就好像在一个新的图层上操作一样
2、Canvas.restore()
可以理解为合并图层操作,作用是将save()之后绘制的所有图像与sava()之间的图像进行合并
3、Canvas.translate()
绘图坐标的平移
4、Canvas.rotare()
绘图坐标的翻转
Matrix变换矩阵运算方式
image.pngscale是缩放,skew是错切,trans是平移,persp代表透视。
rotare翻转则是改变的是scale,skew,trans三个的值。
Canvas里面牵扯两种坐标系:Canvas自己的坐标系、绘图坐标系
Canvas的坐标系:
它就在View的左上角,做坐标原点往右是X轴正半轴,往下是Y轴的正半轴,有且只有一个,唯一不变
绘图坐标系:
它不是唯一不变的,它与Canvas的Matrix有关系,当Matrix发生改变的时候,绘图坐标系对应的进行改变, 同时这个过程是不可逆的(save和restore方法来保存和还原变化操作), Matrix又是通过我们设置translate、rotate、scale、skew来进行改变的
Canvas的状态保存---状态栈、Layer栈
状态栈--save、 restore方法来保存和还原变换操作Matrix以及Clip剪裁,也可以通过restoretoCount直接还原到对应栈的保存状态.
比如你用画笔工具画东西的时候,本来就有一层,你调用一层sava,就有两层,在调用一次sava就有三层,也就是说调用两次sava,有三层,从低往高的形式。那restore可以理解为出栈的方式。调用restore之后会出栈,把容器里绘制的东西都会整合到一起。还有个restoreacount方法。比如说容器里面有三层,理解为三个栈,然后调用restoreaccount(2),首先会把容器里面所有东西都组合起来,保存起来,然后第三层的时候就出栈。
Layer栈:saveLayer的时候都会新建一个透明的图层(离屏Bitmap-离屏缓冲),并且会将saveLayer之前的一些Canvas操作延续过来, 后续的绘图操作都在新建的layer上面进行, 当我们调用restore 或者 restoreToCount 时 更新到对应的图层和画布上.
sava和savaLayer差不多,只不过sava是一个不透明的图层,savaLayer是一个透明的图层。
以下有两个例子;分别是刮刮卡还有画一个钟表的例子
刮刮卡例子:
public class GuaGuaCardView_SRCOUT extends View {
private Paint mBitPaint;
private Bitmap BmpDST,BmpSRC,BmpText;
private Path mPath;
private float mPreX,mPreY;
public GuaGuaCardView_SRCOUT(Context context) {
super(context);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mBitPaint = new Paint();
mBitPaint.setColor(Color.RED);
mBitPaint.setStyle(Paint.Style.STROKE);
mBitPaint.setStrokeWidth(45);
BmpText = BitmapFactory.decodeResource(getResources(), R.drawable.guaguaka_text1,null);
BmpSRC = BitmapFactory.decodeResource(getResources(), R.drawable.guaguaka,null);
BmpDST = Bitmap.createBitmap(BmpSRC.getWidth(), BmpSRC.getHeight(), Bitmap.Config.ARGB_8888);
mPath = new Path();
}
public GuaGuaCardView_SRCOUT(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mBitPaint = new Paint();
mBitPaint.setColor(Color.RED);
mBitPaint.setStyle(Paint.Style.STROKE);
mBitPaint.setStrokeWidth(45);
BmpText = BitmapFactory.decodeResource(getResources(), R.drawable.guaguaka_text1,null);
BmpSRC = BitmapFactory.decodeResource(getResources(), R.drawable.guaguaka,null);
BmpDST = Bitmap.createBitmap(BmpSRC.getWidth(), BmpSRC.getHeight(), Bitmap.Config.ARGB_8888);
mPath = new Path();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(BmpText,0,0,mBitPaint);
// canvas.save();
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
//先把手指轨迹画到目标Bitmap上
Canvas c = new Canvas(BmpDST);
c.drawPath(mPath,mBitPaint);
//然后把目标图像画到画布上
canvas.drawBitmap(BmpDST,0,0,mBitPaint);
//计算源图像区域
mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
canvas.drawBitmap(BmpSRC,0,0,mBitPaint);
mBitPaint.setXfermode(null);
canvas.restoreToCount(2);
}
@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();
break;
case MotionEvent.ACTION_UP:
break;
}
postInvalidate();
return super.onTouchEvent(event);
}
}
钟表例子:
public class WatchView extends View {
private static final String TAG = "WatchView";
private int mWidth;
private int mHerght;
public WatchView(Context context) {
super(context);
}
public WatchView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public WatchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = getMeasuredWidth();
mHerght = getMeasuredHeight();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画外圆
Paint paintCircle = new Paint();
paintCircle.setStrokeWidth(5);
paintCircle.setAntiAlias(true);
paintCircle.setStyle(Paint.Style.STROKE);
canvas.drawCircle(mWidth / 2,
mHerght / 2, mWidth / 2, paintCircle);
//画刻度线
Paint paintDegree = new Paint();
paintCircle.setStrokeWidth(3);
for (int i = 0; i < 12; i++) {
if (i == 0 || i == 3 || i == 6 || i == 9) {
paintDegree.setStrokeWidth(5);
paintDegree.setTextSize(100);
canvas.drawLine(mWidth / 2, mHerght / 2 - mWidth / 2,
mWidth / 2, mHerght / 2 - mWidth / 2 + 60,
paintDegree);
String degree = String.valueOf(i);
canvas.drawText(degree, mWidth / 2 - paintDegree.measureText(degree) / 2,
mHerght / 2 - mWidth / 2 + 160, paintDegree);
} else {
paintDegree.setStrokeWidth(3);
paintDegree.setTextSize(50);
canvas.drawLine(mWidth / 2, mHerght / 2 - mWidth / 2,
mWidth / 2, mHerght / 2 - mWidth / 2 + 30,
paintDegree);
String degree = String.valueOf(i);
canvas.drawText(degree, mWidth / 2 - paintDegree.measureText(degree) / 2,
mHerght / 2 - mWidth / 2 + 60, paintDegree);
}
canvas.rotate(30, mWidth / 2, mHerght / 2);
}
//画指针
Paint paintHour = new Paint();
paintHour.setStrokeWidth(20);
Paint paintMinute = new Paint();
paintMinute.setStrokeWidth(10);
// Log.i(TAG, "Current SaveCount1 = " + canvas.getSaveCount());
// canvas.save();
// Log.i(TAG, "Current SaveCount2 = " + canvas.getSaveCount());
canvas.translate(mWidth/2, mHerght/2);
canvas.drawLine(0,0,100,100,paintHour);
canvas.drawLine(0,0,100,200,paintMinute);
// canvas.restore();
Log.i(TAG, "Current SaveCount3 = " + canvas.getSaveCount());
}
}