自定义 绘制的方式是重写 绘制方法。其中最常用的是 onDraw()方法
绘制的关键是 Canvas 的使用
Canvas 的绘制类方法:drawXX(),关键参数为Paint
Canvas 的辅助类方法:范围裁切 和 几何变换,可以使用不同的绘制方法来控制 遮盖关系
绘制类方法:
Paint 类中最常用的方法。
- Paint.setStyle(Style style) 设置绘制模式
- Paint.setColor(int color) 设置颜色
- Paint.setStrokeWidth(float width) 设置线条宽度
- Paint.setTextSize(float textSize) 设置文字大小
- Paint.setAntiAlias(boolean aa) 设置抗锯齿开关
Canvas 中的常用方法,canvas 类下的 draw 打头的方法,例如drawCircle(),drawBitmp()
-
Canvas.drawColor(@ColorInt int color) 颜色填充
例如:
drawColor(Color.BLACK)
会把整个区域染成纯黑色,覆盖掉原有内容;drawColor(Color.parse("#88880000")` 会在原有的绘制效果上加一层半透明的红色遮罩。
类似的方法还有 drawRGB 和 drawARGB ,他们只是使用方式不同,作用都是一样的。
-
drawCircle(float centerX, float centerY, float radius, Paint paint) 画圆
前两个参数是圆心的坐标,第三个参数是 圆的半径,单位都是像素第四个就是 Paint 了。
-
drawRect(float left, float top, float right, float bottom, Paint paint) 画矩形
前四个参数分别对应四个坐标,最后是 Paint
-
drawPoint(float x, float y, Paint paint) 画点
x 和 y 是坐标
点的 大小可以通过paint.setStrokeWidth()来设置,点的形状可以通过 paint .setStrokeCap 来设置
-
drawPoints(float[] pts, int offset, int count, Paint paint) / drawPoints(float[] pts, Paint paint) 画点(批量)
-
drawOval(float left, float top, float right, float bottom, Paint paint) 画椭圆
只能绘制 横着或者竖着的椭圆。斜着的需要使用几何变换。四个参数分别对应四个边界的坐标
-
drawLine(float startX, float startY, float stopX, float stopY, Paint paint) 画线
前两个是开始的坐标,然后是终点的坐标。
-
drawLines(float[] pts, int offset, int count, Paint paint) / drawLines(float[] pts, Paint paint) 画线(批量)
-
drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint) 画圆角矩形
前四个是坐标,rx 和 ry 是圆角的 横向半径和 纵向半径
-
drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint) 绘制弧形或扇形
drawArc()
是使用一个椭圆来描述弧形的。left
,top
,right
,bottom
描述的是这个弧形所在的椭圆;startAngle
是弧形的起始角度(x 轴的正向,即正右的方向,是 0 度的位置;顺时针为正角度,逆时针为负角度),sweepAngle
是弧形划过的角度;useCenter
表示是否连接到圆心,如果不连接到圆心,就是弧形,如果连接到圆心,就是扇形。 -
drawPath(Path path, Paint paint) 画自定义图形
Path 可以描述直线二次曲线三次曲线,圆,椭圆,弧形,圆角矩形。将这些图形结合起来就可以描述出 很多复杂的图形
Path 可以描述直线,二次曲线,三次曲线,圆,椭圆,圆角矩形,将这些结合起来,就可以绘制很多复杂的图形。
Path 有两类方法,一类是描述路径的,另一类是辅助的设置或者计算
第一类:描述路径
-
addCircle(float x, float y, float radius, Direction dir) 添加圆
前两个时候 圆心的 坐标,然后就是半径。最后是 路径,有两种,是CW / 顺时针 和 CCW / 逆时针
-
xxxTo()——画线(直线或曲线)
-
lineTo(float x, float y) / rLineTo(float x, float y) 画直线
从当前位置 向目标位置画一条线,x,y 对应的是目标位置的坐标,这两个区别 第一个是绝对坐标,第二个是 相对坐标
path.lineTo(100, 100); // 由当前位置 (0, 0) 向 (100, 100) 画一条直线 path.rLineTo(100, 0); // 由当前位置 (100, 100) 向正右方 100 像素的位置画一条直线
-
quadTo(float x1, float y1, float x2, float y2) / rQuadTo(float dx1, float dy1, float dx2, float dy2) 画二次贝塞尔曲线
四个参数对应 控制点 和终点的坐标,第二个参数是 相对坐标。
-
cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) / rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) 画三次贝塞尔曲线
和上面的同理
-
moveTo(float x, float y) / rMoveTo(float x, float y) 移动到目标位置
不论是直线还是贝塞尔曲线,都是以当前位置作为起点,而不能指定起点。但你可以通过
moveTo(x, y)
或rMoveTo()
来改变当前位置,从而间接地设置这些方法的起点//开始的位置 path.moveTo(100, 100); // 线的末端,分别对应 x 和 y path.lineTo(300, 100); canvas.drawPath(path, mPaint);
-
arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) / arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo) / arcTo(RectF oval, float startAngle, float sweepAngle) 画弧形
这个方法和
Canvas.drawArc()
比起来,少了一个参数useCenter
,而多了一个参数forceMoveTo
。少了
useCenter
,是因为arcTo()
只用来画弧形而不画扇形,所以不再需要useCenter
参数;而多出来的这个forceMoveTo
参数的意思是,绘制是要「抬一下笔移动过去」,还是「直接拖着笔过去」,区别在于是否留下移动的痕迹。第二类:辅助的设置或计算
-
Path.setFillType(Path.FillType ft) 设置填充方式
-
方法中填入不同的
FillType
值,就会有不同的填充效果。FillType
的取值有四个:EVEN_ODD
WINDING
(默认值)INVERSE_EVEN_ODD
INVERSE_WINDING
示例:
Paint :该类保存了绘制几何 图形,文本 和位图 的样式和颜色信息。也就是说 我们可以使用Paint 保存的样式 和 颜色 还绘制 图形,文本 和 bitmap ,这就是 Paint 的 强大之处。
Paint 的使用
使用 Paint 之前需要初始化
mPaint = new Paint();
常用的 方法
mPaint.setColor(Color.BLUE);
//alpha 的范围是 0-255,是一个 int值
mPaint.setAlpha(255);
//设置画笔的样式
mPaint.setStyle(Paint.Style.FILL);// 填充内容
mPaint.setStyle(Paint.Style.STROKE);//描边
mPaint.setStyle(Paint.Style.FILL_AND_STROKE); //填充内容并 描边
//设置画笔的宽度
mPaint.setStrokeWidth(50);
//设置画笔的线帽,共三种
mPaint.setStrokeCap(Paint.Cap.BUTT);// 默认
mPaint.setStrokeCap(Paint.Cap.ROUND); // 圆形
mPaint.setStrokeCap(Paint.Cap.SQUARE); //直线
//路线
Path path = new Path();
//开始的位置
path.moveTo(100, 100);
// 线的末端,分别对应 x 和 y
path.lineTo(300, 100);
//线的 连接处
mPaint.setStrokeJoin(Paint.Join.BEVEL); //直线
mPaint.setStrokeJoin(Paint.Join.ROUND);//圆弧
mPaint.setStrokeJoin(Paint.Join.MITER);//锐角
//防锯齿 损失性能
mPaint.setAntiAlias(true);
//抖动处理 损失性能
mPaint.setDither(true);
//Shader ,着色器,用于绘制颜色,他和直接设置颜色的区别就是 ,着色器 设置的是一个颜色方案,或者是一套规则。
//当设置了 Shader 之后,Paint 在绘制图形和位置时 就不使用 setColor 设置的颜色了
//在绘制里面使用 Shader ,不是直接使用这个类,而是用他的几个子类:linearGradient ,RadiaGradient
//SweepGradient ,BitmapShader ,ComposeShader 这几个。
//LinearGradient :线性渐变,设置两个点的两种颜色,两个点的中级则为渐变。
Shader shader = new LinearGradient(100,100,500,500,Color.RED,Color.BLUE, Shader.TileMode.CLAMP);
paint.setShader(shader);
canvas.drawCircle(300,300,300,paint);
// 更多的用法可查看这篇 文章:https://hencoder.com/ui-1-2/
三种 线帽 的样子:
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
mPaint = new Paint();
mPaint.setColor(Color.BLUE);
//alpha 的范围是 0-255,是一个 int值
mPaint.setAlpha(255);
//画笔的样式
mPaint.setStyle(Paint.Style.FILL_AND_STROKE); //填充内容并 描边
//设置画笔的宽度
mPaint.setStrokeWidth(50);
//设置画笔的线帽
mPaint.setStrokeCap(Paint.Cap.SQUARE); // 圆形
//路线
Path path = new Path();
//开始的位置
path.moveTo(100, 100);
// 线的末端,分别对应 x 和 y
path.lineTo(300, 100);
canvas.drawPath(path, mPaint);
//重置
mPaint.reset();
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setStrokeWidth(50);
mPaint.setStrokeCap(Paint.Cap.ROUND); //圆形
Path path1 = new Path();
path1.moveTo(100,200);
path1.lineTo(300,200);
canvas.drawPath(path1,mPaint);
//重置
mPaint.reset();
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setStrokeWidth(50);
mPaint.setStrokeCap(Paint.Cap.BUTT);//没有
Path path2 = new Path();
path2.moveTo(100,300);
path2.lineTo(300,300);
canvas.drawPath(path2,mPaint);
}
以上就是三种 键帽的对比
下面看一下 线 连接处 和 填充/描边 的对比
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
mPaint = new Paint();
mPaint.setColor(Color.BLUE);
//alpha 的范围是 0-255,是一个 int值
mPaint.setAlpha(255);
//画笔的样式
mPaint.setStyle(Paint.Style.STROKE); //描边
//设置画笔的宽度
mPaint.setStrokeWidth(50);
//设置画笔的线帽
mPaint.setStrokeCap(Paint.Cap.BUTT); // 默认
//直线
mPaint.setStrokeJoin(Paint.Join.BEVEL); //直线
//路线
Path path = new Path();
//开始的位置
path.moveTo(100, 100);
// 线的末端,分别对应 x 和 y
path.lineTo(300, 100);
path.lineTo(100, 300);
//关闭这条线,如果线尾不等于第一点,则补上
path.close();
canvas.drawPath(path, mPaint);
//重置
mPaint.reset();
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);//描边 加 填充
mPaint.setStrokeWidth(50);
mPaint.setStrokeCap(Paint.Cap.BUTT);
mPaint.setStrokeJoin(Paint.Join.ROUND);//圆弧
Path path1 = new Path();
path1.moveTo(100,400);
path1.lineTo(300,400);
path1.lineTo(100,600);
path1.close();
canvas.drawPath(path1,mPaint);
//重置
mPaint.reset();
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(50);
mPaint.setStrokeCap(Paint.Cap.BUTT);
mPaint.setStrokeJoin(Paint.Join.MITER);//锐角
Path path2 = new Path();
path2.moveTo(100,700);
path2.lineTo(300,700);
path2.lineTo(100,900);
path2.close();
canvas.drawPath(path2,mPaint);
}
如上所示,第二个使用了 填充
绘制文本
//设置字符之间的间距
mPaint.setLetterSpacing(0.2f);
//设置文本 删除线
mPaint.setStrikeThruText(true);
//是否设置下划线
mPaint.setUnderlineText(true);
//设置文本大小
mPaint.setTextSize(50);
//设置字体类型
mPaint.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));// 常规
mPaint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));// 粗体
mPaint.setTypeface(Typeface.defaultFromStyle(Typeface.ITALIC));// 斜体
mPaint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD_ITALIC));// 粗斜体
// 使用 Paint.setTextSkewX() 来让文字倾斜
mPaint.setTextSkewX(0.5f);
//加载自定义字体
Typeface.create(familyName,style);
//文字倾斜
mPaint.setTextSkewX(-0.25f);//文字倾斜默认为0,官方推荐的 -0.25f 是斜体
//文本对齐方式
mPaint.setTextAlign(Paint.Align.LEFT); // 左对齐
mPaint.setTextAlign(Paint.Align.CENTER); //居中
mPaint.setTextAlign(Paint.Align.RIGHT); //右对齐
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
mPaint = new Paint();
mPaint.setColor(Color.BLUE);
mPaint.setStyle(Paint.Style.STROKE); //描边
mPaint.setStrokeCap(Paint.Cap.BUTT); //线帽:直线
mPaint.setStrokeJoin(Paint.Join.BEVEL); // 连接处:直线
// 使用 Paint.setFakeBoldText() 来加粗文字
mPaint.setFakeBoldText(true);
int baselineX = 100;
//设置文本大小
mPaint.setTextSize(50);
//设置字体类型
mPaint.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));// 常规
//文本对齐方式
mPaint.setTextAlign(Paint.Align.LEFT);
//文本
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
//获取 基线的坐标,这样文字会位于 自定义View 的中间
float baseLineY = getHeight() / 2 + ((fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom);
// 开始 的 x ,y
canvas.drawText("我是自定义文本", baselineX, baseLineY, mPaint);
}
辅助类方法:范围裁切 和 几何变换
范围裁切
1,clipRect()
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Bitmap bitmap= BitmapFactory.decodeResource(getResources(), R.drawable.maps);
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int left = (getWidth() - bitmap.getWidth()) / 2;
int top = (getHeight() - bitmap.getHeight()) / 2;
//保存状态
canvas.save();
//裁切 一个矩形
canvas.clipRect(left+50,top+50,left+300,top+200);
//设置图片的位置
canvas.drawBitmap(bitmap,left,top,paint);
//恢复绘制范围
canvas.restore();
}
原图:
裁切后:
2,clipPath()
其实 和clipRect 用法一样,只是把参数换成了 path ,所以能裁切的形状更多一些
Paint paint = new Paint();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.maps);
Point point1 = new Point(200, 200);
Point point2 = new Point(600, 200);
Path path1 = new Path();
Path path2 = new Path();
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//保存状态
canvas.save();
//添加一个圆
path1.addCircle(200+point1.x,200+point1.y,150, Path.Direction.CW);
//裁剪圆
canvas.clipPath(path1);
//中间两个参数为 图片距离左边和顶部的位置
canvas.drawBitmap(bitmap,point1.x,point1.y,paint);
canvas.restore();
canvas.save();
//画在圆外面
path2.setFillType(Path.FillType.INVERSE_WINDING);
path2.addCircle(200+point2.x,200+point2.y,150, Path.Direction.CW);
canvas.clipPath(path2);
canvas.drawBitmap(bitmap, point2.x, point2.y, paint);
canvas.restore();
}
原图同上,裁切后:
几何变换
几何变换大概分为三类
1,使用Canvas 来做常见的 二维变换
2,使用Matrix 来做常见和不常见的二维变换
3,使用Camera 来做 三维变换
1,二维变换
平移:
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Bitmap bitmap= BitmapFactory.decodeResource(getResources(), R.drawable.maps);
Point point1 = new Point(200, 200);
Point point2 = new Point(600, 200);
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bitmap, point1.x, point1.y, paint);
canvas.save();
//平移 dx 表示横向,dy 表示纵向
canvas.translate(200,50);
canvas.drawBitmap(bitmap, point2.x, point2.y, paint);
canvas.restore();
}
移动前:
移动后:
缩放:
canvas.save();
//缩放,前两个是缩放的倍数,后两个是缩放的轴心
canvas.scale(1,0.5f,(float)(point2.x+(bitmap.getWidth()/2)),(float)(point2.y+(bitmap.getHeight()/2)));
canvas.drawBitmap(bitmap, point2.x, point2.y, paint);
canvas.restore();
旋转:
canvas.save();
//第一个参数 为 旋转的度数,后面是 轴心
canvas.rotate(60,(float)(point2.x+(bitmap.getWidth()/2)),(float)(point2.y+(bitmap.getHeight()/2)));
canvas.drawBitmap(bitmap, point2.x, point2.y, paint);
canvas.restore();
错切:
canvas.save();
// 参数为 x 和 y 轴的错切系数
//通俗一点 x 就是左右旋转,y 就是上下旋转
canvas.skew(0,0.5f);
canvas.drawBitmap(bitmap, point1.x, point1.y, paint);
canvas.restore();
canvas.save();
canvas.skew(-0.5f,0);
canvas.drawBitmap(bitmap, point2.x, point2.y, paint);
canvas.restore();
2,使用Matrix 来做 变换
Matrix 常见的变换方式
1,创建 Matrix 对象
2,调用 Matrix 的pre/postTranslate/Rotate/Scale/Skew()方法来设置几何变换;
3,使用Canvas.setMatrix(matrix) 或 Canvas.concat(matrix) 把几何变换应用到 Canvas .
//移动
matrix.postTranslate(200,50);
//旋转
matrix.postRotate(60,(float)(point2.x+(bitmap.getWidth()/2)),(float)(point2.y+(bitmap.getHeight()/2)));
canvas.drawBitmap(bitmap, point1.x, point1.y, paint);
canvas.save();
canvas.concat(matrix);
canvas.drawBitmap(bitmap, point2.x, point2.y, paint);
canvas.restore();
把Matrix 应用到 Canvas 有两个方法:Canvas.setMatrix(matrix) 和 Canvas.concat(matrix)
Canvas.setMatrix(matrix)
:用Matrix
直接替换Canvas
当前的变换矩阵,即抛弃Canvas
当前的变换,改用Matrix
的变换(注:根据下面评论里以及我在微信公众号中收到的反馈,不同的系统中setMatrix(matrix)
的行为可能不一致,所以还是尽量用concat(matrix)
吧);Canvas.concat(matrix)
:用Canvas
当前的变换矩阵和Matrix
相乘,即基于Canvas
当前的变换,叠加上Matrix
中的变换。
3, 使用Camera 来做 三维变换
Camera 的三维变换有三类:旋转,平移,移动相机
1,三维旋转:
Camera.rotate*()
一共有四个方法: rotateX(deg)
rotateY(deg)
rotateZ(deg)
rotate(x, y, z)
。
Camera camera = new Camera();
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
camera.save();
//旋转Camera 的三维空间
camera.rotateX(30);
//将旋转投影到 Canvas
camera.applyToCanvas(canvas);
camera.restore();
canvas.drawBitmap(bitmap, point1.x, point1.y, paint);
canvas.restore();
canvas.save();
camera.save();
//旋转Camera 的三维空间
camera.rotateY(30);
//将旋转投影到 Canvas
camera.applyToCanvas(canvas);
canvas.drawBitmap(bitmap, point2.x, point2.y, paint);
canvas.restore();
camera.restore();
}
2,Camera.translate(float x, float y, float z) 移动
它的使用方式和 Camera.rotate*()
相同
4,Camera.setLocation(x, y, z) 设置虚拟相机的位置
注意,这个方法参数的单位不是像素,而是 inch ,尺寸
在 Camera
中,相机的默认位置是 (0, 0, -8)(英寸)。8 x 72 = 576,所以它的默认位置是 (0, 0, -576)(像素)。
如果绘制的内容过大,当它翻转起来的时候,就有可能出现图像投影过大的「糊脸」效果。而且由于换算单位被写死成了 72 像素,而不是和设备 dpi 相关的,所以在像素越大的手机上,这种「糊脸」效果会越明显。
而使用 `setLocation()` 方法来把相机往后移动,就可以修复这种问题。
camera.setLocation(0, 0, newZ);
旋转效果:
public class Practice13CameraRotateHittingFaceView extends View {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Bitmap bitmap;
Point point = new Point(200, 50);
Camera camera = new Camera();
Matrix matrix = new Matrix();
int degree;
ObjectAnimator animator = ObjectAnimator.ofInt(this, "degree", 0, 360);
public Practice13CameraRotateHittingFaceView(Context context) {
super(context);
}
public Practice13CameraRotateHittingFaceView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public Practice13CameraRotateHittingFaceView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
{
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.maps);
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth() * 2, bitmap.getHeight() * 2, true);
bitmap.recycle();
bitmap = scaledBitmap;
animator.setDuration(5000);
animator.setInterpolator(new LinearInterpolator());
animator.setRepeatCount(ValueAnimator.INFINITE);
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
float newZ = - displayMetrics.density * 6;
camera.setLocation(0, 0, newZ);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
animator.start();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
animator.end();
}
@SuppressWarnings("unused")
public void setDegree(int degree) {
this.degree = degree;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
int centerX = point.x + bitmapWidth / 2;
int centerY = point.y + bitmapHeight / 2;
camera.save();
matrix.reset();
camera.rotateZ(degree);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
canvas.save();
canvas.concat(matrix);
canvas.drawBitmap(bitmap, point.x, point.y, paint);
canvas.restore();
}
}
翻页效果:
public class Practice14FlipboardView extends View {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Bitmap bitmap;
Camera camera = new Camera();
int degree;
ObjectAnimator animator = ObjectAnimator.ofInt(this, "degree", 0, 180);
public Practice14FlipboardView(Context context) {
super(context);
}
public Practice14FlipboardView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public Practice14FlipboardView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
{
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.maps);
animator.setDuration(2500);
animator.setInterpolator(new LinearInterpolator());
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.REVERSE);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
animator.start();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
animator.end();
}
@SuppressWarnings("unused")
public void setDegree(int degree) {
this.degree = degree;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int x = centerX - bitmapWidth / 2;
int y = centerY - bitmapHeight / 2;
canvas.save();
canvas.clipRect(0,0,getWidth(),centerY);
canvas.drawBitmap(bitmap,x,y,paint);
canvas.restore();
canvas.save();
if (degree <90){
//下半部分
canvas.clipRect(0,centerY,getWidth(),getHeight());
}else {
canvas.clipRect(0,0,getWidth(),centerY);
}
camera.save();
camera.rotateX(degree);
canvas.translate(centerX, centerY);
camera.applyToCanvas(canvas);
canvas.translate(-centerX, -centerY);
camera.restore();
canvas.drawBitmap(bitmap, x, y, paint);
canvas.restore();
}
}
参考自:
https://www.jianshu.com/p/3aa9dc7d3320
https://www.jb51.net/article/128264.htm
https://hencoder.com/ui-1-1/
https://hencoder.com/ui-1-3/
练习地址:
https://github.com/LvKang345/PracticeDraw1
https://github.com/LvKang345/PracticeDraw2
https://github.com/LvKang345/PracticeDraw3
https://github.com/LvKang345/PracticeDraw4