美文网首页Android进阶Android自定义Viewandroid图片处理
Android高级进阶——绘图篇(四)Paint常用函数介绍

Android高级进阶——绘图篇(四)Paint常用函数介绍

作者: aKaiC | 来源:发表于2018-04-22 19:12 被阅读5次

    开篇:

    经过前几篇,我们基本把paint和canvas的基本用法就看完了,今天我们来个大汇总,列举一个paint的所有函数,然后一个一个的过。经过这几篇,你将能学会paint中所有处理函数的用法。

    一、基本用法

    • 1、概述
      我们先来看一下paint中基本设置的函数都有哪些:
    • reset() :重置画笔
    • setColor(int color) :给画笔设置颜色值
    • setARGB(int a, int r, int g, int b) :同样是设置颜色,但是利用ARGB分开设置
    • setAlpha(int a) :设置画笔透明度
    • setStyle(Paint.Style style) :设置画笔样式,取值有 Paint.Style.FILL :填充内部、Paint.Style.FILL_AND_STROKE :填充内部和描边、Paint.Style.STROKE :仅描边
    • setStrokeWidth(float width) :设置画笔宽度
    • setAntiAlias(boolean aa) :设置画笔是否抗锯齿

    上面这些函数,我们在前面几篇已经详细讲过了,难度也都不大,不再细讲。下面几个函数我们是没有讲到过的,下面做下补充

    • setStrokeCap(Paint.Cap cap) :设置线冒样式,取值有Cap.ROUND(圆形线冒)、Cap.SQUARE(方形线冒)、Paint.Cap.BUTT(无线冒)
    • setStrokeJoin(Paint.Join join) :设置线段连接处样式,取值有:Join.MITER(结合处为锐角)、Join.Round(结合处为圆弧)、Join.BEVEL(结合处为直线)
    • setStrokeMiter(float miter) :设置笔画的倾斜度,90度拿画笔与30拿画笔,画出来的线条样式肯定是不一样的吧。(事实证明,根本看不出来什么区别好吗……囧……)
    • setPathEffect(PathEffect effect) :设置路径样式;取值类型是所有派生自PathEffect的子类:ComposePathEffect, CornerPathEffect, DashPathEffect, DiscretePathEffect, PathDashPathEffect, SumPathEffect

    这四个函数中,setStrokeMiter(float miter)就不再讲了,我做过试验,没什么变化,也就是没啥屌用……,我们分别来看看另外三个函数的具体用法。

    • 2、setStrokeCap(Paint.Cap cap)
      设置线帽样式,取值有Cap.ROUND(圆形线帽)、Cap.SQUARE(方形线帽)、Paint.Cap.BUTT(无线帽)
      我先不讲什么叫做线冒,大家先来看看下面这段代码以及它的效果:
        private void init() {
            //初始化画笔
            paint = new Paint();
            paint.setColor(Color.GREEN);
            paint.setStyle(Paint.Style.FILL_AND_STROKE);
            paint.setStrokeWidth(50);
            path = new Path();
        }
    
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            paint.setStrokeCap(Paint.Cap.BUTT);
            canvas.drawLine(100, 100, 500, 100, paint);
            paint.setStrokeCap(Paint.Cap.SQUARE);
            canvas.drawLine(100, 300, 500, 300, paint);
            paint.setStrokeCap(Paint.Cap.ROUND);
            canvas.drawLine(100, 500, 500, 500, paint);
    
            paint.setStrokeWidth(3);
            paint.setColor(Color.RED);
            canvas.drawLine(100, 0, 100, 1000, paint);
    
        }
    

    在这里,我们水平画了三条线,他们的线冒类型分别是Cap.BUTT(无线帽)、Cap.SQUARE(方形线帽)、Cap.ROUND(圆形线冒)
    最后,垂直画出x=100的那条起始线:

    image.png

    从效果图中可以明显看出,从无线冒多出来的那块区域就是线帽!就相当于给原来的直线加上一个帽子一样,所以叫线帽

    • 3、setStrokeJoin(Paint.Join join)

    参数取值有:

    • Join.MITER(结合处为锐角)
    • Join.Round(结合处为圆弧)
    • Join.BEVEL(结合处为直线)

    网上说,他们三个的区别如下:

    image.png

    但我运行出来的效果却不是如此,Join.Round和 Join.BEVEL没有明显的区别:
    我们画出来三个锐角的path,分别给这三段Path设置不同的连接方式:

    Jietu20180422-170426.jpg
    • 4、setPathEffect(PathEffect effect)
      设置路径样式;取值类型是所有派生自PathEffect的子类
    image.png

    我们一个个来看他们的效果:

    • (1)、CornerPathEffect——圆形拐角效果
      它的作用就是将原来Path生硬的直线拐角,变成圆形拐角:
    image.png

    上面的那条是Path默认的直线拐角,下面的那条是圆形拐角。
    其构造函数为:

    public CornerPathEffect(float radius)

    它只有一个参数radius:即当前连接两条直线所使用的圆的半径。

    image.png

    上面这个图,很清晰的展示了利用半径R=50的圆来代替原来两条直线间的夹角。
    我们利用代码,再来看看具体效果:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        path.moveTo(100, 700);
        path.lineTo(400, 100);
        path.lineTo(700, 900);
    
        paint.setColor(Color.RED);
        canvas.drawPath(path, paint);
    
        paint.setColor(Color.BLUE);
        paint.setPathEffect(new CornerPathEffect(100));
        canvas.drawPath(path, paint);
    
        paint.setColor(Color.YELLOW);
        paint.setPathEffect(new CornerPathEffect(200));
        canvas.drawPath(path, paint);
    }
    

    在这里,我利用Path构造了一个夹角,在同一个位置画了三遍,第一遍是没有添加任何PathEffect的;第二遍,CornerPathEffect的圆半径为100;第三遍CornerPathEffect的圆半径为200;

    image.png

    很明显能看出在半径不同情况下,连接位置也是不一样的

    • (2)、DashPathEffect——虚线效果
      这个功能能够实现虚线段的效果:
    image.png

    它的函数声名如下:

    public DashPathEffect(float intervals[], float phase)

    其中:
    intervals[]:表示组成虚线的各个线段的长度;整条虚线就是由intervals[]中这些基本线段循环组成的。比如,我们定义new float[] {20,10};那这个虚线段就是由两段线段组成的,第一个可见的线段长为20,每二个线段不可见,长度为10;

    image.png

    对于intervals[]数组的有两个限定:

    • 长度必须大于等于2;因为必须有一个实线段和一个空线段来组成虚线。
    • 个数必须为偶数,如果是基数,最后一个数字将被忽略;这个很好理解,因为一组虚线的组成必然是一个实线和一个空线成对组成的。

    我们再做一个假设,如果我们定义intervals[]为new float[] {20,10,100,100};那么这条虚线将是由四条子线段循环组成的,第一条实线长度为20,第二个空线长度为10,第三个实线长为100,第四条空线长充为100;
    phase:开始绘制的偏移值
    我们来看看代码的运行效果来验证我们想的是否正确:

    效果图如下:

    image.png

    从这个效果图中可以看到两点:
    第一:蓝线段的基本组成部分,分别长度为20,10,100,100实线段和空线段组成的
    第二:最下方的红线段位移了15,从开始处就可以明显看出效果。原来20的线段,只剩5,所以看起来就像一个点一样。大家也可以发挥想象,利用动画设置偏移量让这条虚线段动起来:

    效果图如下:


    Jietu20180422-174024.gif

    代码实现:

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            path.reset();
            paint.reset();
            init();
            path.moveTo(100, 700);
            path.lineTo(400, 100);
            path.lineTo(700, 900);
    
            paint.setColor(Color.RED);
            canvas.drawPath(path, paint);
    
            //使用DashPathEffect画线段
            paint.setColor(Color.BLUE);
            paint.setPathEffect(new DashPathEffect(new float[]{20, 10, 100, 100}, 0));
            canvas.translate(0, 100);
            canvas.drawPath(path, paint);
    
            //画同一条线段,偏移值为15
            paint.setColor(Color.RED);
            paint.setPathEffect(new DashPathEffect(new float[]{20, 10, 100, 100}, dx));
            canvas.translate(0, 100);
            canvas.drawPath(path, paint);
        }
    
        public void startAnim() {
            ValueAnimator animator = ValueAnimator.ofInt(0, 20 + 10 + 100 + 100);
            animator.setDuration(2000);
            animator.setRepeatCount(ValueAnimator.INFINITE);
            animator.setInterpolator(new LinearInterpolator());
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    dx = (int) animation.getAnimatedValue();
                    invalidate();
                }
            });
            animator.start();
        }
    
    • (3)、DiscretePathEffect——离散路径效果


      image.png

    同样,图中第一条线是原生的,第二条线加上离散路径效果后的样式。
    DiscretePathEffect就是将原来路径分隔成定长的线段,然后将每条线段随机偏移一段位置,我们可以用它来模拟一种类似生锈铁丝的效果;
    它的构造函数如下:

    public DiscretePathEffect(float segmentLength, float deviation)

    第一个参数segmentLength:表示将原来的路径切成多长的线段。如果值为2,那么这个路径就会被切成一段段由长度为2的小线段。所以这个值越小,所切成的小线段越多;这个值越大,所切成的小线段越少。
    第二参数deviation:表示被切成的每个小线段的可偏移距离。值越大,就表示每个线段的可偏移距离就越大,就显得越凌乱,值越小,每个线段的可偏移原位置的距离就越小。
    我们看下代码效果

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            path = getPath();
            //第一条原生Path
            canvas.drawPath(path, paint);
    
            //第二条Path
            canvas.translate(0, 200);
            paint.setPathEffect(new DiscretePathEffect(2, 5));
            canvas.drawPath(path, paint);
            //第三条Path
            canvas.translate(0, 200);
            paint.setPathEffect(new DiscretePathEffect(6, 5));
            canvas.drawPath(path, paint);
            //第四条Path
            canvas.translate(0, 200);
            paint.setPathEffect(new DiscretePathEffect(6, 15));
            canvas.drawPath(path, paint);
    
        }
    
        private Path getPath() {
            Path path = new Path();
            // 定义路径的起点
            path.moveTo(0, 0);
    
            // 定义路径的各个点
            for (int i = 0; i <= 40; i++) {
                path.lineTo(i * 35, (float) (Math.random() * 150));
            }
            return path;
        }
    

    效果图:


    image.png

    从第二条和第三条相比,可以明显看出,在仅增大segmentLength的情况下,很明显第三条线段所切的子线段要大一些,所以就没有第二条曲线的那么多线段相交所产生的折点,所以相比第二条更顺滑一些,当然铁锈效果就没有第二条那么明显了。
    第三条和第四条相比,在segmentLength都是6的情况下,在第四条仅增大了deviation参数(偏移距离),从效果图中也明显可以看出每个子线段向外偏移的距离也增大了。
    从效果图中很明显可以看出各个参数的作用,这里就不多讲了

    • (4)、PathDashPathEffect——印章路径效果
      这个名字是我自己取的……,因为这个功能就相当于Photoshop中的印章功能。
      它的作用就是用另一个路径图案做为印章,沿着指定路径一个个盖上去。
      我们先来看看PathDashPathEffect的声明:

    public PathDashPathEffect(Path shape, float advance, float phase,Style style)

    其中:

    • Path shape:表示印章路径,比如我们下面示例中的三角形加右上角一个点;
    • float advance:表示两个印章路径间的距离,很容易理解,印章间距离越大,间距就越大。
    • float phase:路径绘制偏移距离,与上面DashPathEffect中的float phase参数意义相同
    • Style style:表示在遇到转角时,如何操作印章以使转角平滑过渡,取值有:Style.ROTATE、Style.MORPH、Style.TRANSLATE;Style.ROTATE表示通过旋转印章来过渡转角;Style.MORPH表示通过变形印章来过渡转角;Style.TRANSLATE表示通过位移来过渡转角。这三个效果的具体意义,上面会通过具体示例来分别讲解。

    然后我们看看如何来实现下面这个效果:


    image.png

    如上图所示,印章路径就是一个三角形加右上角一个点。然后就拿这个印章路径在原来的路径上每隔一定距离盖章。
    上图的对应代码为:

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            //画出原始路径
            Path path = new Path();
            path.moveTo(100, 600);
            path.lineTo(400, 100);
            path.lineTo(700, 900);
            canvas.drawPath(path, paint);
    
            //构建印章路径
            Path stampPath = new Path();
            stampPath.moveTo(0, 20);
            stampPath.lineTo(10, 0);
            stampPath.lineTo(20, 20);
            stampPath.close();
            stampPath.addCircle(0, 0, 3, Path.Direction.CCW);
    
            //使用印章路径效果
            canvas.translate(0, 200);
            paint.setPathEffect(new PathDashPathEffect(stampPath, 35, 0, PathDashPathEffect.Style.TRANSLATE));
            canvas.drawPath(path, paint);
    
        }
    

    在示例中我们有两个路径,大家一定要把路径和印章路径分清楚,一个路径是path,这个是原始路径;另一个路径是stampPath,这个是印章路径。印章路径是用来构造画笔的(paint),而原始的路径就是用这个画笔来做画的。
    下面我们就来看看,在Style不同的情况下,在转角处都如何处理的:

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            drawPathDashPathEffect(canvas);
    
        }
    
        private Path getPath() {
            Path path = new Path();
            // 定义路径的起点
            path.moveTo(0, 0);
    
            // 定义路径的各个点
            for (int i = 0; i <= 40; i++) {
                path.lineTo(i * 35, (float) (Math.random() * 150));
            }
            return path;
        }
    
        private void drawPathDashPathEffect(Canvas canvas) {
            Path path = getPath();
            canvas.drawPath(path, paint);
    
            canvas.translate(0, 200);
            paint.setPathEffect(new PathDashPathEffect(getStampPath(), 35, 0, PathDashPathEffect.Style.MORPH));
            canvas.drawPath(path, paint);
    
            canvas.translate(0, 200);
            paint.setPathEffect(new PathDashPathEffect(getStampPath(), 35, 0, PathDashPathEffect.Style.ROTATE));
            canvas.drawPath(path, paint);
    
            canvas.translate(0, 200);
            paint.setPathEffect(new PathDashPathEffect(getStampPath(), 35, 0, PathDashPathEffect.Style.TRANSLATE));
            canvas.drawPath(path, paint);
        }
    
        private Path getStampPath() {
            Path path = new Path();
            path.moveTo(0, 20);
            path.lineTo(10, 0);
            path.lineTo(20, 20);
            path.close();
    
            path.addCircle(0, 0, 3, Path.Direction.CCW);
            return path;
        }
    

    这段代码通过getPath()函数随机生成一条路径,并将原始路径和各个Style的路径画出来。第一条是原始路径,第二条的Style是Style.MORPH,第三条是Style.ROTATE,第四条是Style.TRANSLATE;

    image.png

    大家第一眼估计就看得眼花缭乱了,仔细看一下蓝线处的三角形的样式。

    image.png

    同样是转角,如上图,当Style.MORPH时,就是通过对印章进行变形来过渡转角的

    image.png

    当Style为Style.ROTATE时就是靠旋转印章角度来过渡转角的

    image.png

    当Style为Style.TRANSLATE时,即不会对印章变形也不会对旋转印章角度,而只是通过变改印章的位置来过渡
    其中的一个参数float phase,表示路径绘制偏移距离我们还没讲,这个与上面的偏移距离的意义一样,通过改变偏移距离同样可以实现动画

    • (5)、ComposePathEffect与SumPathEffect

    这两个都是用来合并两个特效的。但它们之间是有区别的:

    public ComposePathEffect(PathEffect outerpe, PathEffect innerpe)

    ComposePathEffect合并两个特效是有先后顺序的,它会先将第二个参数的PathEffect innerpe的特效作用于路径上,然后再在此加了特效的路径上作用第二个特效。

    public SumPathEffect(PathEffect first, PathEffect second)

    而SumPathEffect是分别对原始路径分别作用第一个特效和第二个特效。然后再将这两条路径合并,做为最终结果。
    我们来看看效果:

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //画原始路径
            Path path = getPath();
            canvas.drawPath(path, paint);
    
            //仅应用圆角特效的路径
            canvas.translate(0, 200);
            CornerPathEffect cornerPathEffect = new CornerPathEffect(100);
            paint.setPathEffect(cornerPathEffect);
            canvas.drawPath(path, paint);
    
            //仅应用虚线特效的路径
            canvas.translate(0, 200);
            DashPathEffect dashPathEffect = new DashPathEffect(new float[]{2, 5, 10, 10}, 0);
            paint.setPathEffect(dashPathEffect);
            canvas.drawPath(path, paint);
    
            //利用ComposePathEffect先应用圆角特效,再应用虚线特效
            canvas.translate(0, 200);
            ComposePathEffect composePathEffect = new ComposePathEffect(dashPathEffect, cornerPathEffect);
            paint.setPathEffect(composePathEffect);
            canvas.drawPath(path, paint);
    
            //利用SumPathEffect,分别将圆角特效应用于原始路径,然后将生成的两条特效路径合并
            canvas.translate(0, 200);
            paint.setStyle(Paint.Style.STROKE);
            SumPathEffect sumPathEffect = new SumPathEffect(cornerPathEffect, dashPathEffect);
            paint.setPathEffect(sumPathEffect);
            canvas.drawPath(path, paint);
        }
    

    代码很简单,就是画几条特效路径 ,效果图如下:

    image.png

    特别注意第 4 条路径和 第 5 条路径:
    第 4 条路径的生成方法为:

    ComposePathEffect(dashPathEffect,cornerPathEffect);

    表示先将圆角特效应用于原始路径,得到第 2 条路径,然后再在第 2 条路径的基础上应用虚线特效得到最终的效果 第 4 条路径;

    而第 5 条路径的生成方法则比较弱智:

    SumPathEffect(cornerPathEffect,dashPathEffect);

    它的原理是,先将圆角特效应用于原始路径得到 第 2 条路径,然后将虚线特效应依然应用于原始路径,得到 第 3 条路径.然后将 第 2 条路径和 第 3 条路径合并(即画在一起……囧),就得到 第 5 条路径
    好了,到这里,所有的路径效果都讲完了。

    二、字体相关

    • setTextSize(float textSize) :设置文字大小
    • setFakeBoldText(boolean fakeBoldText) :设置是否为粗体文字
    • setStrikeThruText(boolean strikeThruText) :设置带有删除线效果
    • setUnderlineText(boolean underlineText) :设置下划线
    • setTextAlign(Paint.Align align) :设置开始绘图点位置
    • setTextScaleX(float scaleX) :水平拉伸设置
    • setTextSkewX(float skewX) :设置字体水平倾斜度,普通斜体字是-0.25,可见往右斜
    • setTypeface(Typeface typeface) :字体样式

    其他函数:

    • setLinearText(boolean linearText)

    设置是否打开线性文本标识;由于文本想要快速绘制出来,必然是需要提前缓存在显存中的,一般而言每个文字需要一个字节的大小来存储它(当然具体需要多少字节与编码方式有关),那如果是长篇文章,可见所需的大小可想而知。我们可以通过setLinearText (true)告诉Android我们不需要这样的文本缓存。但如果我们不用文本缓存,虽然能够省去一些内存空间,但这是以显示速度为代价的。
    由于这个是API 1的函数,由于当时的android手机的内存大小还是很小的,所以尽量减少内存使用是每个应用的头等大事,在当时的的环境下这个函数还是很有用的。
    但在今天,内存动不动就是4G以上了,文本缓存的所占的那点内存就微不足道了,没有哪个APP会牺牲性能来减少这点这内存占用了,所以这个函数基本没用了。

    相关文章

      网友评论

        本文标题:Android高级进阶——绘图篇(四)Paint常用函数介绍

        本文链接:https://www.haomeiwen.com/subject/itsclftx.html