Android 自定义View学习(七)——Canvas知识学习

上篇的学习,Paint中的基础知识基本结束,笔学完了,学习开始学习画布。本篇记录学习Canvas中的一些知识。在drawBitmap()方法中会引出计划下篇要学习的内容MatrixdrawPath()方法会引出计划在下下篇学习的贝塞尔曲线,学习的重点也是这两个方法

学习资料:

本人很菜,有错误,请指出

虽然称Canvas为画布,但并不是直接在Canvas画,Canvas内部默认会创建一个Biatmap,也可以通过构造方法或者setBitmap()方法传入一个,像素所有的信息是画在了这个Bitmap上,然后Bitmap被保存在了Canvas之内

这是我看爱哥的博客后,自己做的一个总结,若有错误,请指出。针对Canvas的两种构造方法,爱哥针对源码有做分析,可以看看了解一下

Canvas的方法中,clipdraw方法占据了一大半,在Android 自定义View学习(二)——开始了解Canvas和Paint了解过了几个draw方法,本篇进行补充学习

1.draw方法补充学习

draw一系列方法中,有一个特殊的牛B的存在,drawBitmapMesh(),这个方法牛B在可以几乎对Bitmap做任何操作。虽然这个方法很强大,但使用的频率并不算高,也有点鸡肋。一些比较简单的Bitmap处理可能优先考虑Matrix,而过于复杂的处理,会耗时比较久,效率可能并不高。一般优先不考虑这个方法,遇到Matrix实现不了的需求,记得有这么一个方法可以学习,然后使用

1.1 drawBitmap方法

感觉这个方法使用频率很高。一共有6个重载方法,其中两个参数最多的方法已经废弃,也就是需要学习4个

1.1.1 第1个方法

drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint)
  • bitmap 要画的目标Bitmap
  • left 左上角的X轴坐标
  • top 左上角的Y轴坐标
  • paint 画笔

简单使用

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    final Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    final float x = getWidth() / 2 - bitmap.getWidth() / 2;//水平居中
    final float y = 0;
    canvas.drawBitmap(bitmap, x, y, mPaint);
}

《Android 自定义View学习(七)——Canvas知识学习》 drawBitmap第1个方法

在实际开发中,还会考虑图片宽高的压缩,显示位置,以及padding的等等

1.1.2 第2个和第3个方法

drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst,@Nullable Paint paint)

drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst,@Nullable Paint paint)

两个方法的差别在于第三个参数RectRectF

  • src 用来截取Bitmap局部所想要显示的像素块区域,通过构造方法中的四个坐标系点确定范围。这个参数可以为null,为null就是整个Bitmap都作为目标资源显示
  • dst 用来显示的区域,在控件中绘制Bitmap的区域,可以实现拉伸或者缩放

简单使用

private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.test);
    rect = new Rect(0,0,1080,600);
    rectF = new RectF(0f,0f,1080f,600f);
}

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (bitmap != null)
        canvas.drawBitmap(bitmap,null,rectF,mPaint);
}

《Android 自定义View学习(七)——Canvas知识学习》 drawBitmap第2个方法

1080是测试手机的屏幕宽度

第2个参数rect = new Rect(x,y,x1,y1) ,构造方法中四个参数分别为:

  • x, X轴开始的坐标点,默认为0
  • y, Y轴开始的坐标点
  • x1, X轴结束的坐标点,若x1-x大于了Bitmap的宽度,截取的有效区域就是Bitmap的宽度
  • y1, Y轴结束的坐标点

第3个参数rectF = new RectF(x,y,x1,y1),构造方法中四个参数分别为:

  • x,开始绘制的X轴的坐标点,默认为0
  • y,开始绘制的Y轴的坐标点
  • x1,结束绘制的X轴的坐标点,若x1-x大于srcx1-x,就是拉伸;小于就是缩放
  • y1,结束绘制的Y轴的坐标点

简单修改代码:

rect = new Rect(0,0,400,600);
rectF = new RectF(100f,0f,700f,600f);

《Android 自定义View学习(七)——Canvas知识学习》 拉伸显示局部

此时在控件X100f位置开始绘制资源Bitmap(0,0)到(400,600)局部区域,最终的显示效果就成了拉伸局部的效果。Y轴同理

这两个参数的作用,用几个数简单测试一下,比较直观。

1.1.3 第4个方法

drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint)

简单使用

private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    matrix = new Matrix();
    matrix.setTranslate(100f,100f);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawBitmap(bitmap,matrix,mPaint);
}

《Android 自定义View学习(七)——Canvas知识学习》 drawBitmap第4个方法

Matrix是一个3 * 3的矩阵,下篇进行记录学习

1.2 画Line方法

画一条线的方法

drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint)

画多条线的方法

drawLines(@Size(multiple=4) @NonNull float[] pts, @NonNull Paint paint)

drawLines(@Size(multiple=4) @NonNull float[] pts, int offset, int count,@NonNull Paint paint)

1.2.1 drawLine 画一条线

drawLine()方法就一个,没有重载方法。画规则曲线可以考虑使用Path

private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setColor(Color.parseColor("#FF4081"));
    mPaint.setStrokeWidth(10f);
}

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawLine(100f,100f,600f,500f,mPaint);
}

《Android 自定义View学习(七)——Canvas知识学习》 画直线

方法参数也很简单,四个参数确定两个点的坐标,然后两点一线

1.2.2 drawLines画多条直线

两个参数的drawsLines(float[] pts, Paint paint)方法简单使用

private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setColor(Color.parseColor("#FF4081"));
    mPaint.setStrokeWidth(10f);
    floats = new float[]{100f,100f,300f,300f,400f,200f,600f,200f};//每4个数一组,确定两个点的坐标
}

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawLines(floats,mPaint);
}

《Android 自定义View学习(七)——Canvas知识学习》 画多条直线

方法源码中有这样一个@Size(multiple=4)注解,要求floats中每4个值看做一组,每组来确定一条直线的两个端点,不足4个的部分是无效的

两个参数的方法内部还是调用了四个参数的方法,在源码中

public void drawLines(@Size(multiple=4) @NonNull float[] pts, @NonNull Paint paint) {
    drawLines(pts, 0, pts.length, paint);
}

drawLines(float[] pts, int offset, int count,Paint paint)中,

  • offset 表示跳过floats中几个值
  • count 表示跳过offset后,数组的长度

简单修改代码

canvas.drawLines(floats,4,floats.length-4,mPaint);

就只会画出(400f,200f),(600f,200f)确定的那条水平的短线

1.3 画Potion方法

同1.2画Line方法一样,画点的方法也是有两种,画一个点和画多个点

画一个点很简单,直接看画多个点

drawPoints(@Size(multiple=2) float[] pts, int offset, int count,@NonNull Paint paint) 

根据方法内的注解得知,pts的大小要大于2,并且每两个一组,多余的无效

简单使用:

private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setColor(Color.parseColor("#FF4081"));
    mPaint.setStrokeWidth(10f);
    floats = new float[]{100f,100f,300f,200f,200f};
}

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawPoints(floats,mPaint);
}

《Android 自定义View学习(七)——Canvas知识学习》 绘制两个点

floats中,有5个值,但实际有效的也就是前4个

1.3 drawPath 方法

Android 自定义View学习(三)——Paint绘制文字属性中学习了解了setPathEffect(PathEffect effect)方法在绘制路径时的效果,drawPath()方法往往都会配合PahtEffect来使用

 drawPath(@NonNull Path path, @NonNull Paint paint)

方法只有一个,简单使用

private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setColor(Color.parseColor("#FF4081"));
    mPaint.setStyle(Paint.Style.STROKE);//设置风格为空心
    mPaint.setStrokeWidth(10f);

    path = new Path();
    path.moveTo(540f,50f);
    path.lineTo(740f,300f);
    path.lineTo(340f,300f);
    path.close();//形成闭合 将(340,300)和(540,50)连接起来
}
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawPath(path, mPaint);
}

《Android 自定义View学习(七)——Canvas知识学习》 绘制等腰三角形

绘制出一个等腰三角形

主要就是用到Path这个类,这个类有很多方法除了可以绘制直线,还可以绘制曲线

1.3.1 Path类

The Path class encapsulates compound (multiple contour) geometric paths consisting of straight line segments, quadratic curves, and cubic curves.
It can be drawn with canvas.drawPath(path, paint), either filled or stroked (based on the paint’s Style), or it can be used for clipping or to draw text on a path.

这个类封装了一些可以借助一元直线方程,二远方程曲线,立方曲线的方法来绘制一些较为复杂的组合形式的集合图形,绘制出来的风格则是根据画笔设置的style来决定,也可以剪切或者绘制一段文字在路径上

除了已经用到的moveTo()lineTo()方法,Path中还有很多add开头的方法。有两个重点方法是quadTo()cubicTo()方法

1.3.1 quadTo和cubicTo方法

quadTo()可以用来绘制二阶贝塞尔曲线,也就是3个点确定一个曲线

private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setColor(Color.parseColor("#FF4081"));
    mPaint.setStyle(Paint.Style.STROKE);//设置风格为空心
    mPaint.setStrokeWidth(10f);

    path = new Path();
    path.moveTo(100f,100f);

    floats = new float[]{200f,200f,900f,100f};
    path.quadTo(200f,200f,900f,100f);
}

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawPath(path, mPaint);
    canvas.drawPoints(floats,mPaint);
}

《Android 自定义View学习(七)——Canvas知识学习》 二阶贝塞尔曲线

图中的曲线,是由(100,100),(900,100),(200,200)三个点来确定。

cubicTo方法使用和quadTo用法类似,只是多了一组参数,多了一个点而已

 path.cubicTo(200f,200f,400f,100f,900f,200f);

测试效果和quadTo很容易就分区

依稀记得高三的数学考试最后一道大题往往就是要求绘制会一个点的运动轨迹,大部分都是一段曲线或者一个椭圆之类的,和这里有些类似。

这两方法重要的是理解其中的原理,贝尔塞尔曲线等到Matrix学习结束后再进行学习

1.3.2 addTo方法

先来看简单用法

private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setColor(Color.parseColor("#FF4081"));
    mPaint.setStyle(Paint.Style.STROKE);//设置风格为空心
    mPaint.setStrokeWidth(10f);

    path = new Path();
    path.moveTo(100f,100f);
    rectF = new RectF(100, 100, 400, 400);
    path.arcTo(rectF,0,90);
}
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawPath(path, mPaint);
}

《Android 自定义View学习(七)——Canvas知识学习》 addTo方法

这个方法就是画出一段弧线后,将弧线的起始点和moveTo确定的path的起始点进行连接起来。弧线截取圆的一部分。圆的直径为300,经过很简单分析,圆心在(250,250)的点

1.3.3 rLineTo方法

r就是relative,相对的缩写

简单使用

private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setColor(Color.parseColor("#FF4081"));
    mPaint.setStyle(Paint.Style.STROKE);//设置风格为空心
    mPaint.setStrokeWidth(10f);

    path = new Path();
    path.moveTo(100f,100f);
    path.rLineTo(300,200f);
 }
 
 protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawPath(path, mPaint);
    canvas.drawPoint(300,200,mPaint);
}

《Android 自定义View学习(七)——Canvas知识学习》 rLineTo方法

rLineTo方法确定的坐标是相对于moveTo来说的,实际最终的画出的直线的结束点的坐标为(400,300)

1.3.4 addArc方法

简单使用

private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setColor(Color.parseColor("#FF4081"));
    mPaint.setStyle(Paint.Style.STROKE);//设置风格为空心
    mPaint.setStrokeWidth(10f);

    path = new Path();
    path.moveTo(100f,100f);
    path.lineTo(300,200f);
    //添加弧形
    rectF = new RectF(100, 100, 400, 400);
    path.addArc(rectF,0,90);
}

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawPath(path, mPaint);
        canvas.drawPoint(300,200,mPaint);
}

《Android 自定义View学习(七)——Canvas知识学习》 addArc方法

addArc方法和addTo方法却别是,addArc是添加一段弧形,并不将绘制的图形连接起来

1.4 canvas.drawTextOnPath 方法

在绘制的路径上,绘制文字

private void init() {
    //路径画笔
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setColor(Color.parseColor("#FF4081"));
    mPaint.setStyle(Paint.Style.STROKE);//设置风格为空心
    mPaint.setStrokeWidth(10f);
    //文字画笔
    textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    textPaint.setTextSize(60f);

    //路径
    path = new Path();
    rectF = new RectF(100, 100, 300, 400);
    path.addOval(rectF, Path.Direction.CW);

    chars = new char[]{'a','b','c','d','e'};
}

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //绘制路径
    canvas.drawPath(path, mPaint);
    //绘制路径上的文字
    canvas.drawTextOnPath(chars,0,chars.length,path,0f,chars.length,textPaint);
}

《Android 自定义View学习(七)——Canvas知识学习》 在路径上绘制文字

addOval方法中,可以改变路径闭合的方向,简单修改代码

path.addOval(rectF, Path.Direction.CCW);

《Android 自定义View学习(七)——Canvas知识学习》 Path.Direction.CCW

文字在路径内侧绘制,并且逆时针

draw系列方法中,有一个看起来很有意思的方法,drawTextRun方法,但这个方法最低要求的23

canvas.drawTextRun(chars,0,chars.length,0,chars.length,100f,100f,false,textPaint);

没有23的真机,就用了虚拟机,看方法名字,以为会按照一定的方法,文字进行滚动,可并没有,设置为falseabcde,设置为trueedcba,不清楚在真机上啥效果

加上在开始了解Canvas中的方法,draw大致就学习到这里

2.Clip方法学习

Clip开头的方法主要有两个:

  • clipPath() 利用Path的方法,可以裁切出一块不规则区域画布
  • clipRect() 可以裁切出一块矩形画布

还有一个已经废弃的clipRegion(),废弃就不学了,直接学替代的方法

2.1 clipRect裁切规则区域画布

简单使用:

private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setColor(Color.parseColor("#FF4081"));
    mPaint.setStyle(Paint.Style.FILL);
    //矩形
    rectF = new RectF(0, 0, 400, 400);
}

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //绘制底色 黄
    canvas.drawColor(Color.YELLOW);
    //截取画布
    canvas.clipRect(rectF);
    //截取后的画布底色
    canvas.drawColor(Color.CYAN);
    //验证有效区域
    canvas.drawRect(300,300,600,600,mPaint);
}

《Android 自定义View学习(七)——Canvas知识学习》 drawRect方法

clipRect(rectF),就是在在画布裁出以(0,0),(0,400),(400,0),(400,400)四个点确定的矩形。之后Canvas有效的区域便就是裁出的矩形区域,再次进行绘制时,超出这个区域便无法绘制,但裁切并不会影响c裁切前已经绘制好的区域,clip裁切针对的是Canvas

cliprRect有这样一个cliprRect((@NonNull RectF rect, @NonNull Region.Op op)重载方法

2.1.1 Region.Op

OpRegion类中的一个枚举,有6个值

直接用代码演示效果

private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setColor(Color.WHITE);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(5f);
    //裁切区域1
    rectF1 = new RectF(100, 100, 300, 300);
    //裁切区域2
    rectF2 = new RectF(200, 200, 400, 400);
}

 protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //绘制底色 蓝
    canvas.drawColor(Color.BLUE);
    canvas.save();
    //截取画布1
    canvas.clipRect(rectF1);
    //截取画布2
    canvas.clipRect(rectF2,Region.Op.DIFFERENCE);
    //截取后的有效区域画布底色
    canvas.drawColor(Color.RED);
    canvas.restore();
    //绘制辅助区域
    canvas.drawRect(rectF1, mPaint);
    canvas.drawRect(rectF2, mPaint);
}

红色区域就代表两次裁切后的有效区域

  • Region.Op.DIFFERENCE

《Android 自定义View学习(七)——Canvas知识学习》 Region.Op.DIFFERENCE第一次的非交集

取第一次裁切的非交集部分

  • Region.Op.INTERSECT

《Android 自定义View学习(七)——Canvas知识学习》 Region.Op.INTERSECT交集

取两次的交集

  • Region.Op.REPLACE

《Android 自定义View学习(七)——Canvas知识学习》 Region.Op.REPLACE第二次替代第一次裁切

第二次替代第一次裁切

  • Region.Op.UNION

《Android 自定义View学习(七)——Canvas知识学习》 Region.Op.UNION两次裁切的和

两次裁切的和

  • Region.Op.REVERSE_DIFFERENCE

《Android 自定义View学习(七)——Canvas知识学习》 Region.Op.REVERSE_DIFFERENCE取第2次的非交集

DIFFERENCE相反,取的第2次裁切的非交集区域

  • Region.Op.XOR

《Android 自定义View学习(七)——Canvas知识学习》 Region.Op.XOR两次的非交集

异或,取两次交集外的区域

有点类似PorterDuffXfermode图像处理的效果

2.2 CilpPath 裁切不规则画布

裁切出一个圆形区域的画布

private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setColor(Color.WHITE);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(5f);

    path = new Path();
    path.addCircle(300,300,100, Path.Direction.CCW);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //绘制底色 蓝
    canvas.drawColor(Color.BLUE);
    //裁切画布
    canvas.clipPath(path);
    //绘制裁切后的区域底色
    canvas.drawColor(Color.parseColor("#FF4081"));
    //绘制辅助圆形
    canvas.drawCircle(300,300,100,mPaint);
}

《Android 自定义View学习(七)——Canvas知识学习》 裁切圆形的画布

clip的方法基本就学到这里

3.其他方法

画布除了裁切外,还有可以进行旋转

3.1 rotate 旋转方法

private void init() {
        rectP1 = new Paint(Paint.ANTI_ALIAS_FLAG);
        rectP1.setColor(Color.BLUE);

        rectP2 = new Paint(Paint.ANTI_ALIAS_FLAG);
        rectP2.setColor(Color.parseColor("#FF4081"));

        rectF1 = new RectF(100,100,400,400);
        rectF2 = new RectF(200,200,300,300);
}

/**
 *  旋转画布
 */
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //旋转30°
    canvas.rotate(30);
    canvas.drawRect(rectF1,rectP1);
    canvas.drawRect(rectF2,rectP2);
}

《Android 自定义View学习(七)——Canvas知识学习》 旋转30度

整个画布进行了顺时针旋转30°,参数为正时,是顺时针旋转,负数为逆时针旋转

如果只想让小的红色的矩形进行旋转,而蓝色的大矩形不旋转,需要了解画布中的

3.2 save和restore方法

简单修改代码,加入saverestore两个方法

private void init() {
    rectP1 = new Paint(Paint.ANTI_ALIAS_FLAG);
    rectP1.setColor(Color.BLUE);

    rectP2 = new Paint(Paint.ANTI_ALIAS_FLAG);
    rectP2.setColor(Color.parseColor("#FF4081"));

    rectF1 = new RectF(100,100,400,400);
    rectF2 = new RectF(200,200,300,300);

}

/**
 *  旋转画布
 */
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawRect(rectF1,rectP1);
    canvas.save();
    //旋转30°
    canvas.rotate(30);
    canvas.drawRect(rectF2,rectP2);
    canvas.restore();
}

《Android 自定义View学习(七)——Canvas知识学习》 旋转小矩形

save()就是保存当前图层
restore() 就是把图层恢复到最近一次save()方法前的状态

关于保存图层,还有一个更加强大的saveLayer(),这个方法就等用到时,再进行学习

3.3 translate 平移画布

这个方法使用很简单

@Override
protected void onDraw(Canvas canvas) {    
    super.onDraw(canvas);   
    //平移 
    canvas.translate(800,200);  
    //绘制矩形  
    canvas.drawRect(rectF1,rectP1);   
    canvas.drawRect(rectF2,rectP2); 
    //在(100,100)处绘制一个小圆,用来辅助观察坐标系的改变
    canvas.drawCircle(100,100,30,rectP2);
}

《Android 自定义View学习(七)——Canvas知识学习》 平移

这个方法需要注意的是,Canvas的坐标系就进行了改变,观察小圆的位置

3.4 scale 缩放方法

缩放的使用也非常简单

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.scale(0.5F, 1.0F);
    canvas.drawBitmap(bitmap,0,0,null);
}

《Android 自定义View学习(七)——Canvas知识学习》 缩放

缩放有效值为0~1f,1表示不进行缩放,原始大小

缩放方法有个重载方法scale(float sx, float sy, float px, float py)

简单修改代码

canvas.scale(0.5F, 1.0F,540,0);

《Android 自定义View学习(七)——Canvas知识学习》 指定缩放中心

px,py确定缩放中心,canvas.scale(0.5F, 1.0F)默认为(0,0)为缩放中心,指定(540,0)为缩放中心时,屏幕宽度为1080Canvas就在水平居中缩放

3.5 skew 错切

简单使用

 protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.skew(0.5f,0);
    canvas.drawBitmap(bitmap,0,0,null);
}

《Android 自定义View学习(七)——Canvas知识学习》 错切

这几个方法都只是简单的调用,看了看效果,以后用到就再深入了解学习

4. 最后

Canvas的基本知识也就学习这些,遗漏的遇到再学习

篇幅有点长,但并不难理解,基本都是调用一下就可以比较直观看出效果的方法

下篇学习Matrix

月饼节到了,中秋快乐 : )

    原文作者:英勇青铜5
    原文地址: https://www.jianshu.com/p/82ad29db52f3
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞