裁切
裁切的本质就是不管你想绘制什么,最终canvas只会在你规定的区域里绘制你想要的东西,换句话说
裁切也相当于在你绘制好的自定义view中只显示出来你裁切的那一块,其余部分不展示
这里给出一个最简单的例子:
这个布局预览器看到的灰色边框就是自定义view的大小,明显的能看出来我们实际绘制的内容距离我们自定义view的距离。 这是查验裁切效果最好的方法。
代码就给出第三张图的代码:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.dly);
canvas.save();
canvas.clipRect(20,20,260,260);
canvas.drawBitmap(bitmap,0,0,mPaint);
canvas.restore();
canvas.save();
canvas.clipRect(20,300,260,460);
canvas.drawBitmap(bitmap,0,0,mPaint);
canvas.restore();
}
复制代码
二维变换
先看一段简单的translate的代码:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.dly);
canvas.translate(200,100);
canvas.drawBitmap(bitmap,0,0,mPaint);
}
复制代码
再看旋转
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.dly);
// canvas.translate(200,100);
canvas.save();
//注意旋转的角度 是以顺时针为正,逆时针为负
canvas.rotate(45,200,200);
canvas.drawBitmap(bitmap,0,0,mPaint);
canvas.restore();
//绘制这个中心点只是让你明白 是以哪个点为中心 进行旋转,
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setColor(Color.BLUE);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(10);
canvas.drawPoint(200,200,mPaint);
}
复制代码
我们当然可以以图片中心为轴点,旋转个90度 这样似乎更好理解
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.dly);
// canvas.translate(200,100);
canvas.save();
//注意旋转的角度 是以顺时针为正,逆时针为负
canvas.rotate(90,183,275);
canvas.drawBitmap(bitmap,0,0,mPaint);
canvas.restore();
复制代码
放大缩小
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.dly);
//等比例缩放 这里寻找中心点的方法 直接用bitmap实际的宽高来做,比之前的写死位置的更加直观
canvas.save();
canvas.scale(3.3f, 3.3f, bitmap.getWidth() / 2, bitmap.getHeight() / 2);
canvas.drawBitmap(bitmap, 0, 0, mPaint);
canvas.restore();
}
复制代码
还有个错切变化,比较简单,各位自研。
canvas变换的顺序
所有canvas的变化顺序 全是反着来的,这点要注意一下。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.dly);
int centerX=bitmap.getWidth()/2;
int centerY=bitmap.getHeight()/2;
// 目标效果 想 先移动 x轴 100个像素 再旋转90度 绘制
//所以代码层面 必须 先rotate 90 再 translate
canvas.save();
canvas.rotate(90,183,275);
canvas.translate(100,0);
//注意旋转的角度 是以顺时针为正,逆时针为负
canvas.drawBitmap(bitmap,0,0,mPaint);
canvas.restore();
}
复制代码
三维变换
要理解好三维变化,首先要理解好android的三维坐标系,注意这个和view的canvans的二维坐标系是不一样的
再看下 三维坐标系的旋转方向
此外,最重要的一点就是 android的三维坐标系所对应的类为camera,这个camera 可不是拍照的那个camera,引入的时候
要注意了,最后特别强调。
camera所有的rotate都是以view的原点为中心 也就是(0,0,0) 这个点。 且不支持设置旋转的轴心。
要想实现类似旋转轴心的效果,我们只能先把canvas挪到原点 然后进行 旋转,然后canvas 再挪回到我们想绘制的位置即可
注意canvas的混合变换是倒序的,这点千万不要忘记了
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Point point1 = new Point(200, 200);
canvas.save();
camera.save();
camera.rotateX(30);
camera.applyToCanvas(canvas);
camera.restore();
canvas.drawBitmap(bitmap, point1.x, point1.y, paint);
canvas.restore();
}
复制代码
然而这样的效果 显然我们不满意,按照之前的说法 我们应该canvas的坐标 先translate到原点,然后camera映射 再然后 translate 到我们目标绘制点,效果就能好很多。
注意这个translate的过程如果使用matrix则可以控制顺序 如果用原生的canvas的话 只能倒序,不要忘记这点
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//这个是我们想要绘制bitmap的 起点
Point point1 = new Point(200, 200);
//计算出我们bitmap的宽高
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
//计算出 我们bitmap 绘制结束以后的中心点
int center1X = point1.x + bitmapWidth / 2;
int center1Y = point1.y + bitmapHeight / 2;
camera.save();
matrix.reset();
camera.rotateX(30);
camera.getMatrix(matrix);
camera.restore();
//先translate到0,0这个点进行 rotateX
matrix.preTranslate(-center1X, -center1Y);
//rotateX 结束以后 再translate 到我们目标位置 进行绘制
matrix.postTranslate(center1X, center1Y);
canvas.save();
canvas.concat(matrix);
canvas.drawBitmap(bitmap, point1.x, point1.y, paint);
canvas.restore();
}
复制代码
这样一看效果好很多
所谓的翻页效果也不过就是在这个基础上进行的动画操作罢了。
绘制顺序
自定义view的绘制顺序很好理解,基本原则就是 后面绘制的会盖住前面绘制的
draw()方法是用来调度 绘制顺序的,主要绘制方法有
按绘制的先后顺序来:
先调用drawBackground方法,注意这个方法是私有的 我们无法重写这个方法噢。
然后 onDraw()方法,这个不用多说了。
再然后
dispatchDraw()方法,注意这个方法一般viewgroup才使用,纯正的view这个方法是几乎用不到的。
换句话说 对于viewgroup来说,总是先ondraw绘制完自己以后 再调用dispatchDraw()来绘制子view
所以有时候我们extends某些viewgroup的时候如果仅仅是在ondraw方法里面重写我们想要的效果,
结果往往看不到,因为ondraw方法走完以后 dispatchDraw() 绘制子view 把我们绘制的内容覆盖掉了
所以谨记viewgroup 自定义的时候 到底是在ondraw还是dispatchDraw 中重写 要考虑清楚了。
最后 在 ViewGroup 的子类中重写除 dispatchDraw() 以外的绘制方法时,可能需要调用 setWillNotDraw(false); 在重写的方法有多个选择时,优先选择 onDraw()。
还有个在 onDrawForeground()这个方法是最后调用的,用来绘制滑动条和前景的。这个用的不多大家可以参考下。 有些蒙版效果 要用这个方法实现。