Android Paint,Path,Canvas

1. Paint

Paint(画笔),保存了绘制几何图形文本位图样式颜色信息

关键词:coloralphastrokesolid线条圆角效果拐角风格xfermode渲染器TileMode

1. 线性渲染
2. 环形渲染
3. 扫描渲染
4. 位图渲染
5. 组合渲染


2. 图层混合模式

1. 离屏绘制
2. 刮刮卡效果实现


3. 滤镜

1. 颜色矩阵(胶片效果等)
2. 饱和度
3. 亮度值


2. Canvas

Canvas(画布),通过画笔绘制几何图形文本路径位图

1. drawLine,drawBitmap,drawPath,drawText (绘制几何图形,文本,位图等)
2. scale(缩放),rotate,translate(平移) (位置形状变换)
3. clipPath
4. Matrix
5. Canvas调用了translate,scale等变换后,后续的操作都是基于变换后的Canvas,都会受到影响;所有我们采用canvas.save(),restore等方法来保存画布各个阶段状态,避免混乱
6. 离子爆炸效果


3. PathPathMeasure

Path(路径),可用于绘制直线,曲线构成的几何路径,还可根据路径绘制文字;常用API:移动,连线,闭合,添加图形等

一阶贝塞尔曲线(法):数据点和控制点rLineToRect类QQ消息数拉拽效果

 

public class PkLeftView extends View {

    private static final String TAG = "GradientLayout";
    private int mW, mH; // 控件,宽高
    Path stampPath = new Path();
    public int startX1, startY1;
    public int startX2, startY2;
    public int startX3, startY3;
    public int startX4, startY4;
    public RectF arcRect;
    private Shader mShader;
    private Bitmap mBitmap;

    private Paint mPaint;
    private Paint mPaintLine;

    private int lineH = PixelDpUtils.dp2px(1); // 画线宽度1/2

    public int xDown = 40; // 折角宽度 X

    public PkLeftView(Context context) {
        this(context, null);
    }

    public PkLeftView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PkLeftView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaintLine = new Paint(); //初始化
        mPaintLine.setColor(Color.BLACK);// 设置颜色
        mPaintLine.setStyle(Paint.Style.STROKE); //描边效果
        mPaintLine.setAntiAlias(true); // 抗锯齿
        mPaintLine.setStyle(Paint.Style.STROKE); //描边效果
        mPaintLine.setStrokeWidth(lineH * 2);//描边宽度

        mPaint = new Paint(); //初始化
        mPaint.setAntiAlias(true); // 抗锯齿
        mPaint.setStyle(Paint.Style.FILL); //描边效果
        mPaint.setStrokeWidth(lineH * 2);//描边宽度
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mW = getMeasuredWidth();
        mH = getMeasuredHeight();
        Log.i(TAG, "mW: " + mW);
        Log.i(TAG, "mW: " + mH);
        xDown = mW / 10;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.i(TAG, "left: " + left);
        Log.i(TAG, "top: " + top);
        Log.i(TAG, "right: " + right);
        Log.i(TAG, "bottom: " + bottom);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mShader = new LinearGradient(0, 0, 500, 500, new int[]{Color.RED, Color.BLUE, Color.GREEN}, new float[]{0.f, 0.7f, 1}, Shader.TileMode.REPEAT);
        mPaint.setShader(mShader);

        // 1. 首先移动到起始点
        startX1 = mH / 2;
        startY1 = lineH;
        stampPath.moveTo(startX1, startY1);

        // 2. 画水平线
        startX2 = mW - lineH;
        startY2 = lineH;

        stampPath.lineTo(startX2, startY2);

        // 3. 画倒角
        startX3 = startX2 - xDown;
        startY3 = mH - lineH;
        stampPath.lineTo(startX3, startY3);

        // 4. 画下边
        startX4 = mH / 2;
        startY4 = startY3;
        stampPath.lineTo(startX4, startY4);

//        // 5. 画圆弧
        if (arcRect == null) {
            arcRect = new RectF(lineH, lineH, mH - lineH, mH - lineH);
        }
        stampPath.addArc(arcRect, 90, 180);

        // 6. 将Path利用Paint绘制出来
        canvas.drawPath(stampPath, mPaint);

        canvas.save();

        // 7.
        canvas.drawPath(stampPath, mPaintLine);

    }

}
public class GradientLayout extends View {

    private Paint mPaint;
    private Shader mShader;
    private Bitmap mBitmap;

    public GradientLayout(Context context) {
        this(context, null);
    }

    public GradientLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public GradientLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {

        mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.beauty);

        mPaint = new Paint(); //初始化
//        mPaint.setColor(Color.RED);// 设置颜色
//        mPaint.setARGB(255, 255, 255, 0); // 设置 Paint对象颜色,范围为0~255
//        mPaint.setAlpha(200); // 设置alpha不透明度,范围为0~255
        mPaint.setAntiAlias(true); // 抗锯齿
        mPaint.setStyle(Paint.Style.FILL); //描边效果
//        mPaint.setStrokeWidth(4);//描边宽度
//        mPaint.setStrokeCap(Paint.Cap.ROUND); //圆角效果
//        mPaint.setStrokeJoin(Paint.Join.MITER);//拐角风格
//        mPaint.setShader(new SweepGradient(200, 200, Color.BLUE, Color.RED)); //设置环形渲染器
//        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DARKEN)); //设置图层混合模式
//        mPaint.setColorFilter(new LightingColorFilter(0x00ffff, 0x000000)); //设置颜色过滤器
//        mPaint.setFilterBitmap(true); //设置双线性过滤
//        mPaint.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.NORMAL));//设置画笔遮罩滤镜 ,传入度数和样式
//        mPaint.setTextScaleX(2);// 设置文本缩放倍数
//        mPaint.setTextSize(38);// 设置字体大小
//        mPaint.setTextAlign(Paint.Align.LEFT);//对其方式
//        mPaint.setUnderlineText(true);// 设置下划线
//
//        String str = "Android高级工程师";
//        Rect rect = new Rect();
//        mPaint.getTextBounds(str, 0, str.length(), rect); //测量文本大小,将文本大小信息存放在rect中
//        mPaint.measureText(str); //获取文本的宽
//        mPaint.getFontMetrics(); //获取字体度量对象

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
       /**
        * 1.线性渲染,LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int colors[], @Nullable float positions[], @NonNull TileMode tile)
        * (x0,y0):渐变起始点坐标
        * (x1,y1):渐变结束点坐标
        * color0:渐变开始点颜色,16进制的颜色表示,必须要带有透明度
        * color1:渐变结束颜色
        * colors:渐变数组
        * positions:位置数组,position的取值范围[0,1],作用是指定某个位置的颜色值,如果传null,渐变就线性变化。
        * tile:用于指定控件区域大于指定的渐变区域时,空白区域的颜色填充方法
        */
//        mShader = new LinearGradient(0, 0, 500, 500, new int[]{Color.RED, Color.BLUE, Color.GREEN}, new float[]{0.f,0.7f,1}, Shader.TileMode.REPEAT);
//        mPaint.setShader(mShader);
//        canvas.drawCircle(250, 250, 250, mPaint);
//        canvas.drawRect(0,0,1000,1000, mPaint);

       /**
        * 环形渲染,RadialGradient(float centerX, float centerY, float radius, @ColorInt int colors[], @Nullable float stops[], TileMode tileMode)
        * centerX ,centerY:shader的中心坐标,开始渐变的坐标
        * radius:渐变的半径
        * centerColor,edgeColor:中心点渐变颜色,边界的渐变颜色
        * colors:渐变颜色数组
        * stoops:渐变位置数组,类似扫描渐变的positions数组,取值[0,1],中心点为0,半径到达位置为1.0f
        * tileMode:shader未覆盖以外的填充模式。
        */
//        mShader = new RadialGradient(250, 250, 250, new int[]{Color.GREEN, Color.YELLOW, Color.RED}, null, Shader.TileMode.CLAMP);
//        mPaint.setShader(mShader);
//        canvas.drawCircle(250, 250, 250, mPaint);

        /**
        * 扫描渲染,SweepGradient(float cx, float cy, @ColorInt int color0,int color1)
        * cx,cy 渐变中心坐标
        * color0,color1:渐变开始结束颜色
        * colors,positions:类似LinearGradient,用于多颜色渐变,positions为null时,根据颜色线性渐变
        */
//        mShader = new SweepGradient(250, 250, Color.RED, Color.GREEN);
//        mPaint.setShader(mShader);
//        canvas.drawCircle(250, 250, 250, mPaint);

        /**
        * 位图渲染,BitmapShader(@NonNull Bitmap bitmap, @NonNull TileMode tileX, @NonNull TileMode tileY)
        * Bitmap:构造shader使用的bitmap
        * tileX:X轴方向的TileMode
        * tileY:Y轴方向的TileMode
              REPEAT, 绘制区域超过渲染区域的部分,重复排版
              CLAMP, 绘制区域超过渲染区域的部分,会以最后一个像素拉伸排版
              MIRROR, 绘制区域超过渲染区域的部分,镜像翻转排版
        */
//        mShader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, Shader.TileMode.MIRROR);
//        mPaint.setShader(mShader);
//        canvas.drawRect(0,0,500, 500, mPaint);
//        canvas.drawCircle(250, 250, 250, mPaint);

        /**
         * 组合渲染,
         * ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB, Xfermode mode)
         * ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB, PorterDuff.Mode mode)
         * shaderA,shaderB:要混合的两种shader
         * Xfermode mode: 组合两种shader颜色的模式
         * PorterDuff.Mode mode: 组合两种shader颜色的模式
         */
        BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
        LinearGradient linearGradient = new LinearGradient(0, 0, 1000, 1600, new int[]{Color.RED, Color.GREEN, Color.BLUE}, null, Shader.TileMode.CLAMP);
        mShader = new ComposeShader(bitmapShader, linearGradient, PorterDuff.Mode.MULTIPLY);
        mPaint.setShader(mShader);
        canvas.drawCircle(250, 250, 250, mPaint);
    }
}
1. 划线两端圆角效果
mPaint.setStrokeCap(Paint.Cap.ROUND); // 圆角效果

 


https://www.codercto.com/a/43619.html

Paint:

一、setAntiAlias()

一般用于绘制不规则图形的时候,使用抗锯齿,比如圆形、文字等。对于规则的图形,是不需要打开抗锯齿功能的,比如矩形。

Android的Paint、Canvas和Path基本使用总结

二、setStyle()

设置填充的样式,主要用于控制几何图形

  • Paint.Style.FILL 填充内部
  • Paint.Style.FILL_AND_STROKE 填充内部和描边
  • Paint.Style.STROKE 仅描边Android的Paint、Canvas和Path基本使用总结

三、setStrokeWidth()

设置画笔的宽度,单位是px.对画笔的STYLE设置成STROKE和FILL_AND_STROKE时有效。但画支线时无论style设置什么值,均有效。

Android的Paint、Canvas和Path基本使用总结

注意事项:

当设置较大stroke画圆,并且绘画的范围占满(基本占满)画布或rect时,stroke会超出绘画范围。此时需要将原来的圆半径减去stroke一半得到的长度作为新圆的半径。stroke超出的长度其实为stroke宽度的一半。

Android的Paint、Canvas和Path基本使用总结Android的Paint、Canvas和Path基本使用总结

对于椭圆也差不多:

Android的Paint、Canvas和Path基本使用总结Android的Paint、Canvas和Path基本使用总结

四、Paint.Cap

Cap指定了描边线和路径的开始和结束的处理。默认为BUTT。

  • Paint.Cap.BUTT 无线帽
  • Paint.Cap.ROUND 圆形线帽
  • Paint.Cap.SQUARE 方形线帽Android的Paint、Canvas和Path基本使用总结

五、Paint.Join

Join指定线条和曲线段在描边路径上连接的处理。默认为MITER。

  • Paint.Join.BEVEL 连接的外边缘以直线相交
  • Paint.Join.MITER 连接的外边缘以锐角相交
  • Paint.Join.ROUND 连接的外边缘以圆弧相交

Android的Paint、Canvas和Path基本使用总结

当画笔style设置为STROKE或者FILL_AND_STROKE时,绘画图形或绘画路径,会根据画笔的Join值对线与线之间的结合处进行处理。

Canvas:

一、画背景

canvas提供3种方法可以用于绘制画布的背景

void drawColor(int color)
void drawARGB(int a,int r,int g,int b)
void drawRGB(int r,int g,int b)
复制代码

二、画直线

  • startX: 起始点X坐标
  • startY: 起始点Y坐标
  • stopX: 终点X坐标
  • stopY: 终点Y坐标
void drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
复制代码

三、画矩形

矩形的范围可以使用两个矩形 工具 类进行设置:Rect 和 RectF,两者的主要区别就是Rect存储的上下左右均为Int类型,而RectF存储的上下左右均为Float类型。

Android的Paint、Canvas和Path基本使用总结

画圆角矩形:

  • rect 矩形的范围
  • rx 生成圆角的x轴半径
  • ry 生成圆角的y轴半径Android的Paint、Canvas和Path基本使用总结但drawRoundRect()方法只能生成四个圆角都是一致的矩形:Android的Paint、Canvas和Path基本使用总结如果想生成圆角不一致的圆角,可使用shape标签:Android的Paint、Canvas和Path基本使用总结

四、画弧

  • RectF 圆弧绘画的矩形范围。
  • startAngle 开始的角度。
  • sweepAngle 整条弧跨越的弧度。
  • useCenter 如果为true,则在弧形中包含椭圆的中心并闭合它。Android的Paint、Canvas和Path基本使用总结画笔不同的style和drawArc()useCenter的值可绘画出不同的圆弧或弧线:Android的Paint、Canvas和Path基本使用总结

注意事项:

canvas画弧时,0度的起始位置在x轴正方向上。

Android的Paint、Canvas和Path基本使用总结

五、画文字

  • text 绘画的文本
  • start 第一个字符的索引
  • end 文本的最后一个字符的索引
  • x 文本绘画的起始x轴位置
  • y 基线(BaseLine)

Android的Paint、Canvas和Path基本使用总结

前面几个参数都很好理解,无非就是文本、索引值和绘画起始的x轴位置,但基线是什么,怎么求呢? Baseline是文字绘制时所参照的基准线,观察下图可看见基线是大部分英文字母的底部沿线。只有确定Baseline的位置,才能将文字准确的绘制在我们想要的位置上。

Android的Paint、Canvas和Path基本使用总结

需要先理清楚一些概念:

1、基线并不是中心线

2、FontMetricsInt.top表示BaseLine到顶部的距离

3、FontMetricsInt.bottom表示BaseLine到底部的距离

4、dy表示BaseLine到中心线的距离

5、BaseLine = 中心线 + dy

6、dy = 中心线 - FontMetricsInt.bottom。因为FontMetricsInt.bottom表示BaseLine到底部的距离。

7、FontMetricsInt.top是一个负值,FontMetricsInt.bottom是一个正数。所以求改文字的中心线时,等于(FontMetricsInt.bottom - FontMetricsInt.top)/ 2

Android的Paint、Canvas和Path基本使用总结

中心线是按文字的所在的位置决定的。以联系人中的字母索引列表为例,每个字母的所需要绘画的高都不一样,需要计算每个字母可占据的高度,再除以一半求出中心线的位置。

Android的Paint、Canvas和Path基本使用总结

六、裁剪

剪切是对canvas的裁剪操作,并不影响原来的之前已经画好的图形。

以clipRect()裁剪变色字体为例:

Android的Paint、Canvas和Path基本使用总结

七、save()和restore()

每次调用save(),都会先保存当前画布的状态,然后将其放入到特定的栈中。

每次调用restore(),都会把栈顶的画布取出来,并按照这个状态恢复当前的画布,然后在这个画布上作画。

clipXX系列的函数对画布的操作是不可逆的,除非调用了save()和restore()对画布进行保存和恢复。

Path:

path代表路径,在canvas中使用drawPath(Path,Paint)进行绘画路径。

Android的Paint、Canvas和Path基本使用总结

一、画直线路径

void moveTo(float x1,float y1)
void lineTo(float x2,float y2)
void close()
复制代码

(x1,y1)是直线的起始点,(x2,y2)是直线的终点,又是下一个绘制直线路径的起始点,lineTo可以无限用。

调用了moveTo()后,调用lineTo()画了N条直线,还没有形成闭环的话,可以调用close()将路径的首尾连接起来。

Android的Paint、Canvas和Path基本使用总结

二、画弧形路径

Android的Paint、Canvas和Path基本使用总结

使用3参的arcTo()方法画出的弧会和起始点相连,因为默认情况下路径都是连贯的,除非以下两种情况:

1、调用addXX系列函数,直接添加固定形状的路径

2、moveTo()改变起始位置

调用4参的arcTo()方法,将第4个参数forceMoveTo设置为true,会使弧的起始点作为绘制的起始点。

Android的Paint、Canvas和Path基本使用总结Android的Paint、Canvas和Path基本使用总结

三、addXXX系列函数

路径默认是连贯的,但addXXX()系列函数可以直接添加一些固定形状的路径,不必考虑连贯性。

1、添加矩形路径

Android的Paint、Canvas和Path基本使用总结

Path.Direction 用于指定如何封闭的形状(如矩形、椭圆形)。

  • Path.Direction.CCW: 指创建逆时针方向的矩形路径。
  • Path.Direction.CW: 指创建顺时针方向的矩形路径。

Android的Paint、Canvas和Path基本使用总结

目前顺时针和逆时针的生成对矩形图形并无影响,但对于根据路径方向绘画文字等,则会起到关键作用。

2、添加圆角矩形路径

Android的Paint、Canvas和Path基本使用总结

绘画圆角矩形路径对比绘画圆角矩形多了两个重载方法,其中float[] radii参数时用于定义4个角的椭圆各自的x轴半径和y轴半径,换句话说,即使用addRoundRect()也可以实现4个圆角不同的矩形。但圆角的顺序并不因为Path.Direction值的不同而有所改变。

Android的Paint、Canvas和Path基本使用总结

3、添加圆形路径

Android的Paint、Canvas和Path基本使用总结

具体参数和canvas绘制圆一样, x 为圆心X轴坐标, y 为圆心y轴坐标,radius 为圆的半径。

4、添加椭圆路径

Android的Paint、Canvas和Path基本使用总结

具体参数和canvas绘制椭圆一样

5、添加圆弧路径

Android的Paint、Canvas和Path基本使用总结

具体参数和canvas绘制圆弧一样

一般使用方案二进行对文本宽高的测量,最精准。



本文来源:码农网
本文链接:https://www.codercto.com/a/43619.html

 


 

refs:
https://blog.csdn.net/zmm911zmm/article/details/89784920
https://www.codercto.com/a/43619.html
https://juejin.cn/post/6844903487570968584 | HenCoder Android 开发进阶: 自定义 View 1-2 Paint 详解 - 掘金
https://juejin.cn/post/6844903488460177416 | HenCoder Android 开发进阶:自定义 View 1-3 文字的绘制 - 掘金
https://juejin.cn/post/6844903489789755406 | HenCoder Android 开发进阶:自定义 View 1-4 Canvas 对绘制的辅助 - 掘金
https://juejin.cn/post/6844903491031269383 | HenCoder Android 自定义 View 1-5: 绘制顺序 - 掘金
https://juejin.cn/post/6844903494256689165 | HenCoder Android 自定义 View 1-6: 属性动画(上手篇) - 掘金
https://juejin.cn/post/6844903494940360711 | 【HenCoder Android 开发进阶】自定义 View 1-7:属性动画(进阶篇) - 掘金
https://juejin.cn/post/6844903496064434183 | # HenCoder Android 自定义 View 1-8 硬件加速 - 掘金
https://rengwuxian.com/ui-2-1/ | HenCoder Android 自定义 View 2-1 布局基础
https://rengwuxian.com/ui-2-2/ | HenCoder Android 自定义 View 2-2 全新定义 View 的尺寸
https://rengwuxian.com/ui-2-3/ | HenCoder Android 自定义 View 2-3 定制 Layout 的内部布局
https://rengwuxian.com/ui-3-1/ | HenCoder 自定义 View 3-1 触摸反馈,以及 HenCoder Plus


posted @ 2023-07-11 11:09  petercao  阅读(87)  评论(0编辑  收藏  举报