Android Paint 之 PathEffect 详解

澳门新葡亰手机版 9

在之前讲 Android
Paint的使用详解的时候,其中有一个方法setPathEffect(PathEffect
effect)没有详细介绍,这篇就结合代码来介绍一下,在之前说过PathEffect共有6个子类ComposePathEffect,CornerPathEffect,DashPathEffect,DiscretePathEffect,PathDashPathEffect,SumPathEffect,这些类代码量都很少,这里先一个个介绍

要想实现这样的折线图表难吗?看完这一篇之后,自定义折线图全是小事。

CornerPathEffect将Path的线段之间的夹角变成圆角。构造函数,其中radius为圆角的半径

澳门新葡亰手机版 1
Android
Paint的使用使我们自定义View不可或缺的东西,其中有一个方法setPathEffect(PathEffect
effect)没有详细介绍,这篇就结合代码来介绍一下,在之前说过PathEffect共有6个子类ComposePathEffect,CornerPathEffect,DashPathEffect,DiscretePathEffect,PathDashPathEffect,SumPathEffect,PathEffect这个路径效果类没有具体的实现,效果是由它的六个子类实现的。

/** 
 * Transforms geometries that are drawn (either STROKE or FILL styles) by 
 * replacing any sharp angles between line segments into rounded angles of 
 * the specified radius. 
 * @param radius Amount to round sharp angles between line segments. 
 */  
public CornerPathEffect(float radius) {  
    native_instance = nativeCreate(radius);  
}

一、PathEffect()

这六个子类分别可以实现不同的路径效果:

澳门新葡亰手机版 2

上了效果图,我们来上一个代码:

看一下代码

二、代码

/**
 * Created by Shanlovana on 2017-03-26.
 */

public class PathView extends View {
    // 实例化画笔
    private Paint mPaint = null;
    private Path mPath;// 路径对象
    private Context mContext;


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

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

    public PathView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 初始化画笔
        initPaint();
        //初始化path
        initPath();


    }

    private void initPath() {
        // 实例化路径
        mPath = new Path();
        // 定义路径的起点
        mPath.moveTo(10, 50);

        // 定义路径的各个点
        for (int i = 0; i <= 20; i++) {
            mPath.lineTo(i * 20, (float) (Math.random() * 100));
        }
    }


    private void initPaint() {
        // 实例化画笔并打开抗锯齿
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
        mPaint.setColor(Color.DKGRAY);
    }

    /*
   * 绘制view时调用的方法,可能会出现多次调用,所以不建议在这里面实例化对象,也就是不要出现new
   *
   * @param canvas 一个画布对象,我们可以用paint在上面画画
   */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int phase = 1;

        /*
         * 绘制路径
         */
        // 没有做处理,还没有写代码
        mPaint.setPathEffect(null);//什么都不设置,没有做处理,显示生硬
        canvas.drawPath(mPath, mPaint);

        canvas.translate(0, 100);//下移100dp
        /*
         * 绘制路径
         */
        //
        mPaint.setPathEffect(new CornerPathEffect(10));
        canvas.drawPath(mPath, mPaint);
        canvas.translate(0, 100);//下移100dp
        /*
         * 绘制路径
         */
        //
        mPaint.setPathEffect(new DiscretePathEffect(3.0F, 5.0F));
        canvas.drawPath(mPath, mPaint);
        canvas.translate(0, 100);//下移100dp
        /*
         * 绘制路径
         */
        //
        mPaint.setPathEffect(new DiscretePathEffect(10.0F, 2.0F));
        canvas.drawPath(mPath, mPaint);
        canvas.translate(0, 100);//下移100dp
        /*
         * 绘制路径
         */
        //
        mPaint.setPathEffect(new DashPathEffect(new float[]{20, 10}, 1));
        canvas.drawPath(mPath, mPaint);

        //这个绘制需拉出去单独写,才会有动态效果,此处就是为了统一展示  ---start
        canvas.translate(0, 100);//下移100dp
        /*
         * 绘制路径
         */
        //
        mPaint.setPathEffect(new DashPathEffect(new float[]{20, 10, 50, 5, 100, 30, 10, 5}, phase));
        canvas.drawPath(mPath, mPaint);
        phase++;
        invalidate();
        //这个绘制需拉出去单独写,才会有动态效果,此处就是为了统一展示  ---end


        //2,这个绘制需拉出去单独写,才会有动态效果,此处就是为了统一展示  ---start
        canvas.translate(0, 100);//下移100dp
        /*
         * 绘制路径
         */
        //
        Path path = new Path();
        path.addCircle(0, 0, 3, Path.Direction.CCW);
        PathEffect pathEffect = new PathDashPathEffect(path, 12, phase, PathDashPathEffect.Style.ROTATE);

        mPaint.setPathEffect(pathEffect);
        canvas.drawPath(mPath, mPaint);
        // 改变偏移值
        phase++;
        // 重绘,产生动画效果
        invalidate();
        //2,这个绘制需拉出去单独写,才会有动态效果,此处就是为了统一展示  ---end


    }

}
public class PathEffectView extends View {  
    private Paint mPaint;  
    private int marging = 82;  
    private CornerPathEffect mCornerPathEffect[];  
    private Path mPath[];  

    public PathEffectView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        init();  
    }  

    private void init() {  
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
        mPaint.setColor(Color.BLACK);  
        mPaint.setStyle(Style.STROKE);  
        mPaint.setStrokeWidth(6);  
        mCornerPathEffect = new CornerPathEffect[8];  
        mPath = new Path[8];  
        for (int i = 0; i < mPath.length; i++) {  
            Path path = new Path();  
            path.moveTo(i * marging, marging);  
            path.lineTo(300 + i * marging, 180);  
            path.lineTo(400 + i * marging, 600);  
            path.lineTo(200 + i * marging, 1000);  
            path.lineTo(110 + i * marging, 1200);  
            mPath[i] = path;  
            mCornerPathEffect[i] = new CornerPathEffect(i * 10);  
        }  
    }  

    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
        canvas.drawColor(Color.WHITE);  
        for (int i = 0; i < mPath.length; i++) {  
            mPaint.setPathEffect(mCornerPathEffect[i]);  
            canvas.drawPath(mPath[i], mPaint);  
        }  
    }  
}

澳门新葡亰手机版,三、代码讲解

下面对照着代码,我们来讲解一下:

在使用的时候,通常是

PathEffect pe = new 一个具体的子类;

然后使用Paint的setPathEffect(PathEffect pe)方法即可。

运行结果为

1,不设置效果

 mPaint.setPathEffect(null);

澳门新葡亰手机版 3

2,设置CornerPathEffect

这个类的作用就是将Path的各个连接线段之间的夹角用一种更平滑的方式连接,类似于圆弧与切线的效果。
一般的,通过CornerPathEffect(float
radius)指定一个具体的圆弧半径来实例化一个CornerPathEffect。

DashPathEffect主要用于画虚线。构造函数,看注释,intervals必须大于大于2,phase是偏移量

3,设置DiscretePathEffect

DiscretePathEffect(离散路径效果)相对来说则稍微复杂点,其会在路径上绘制很多“杂点”的突出来模拟一种类似生锈铁丝的效果。其构造方法有两个参数:

  • 第一个呢指定这些突出的“杂点”的密度,值越小杂点越密集;
  • 第二个参数呢则是“杂点”突出的大小,值越大突出的距离越大反之反之。
/** 
 * The intervals array must contain an even number of entries (>=2), with 
 * the even indices specifying the "on" intervals, and the odd indices 
 * specifying the "off" intervals. phase is an offset into the intervals 
 * array (mod the sum of all of the intervals). The intervals array 
 * controls the length of the dashes. The paint's strokeWidth controls the 
 * thickness of the dashes. 
 * Note: this patheffect only affects drawing with the paint's style is set 
 * to STROKE or FILL_AND_STROKE. It is ignored if the drawing is done with 
 * style == FILL. 
 * @param intervals array of ON and OFF distances 
 * @param phase offset into the intervals array 
 */  
public DashPathEffect(float intervals[], float phase) {  
    if (intervals.length < 2) {  
        throw new ArrayIndexOutOfBoundsException();  
    }  
    native_instance = nativeCreate(intervals, phase);  
}

当我们设置杂点密度很大,突出距离较小时,你会发现线条也变得柔和了起来。

线条三与四的比较

看一下代码

4,DashPathEffect:

这个类的作用就是将Path的线段虚线化。
构造函数为DashPathEffect(float[] intervals, float
offset),其中intervals为虚线的ON和OFF数组,该数组的length必须大于等于2。

而DashPathEffect的第二个参数(phase)我称之为偏移值,动态改变其值会让路径产生动画的效果。

public class PathEffectView extends View {  
    private Paint mPaint;  
    private int marging = 82;  
    private DashPathEffect mCornerPathEffect[];  
    private Path mPath[];  

    public PathEffectView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        init();  
    }  

    private void init() {  
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
        mPaint.setColor(Color.BLACK);  
        mPaint.setStyle(Style.STROKE);  
        mPaint.setStrokeWidth(6);  
        mCornerPathEffect = new DashPathEffect[8];  
        mPath = new Path[8];  
        for (int i = 0; i < mPath.length; i++) {  
            Path path = new Path();  
            path.moveTo(i * marging, marging);  
            path.lineTo(300 + i * marging, 180);  
            path.lineTo(400 + i * marging, 600);  
            path.lineTo(200 + i * marging, 1000);  
            path.lineTo(110 + i * marging, 1200);  
            mPath[i] = path;  
            mCornerPathEffect[i] = new DashPathEffect(  
                    new float[] { 1, 2, 4, 8 }, 1);  
        }  
    }  

    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
        canvas.drawColor(Color.WHITE);  
        for (int i = 0; i < mPath.length; i++) {  
            mPaint.setPathEffect(mCornerPathEffect[i]);  
            canvas.drawPath(mPath[i], mPaint);  
        }  
    }  
}

5,设置PathDashPathEffect:

这个类的作用是使用Path图形来填充当前的路径,其构造函数为PathDashPathEffect
(Path shape, float advance, float phase,PathDashPathEffect.Stylestyle)。

shape则是指填充图形,advance指每个图形间的间距,phase为绘制时的偏移量,style为该类自由的枚举值,有三种情况:Style.ROTATE、Style.MORPH和
Style.TRANSLATE。

其中ROTATE的情况下,线段连接处的图形转换以旋转到与下一段移动方向相一致的角度进行转转,MORPH时图形会以发生拉伸或压缩等变形的情况与下一段相连接,TRANSLATE时,图形会以位置平移的方式与下一段相连接。

运行结果为

6,设置ComposePathEffect和SumPathEffect

ComposePathEffect和SumPathEffect都可以用来组合两种路径效果,就是把两种效果二合一。唯一不同的是组合的方式:

ComposePathEffect(PathEffect outerpe, PathEffect
innerpe)会先将路径变成innerpe的效果,再去复合outerpe的路径效果,即:outerpe(innerpe(Path));

SumPathEffect(PathEffect first, PathEffect
second)则会把两种路径效果加起来再作用于路径。

澳门新葡亰手机版 4

这里phase的偏移量是指偏移指定长度的位置开始画,但总长度还是不变,我们改一下再看看

mCornerPathEffect[i] = new DashPathEffect(new float[] { 10, 20, 40,  
        80 }, i * 10);

运行结果
澳门新葡亰手机版 5先画长度为10的实线,再画长度为20的虚线,接着画长度为40的实线,最后画长度为80的虚线,看一下起始位置,每次的最开始都不一样,因为每次偏移的都不一样,但总长度是不变的,因为上面的线只是左右平移,长度并没有减少,看到上面的线是越来越短,其实这是一种巧合,因为后面到虚线了,看不到了。通俗一点来说就是,线的开始位置和终止位置都没有改变,线就像一个无限长的绳,偏移量就相当于绳往下(后)拽的距离。我们打印看一下长度就知道了,修改一下

for (int i = 0; i < mPath.length; i++) {  
    mPaint.setPathEffect(mCornerPathEffect[i]);  
    canvas.drawPath(mPath[i], mPaint);  
    PathMeasure measure = new PathMeasure(mPath[i], false);  
    Log.d("wld_________", measure.getLength() + "");  
}

看一下log,长度都一样,没有变。

澳门新葡亰手机版 6

DiscretePathEffect切断线段,segmentLength是指定切断的长度,deviation为切断之后线段的偏移量,随机的,小于等于deviation。

/** 
 * Chop the path into lines of segmentLength, randomly deviating from the 
 * original path by deviation. 
 */  
public DiscretePathEffect(float segmentLength, float deviation) {  
    native_instance = nativeCreate(segmentLength, deviation);  
}

看一下代码

public class PathEffectView extends View {  
    private Paint mPaint;  
    private int marging = 82;  
    private DiscretePathEffect mPathEffect[];  
    private Path mPath[];  

    public PathEffectView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        init();  
    }  

    private void init() {  
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
        mPaint.setColor(Color.BLACK);  
        mPaint.setStyle(Style.STROKE);  
        mPaint.setStrokeWidth(6);  
        mPathEffect = new DiscretePathEffect[8];  
        mPath = new Path[8];  
        for (int i = 0; i < mPath.length; i++) {  
            Path path = new Path();  
            path.moveTo(i * marging, marging);  
            path.lineTo(300 + i * marging, 180);  
            path.lineTo(400 + i * marging, 600);  
            path.lineTo(200 + i * marging, 1000);  
            path.lineTo(110 + i * marging, 1200);  
            mPath[i] = path;  
            mPathEffect[i] = new DiscretePathEffect(10, 3 * i);  
        }  
    }  

    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
        canvas.drawColor(Color.WHITE);  
        for (int i = 0; i < mPath.length; i++) {  
            mPaint.setPathEffect(mPathEffect[i]);  
            canvas.drawPath(mPath[i], mPaint);  
        }  
    }  
}

运行结果

澳门新葡亰手机版 7

第一条线偏移量为0,所以看不出来有什么变化。下面再来修改一下,每隔长度为1就中断一次,3*i是偏移的最大长度,

mPathEffect[i] = new DiscretePathEffect(1, 3 * i);

澳门新葡亰手机版 8
感觉有点像磁铁一样,我们来测量一下他的长度

for (int i = 0; i < mPath.length; i++) {  
            mPaint.setPathEffect(mPathEffect[i]);  
            canvas.drawPath(mPath[i], mPaint);  
            PathMeasure measure = new PathMeasure(mPath[i], false);  
            Log.d("wld__________", measure.getLength() + "");  
        }

不可思议,每个长度都一样,还和之前测的一样,一点都没变。

澳门新葡亰手机版 9

PathDashPathEffect和DashPathEffect差不多,不同的是PathDashPathEffect可以通过自定义图形来绘制path,先看一下他的代码

public enum Style {  
    TRANSLATE(0),   //!< translate the shape to each position  
    ROTATE(1),      //!< rotate the shape about its center  
    MORPH(2);       //!< transform each point, and turn lines into curves  

    Style(int value) {  
        native_style = value;  
    }  
    int native_style;  
}  

/** 
 * Dash the drawn path by stamping it with the specified shape. This only 
 * applies to drawings when the paint's style is STROKE or STROKE_AND_FILL. 
 * If the paint's style is FILL, then this effect is ignored. The paint's 
 * strokeWidth does not affect the results. 
 * @param shape The path to stamp along 
 * @param advance spacing between each stamp of shape 
 * @param phase amount to offset before the first shape is stamped 
 * @param style how to transform the shape at each position as it is stamped 
 */  
public PathDashPathEffect(Path shape, float advance, float phase,  
                          Style style) {  
    native_instance = nativeCreate(shape.ni(), advance, phase,  
                                   style.native_style);  
}  

private static native long nativeCreate(long native_path, float advance,  
                                       float phase, int native_style);

shape是填充的图形,这个图形可以自己绘制,advance是图形之间的间距,phase是path的偏移量,其中有3种style,TRANSLATE是指图形以平移的方式填充path,ROTATE会根据path的旋转而旋转,MORPH和ROTATE差不多,不过有一点就是MORPH会在转角的连接处以平滑的方式连接,下面看一下代码

public class PathEffectView extends View {  
    private Paint mPaint;  
    private int marging = 82;  
    private PathEffect mPathEffect1;  
    private PathEffect mPathEffect2;  
    private PathEffect mPathEffect3;  
    private Path mPath;  

    public PathEffectView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        init();  
    }  

    private void init() {  
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
        mPaint.setStyle(Style.STROKE);  
        mPaint.setStrokeWidth(6);  
        mPaint.setColor(Color.RED);  
        mPath = new Path();  
        mPath.moveTo(0, marging);  
        mPath.lineTo(300, 180);  
        mPath.lineTo(400, 600);  
        mPath.lineTo(200, 1000);  
        mPath.lineTo(800, 1200);  
        Path p = new Path();  
        p.addRect(0, 0, 64, 12, Path.Direction.CCW);  
        mPathEffect1 = new PathDashPathEffect(p, 128, 0,  
                android.graphics.PathDashPathEffect.Style.MORPH);  
        mPathEffect2 = new PathDashPathEffect(p, 128, 0,  
                android.graphics.PathDashPathEffect.Style.ROTATE);  
        mPathEffect3 = new PathDashPathEffect(p, 128, 0,  
                android.graphics.PathDashPathEffect.Style.TRANSLATE);  
    }  

    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
        canvas.drawColor(Color.WHITE);  
        mPaint.setPathEffect(mPathEffect1);  
        canvas.drawPath(mPath, mPaint);  
        canvas.translate(200, 0);  
        mPaint.setPathEffect(mPathEffect2);  
        canvas.drawPath(mPath, mPaint);  
        canvas.translate(200, 0);  
        mPaint.setPathEffect(mPathEffect3);  
        canvas.drawPath(mPath, mPaint);  
    }  
}

看一下运行效果,