自定义View 绘制 ,范围裁切,几何变换

自定义 绘制的方式是重写 绘制方法。其中最常用的是 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)

  1. Canvas.setMatrix(matrix):用 Matrix 直接替换 Canvas 当前的变换矩阵,即抛弃 Canvas 当前的变换,改用 Matrix 的变换(注:根据下面评论里以及我在微信公众号中收到的反馈,不同的系统中 setMatrix(matrix) 的行为可能不一致,所以还是尽量用 concat(matrix) 吧);
  2. 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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tʀᴜsᴛ³⁴⁵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值