Android 高级UI5 画笔Paint的基本用法

Android 高级UI 目录

1.setStyle(Paint.Style style)

设置画笔样式,取值有
Paint.Style.FILL :填充内部
Paint.Style.FILL_AND_STROKE :填充内部和描边
Paint.Style.STROKE :仅描边

代码实例:

public class PaintViewBasic extends View {
    private Paint mPaint;

    public PaintViewBasic(Context context) {
        super(context);
        mPaint = new Paint();
    }

    public PaintViewBasic(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
    }

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

    private void drawStyle( Canvas canvas ) {

        mPaint.setColor(Color.RED);//设置画笔的颜色
        mPaint.setTextSize(60);//设置文字大小
        mPaint.setStrokeWidth(5);//设置画笔的宽度
        mPaint.setAntiAlias(true);//设置抗锯齿功能 true表示抗锯齿 false则表示不需要这功能

        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(200,200,160,mPaint);

        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(200,600,160,mPaint);

        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        canvas.drawCircle(200,1000,160,mPaint);

    }
    
}
Paint.Style

2.setStrokeCap(Paint.Cap cap)

设置线冒样式,取值有
Paint.Cap.BUTT(无线冒)
Paint.Cap.ROUND(圆形线冒)
Paint.Cap.SQUARE(方形线冒)
注意:冒多出来的那块区域就是线帽!就相当于给原来的直线加上一个帽子一样,所以叫线帽

    private void drawStrokeCap(Canvas canvas) {
        Paint paint = new Paint();

        paint.setAntiAlias(true);
        paint.setStrokeWidth(200);
        paint.setColor(Color.parseColor("#00ff00"));
        paint.setStrokeCap(Paint.Cap.BUTT);       // 线帽,即画的线条两端是否带有圆角,butt,无圆角
        canvas.drawLine(200, 200, 500, 200, paint);

        paint.setColor(Color.parseColor("#ff0000"));
        paint.setStrokeCap(Paint.Cap.ROUND);       // 线帽,即画的线条两端是否带有圆角,ROUND,圆角
        canvas.drawLine(200, 500, 500, 500, paint);

        paint.setColor(Color.parseColor("#0000ff"));
        paint.setStrokeCap(Paint.Cap.SQUARE);       // 线帽,即画的线条两端是否带有圆角,SQUARE,矩形
        canvas.drawLine(200, 800, 500, 800, paint);
    }
Paint.Cap

3.setStrokeJoin(Paint.Join join)

设置线段连接处样式,取值有:
Paint.Join.MITER(结合处为锐角)
Paint.Join.Round (结合处为圆弧)
Paint.Join.BEVEL (结合处为直线)

  private void drawStrokeJoin( Canvas canvas ) {
        Paint paint = new Paint();

        paint.setAntiAlias( true );
        paint.setStrokeWidth( 80 );
        paint.setStyle(Paint.Style.STROKE ); // 默认是填充 Paint.Style.FILL
        paint.setColor( Color.parseColor("#0000ff") );

        Path path = new Path();
        path.moveTo(100, 100);
        path.lineTo(400, 100);
        path.lineTo(100, 300);
        paint.setStrokeJoin(Paint.Join.MITER);
        canvas.drawPath(path, paint);

        path.moveTo(100, 500);
        path.lineTo(400, 500);
        path.lineTo(100, 700);
        paint.setStrokeJoin(Paint.Join.ROUND);
        canvas.drawPath(path, paint);

        path.moveTo(100, 900);
        path.lineTo(400, 900);
        path.lineTo(100, 1100);
        paint.setStrokeJoin(Paint.Join.BEVEL);
        canvas.drawPath(path, paint);
    }

}

Paint.Join

4.setPathEffect(PathEffect effect)

设置绘制路径的效果,如点画线等

CornerPathEffect:

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

DashPathEffect:

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

DiscretePathEffect:

这个类的作用是打散Path的线段,使得在原来路径的基础上发生打散效果。
一般的,通过构造DiscretePathEffect(float segmentLength,float deviation)来构造一个实例,其中,segmentLength指定最大的段长,deviation指定偏离量。

PathDashPathEffect:

这个类的作用是使用Path图形来填充当前的路径,其构造函数为PathDashPathEffect (Path shape, float advance, float phase,PathDashPathEffect.Stylestyle)。
shape则是指填充图形,advance指每个图形间的间距,phase为绘制时的偏移量,style为该类自由的枚举值,有三种情况:Style.ROTATE、Style.MORPH和
Style.TRANSLATE。其中ROTATE的情况下,线段连接处的图形转换以旋转到与下一段移动方向相一致的角度进行转转,MORPH时图形会以发生拉伸或压缩等变形的情况与下一段相连接,TRANSLATE时,图形会以位置平移的方式与下一段相连接。

ComposePathEffect:

组合效果,这个类需要两个PathEffect参数来构造一个实例,ComposePathEffect (PathEffect outerpe,PathEffect innerpe),表现时,会首先将innerpe表现出来,然后再在innerpe的基础上去增加outerpe的效果。

SumPathEffect:

叠加效果,这个类也需要两个PathEffect作为参数SumPathEffect(PathEffect first,PathEffect second),但与ComposePathEffect不同的是,在表现时,会分别对两个参数的效果各自独立进行表现,然后将两个效果简单的重叠在一起显示出来。

关于参数phase

在存在phase参数的两个类里,如果phase参数的值不停发生改变,那么所绘制的图形也会随着偏移量而不断的发生变动,这个时候,看起来这条线就像动起来了一样。

private float phase;
    private PathEffect[] effects;
    private  int[] colors;

    private void drawPathEffect(Canvas canvas) {
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(4);
        // 创建,并初始化Path
        Path path = new Path();
        path.moveTo(0, 0);
        for (int i = 1; i <= 35; i++) {
            // 生成15个点,随机生成它们的坐标,并将它们连成一条Path
            path.lineTo(i * 20, (float) Math.random() * 60);
        }
        // 初始化七个颜色
        colors = new int[]{Color.BLACK, Color.BLUE, Color.CYAN, Color.GREEN, Color.MAGENTA, Color.RED,
                Color.GRAY};


        // 将背景填充成白色
        canvas.drawColor(Color.WHITE);
        effects = new PathEffect[7];
        // -------下面开始初始化7中路径的效果
        // 使用路径效果
        effects[0] = null;
        // 使用CornerPathEffect路径效果
        effects[1] = new CornerPathEffect(10);
        // 初始化DiscretePathEffect
        effects[2] = new DiscretePathEffect(3.0f, 5.0f);
        // 初始化DashPathEffect
        effects[3] = new DashPathEffect(new float[]{20, 10, 5, 10}, phase);
        // 初始化PathDashPathEffect
        Path p = new Path();
        p.addRect(0, 0, 8, 8, Path.Direction.CCW);
        effects[4] = new PathDashPathEffect(p, 12, phase, PathDashPathEffect.Style.ROTATE);
        // 初始化PathDashPathEffect
        effects[5] = new ComposePathEffect(effects[2], effects[4]);
        effects[6] = new SumPathEffect(effects[4], effects[3]);
        // 将画布移到8,8处开始绘制
        canvas.translate(8, 8);
        // 依次使用7中不同路径效果,7种不同的颜色来绘制路径
        for (int i = 0; i < effects.length; i++) {
            mPaint.setPathEffect(effects[i]);
            mPaint.setColor(colors[i]);
            canvas.drawPath(path, mPaint);
            canvas.translate(0, 200);
        }
        // 改变phase值,形成动画效果
        phase += 1;
        invalidate();
    }
PathEffect

5.setShadowLayer(float radius, float dx, float dy, int shadowColor)

阴影制作:包括各种形状(矩形,圆形等等),以及文字等等都能设置阴影。

    private void drawShadowLayer(Canvas canvas) {
        // 建立Paint 物件
        Paint paint1 = new Paint();
        paint1.setTextSize(100);
        // 设定颜色
        paint1.setColor(Color.BLACK);
        // 设定阴影(柔边, X 轴位移, Y 轴位移, 阴影颜色)
        paint1.setShadowLayer(10, 5, 5, Color.GRAY);
        // 实心矩形& 其阴影
        canvas.drawText("我爱你", 20,100,paint1);
        Paint paint2 = new Paint();
        paint2.setTextSize(100);
        paint2.setColor(Color.GREEN);
        paint2.setShadowLayer(10, 6, 6, Color.GRAY);
        canvas.drawText("你真傻", 20,200,paint2);

        //cx和cy为圆点的坐标
        int radius = 80;
        int offest = 40;
        int startX = radius + offest;
        int startY = radius + offest + 200;

        Paint paint3 = new Paint();
        //如果不关闭硬件加速,setShadowLayer无效
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        paint3.setShadowLayer(20, -20, 10, Color.DKGRAY);
        canvas.drawCircle(startX, startY, radius, paint3);
        paint3.setStyle(Paint.Style.STROKE);
        paint3.setStrokeWidth(5);
        canvas.drawCircle(startX + radius * 2 + offest, startY, radius, paint3);
    }
ShadowLayer

6.setXfermode(Xfermode xfermode)

Xfermode国外有大神称之为过渡模式,这种翻译比较贴切但恐怕不易理解,大家也可以直接称之为图像混合模式,因为所谓的“过渡”其实就是图像混合的一种,这个方法跟我们上面讲到的setColorFilter蛮相似的。查看API文档发现其果然有三个子类:AvoidXfermode, PixelXorXfermode和PorterDuffXfermode,这三个子类实现的功能要比setColorFilter的三个子类复杂得多。

由于AvoidXfermode, PixelXorXfermode都已经被标注为过时了,所以这次主要研究的是仍然在使用的PorterDuffXfermode:

PorterDuffXfermode

该类同样有且只有一个含参的构造方法PorterDuffXfermode(PorterDuff.Mode mode),虽说构造方法的签名列表里只有一个PorterDuff.Mode的参数,但是它可以实现很多酷毙的图形效果!!而PorterDuffXfermode就是图形混合模式的意思,其概念最早来自于SIGGRAPH的Tomas Proter和Tom Duff,混合图形的概念极大地推动了图形图像学的发展,延伸到计算机图形图像学像Adobe和AutoDesk公司著名的多款设计软件都可以说一定程度上受到影响,而我们PorterDuffXfermode的名字也来源于这俩人的人名组合PorterDuff,那PorterDuffXfermode能做些什么呢?我们先来看一张API DEMO里的图片:


image.png

这张图片从一定程度上形象地说明了图形混合的作用,两个图形一圆一方通过一定的计算产生不同的组合效果,在API中Android为我们提供了18种(比上图多了两种ADD和OVERLAY)模式:

ADD:饱和相加,对图像饱和度进行相加,不常用

CLEAR:清除图像

DARKEN:变暗,较深的颜色覆盖较浅的颜色,若两者深浅程度相同则混合

DST:只显示目标图像

DST_ATOP:在源图像和目标图像相交的地方绘制【目标图像】,在不相交的地方绘制【源图像】,相交处的效果受到源图像和目标图像alpha的影响

DST_IN:只在源图像和目标图像相交的地方绘制【目标图像】,绘制效果受到源图像对应地方透明度影响

DST_OUT:只在源图像和目标图像不相交的地方绘制【目标图像】,在相交的地方根据源图像的alpha进行过滤,源图像完全不透明则完全过滤,完全透明则不过滤

DST_OVER:将目标图像放在源图像上方

LIGHTEN:变亮,与DARKEN相反,DARKEN和LIGHTEN生成的图像结果与Android对颜色值深浅的定义有关

MULTIPLY:正片叠底,源图像素颜色值乘以目标图像素颜色值除以255得到混合后图像像素颜色值

OVERLAY:叠加

SCREEN:滤色,色调均和,保留两个图层中较白的部分,较暗的部分被遮盖

SRC:只显示源图像

SRC_ATOP:在源图像和目标图像相交的地方绘制【源图像】,在不相交的地方绘制【目标图像】,相交处的效果受到源图像和目标图像alpha的影响

SRC_IN:只在源图像和目标图像相交的地方绘制【源图像】

SRC_OUT:只在源图像和目标图像不相交的地方绘制【源图像】,相交的地方根据目标图像的对应地方的alpha进行过滤,目标图像完全不透明则完全过滤,完全透明则不过滤

SRC_OVER:将源图像放在目标图像上方

XOR:在源图像和目标图像相交的地方之外绘制它们,在相交的地方受到对应alpha和色值影响,如果完全不透明则相交处完全不绘制

public class PorterDuffView extends View {

    Paint mPaint;
    Context mContext;
    int BlueColor;
    int PinkColor;
    int mWith;
    int mHeight;
    public PorterDuffView(Context context) {
        super(context);
        init(context);
    }
    public PorterDuffView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mHeight = getMeasuredHeight();
        mWith = getMeasuredWidth();
    }

    private void init(Context context) {
        mContext = context;
        BlueColor = ContextCompat.getColor(mContext, R.color.colorPrimary);
        PinkColor = ContextCompat.getColor(mContext, R.color.colorAccent);
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setAntiAlias(true);
    }
    private Bitmap drawRectBm(){
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(BlueColor);
        paint.setStyle(Paint.Style.FILL);
        paint.setAntiAlias(true);
        Bitmap bm = Bitmap.createBitmap(200,200, Bitmap.Config.ARGB_8888);
        Canvas cavas = new Canvas(bm);
        cavas.drawRect(new RectF(0,0,70,70),paint);
        return bm;
    }
    private  Bitmap drawCircleBm(){
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(PinkColor);
        paint.setStyle(Paint.Style.FILL);
        paint.setAntiAlias(true);
        Bitmap bm = Bitmap.createBitmap(200,200, Bitmap.Config.ARGB_8888);
        Canvas cavas = new Canvas(bm);
        cavas.drawCircle(70,70,35,paint);
        return bm;
    }
    @Override
    protected void onDraw(Canvas canvas) {
        mPaint.setFilterBitmap(false);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setTextSize(20);
        RectF recf = new RectF(20,20,60,60);
        mPaint.setColor(BlueColor);
        canvas.drawRect(recf,mPaint);
        mPaint.setColor(PinkColor);
        canvas.drawCircle(100,40,20,mPaint);
        @SuppressLint("WrongConstant") int sc = canvas.saveLayer(0, 0,mWith,mHeight, null, Canvas.MATRIX_SAVE_FLAG |
                Canvas.CLIP_SAVE_FLAG |
                Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
                Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
                Canvas.CLIP_TO_LAYER_SAVE_FLAG);
        int y = 180;
        int x = 50;
        for(PorterDuff.Mode mode : PorterDuff.Mode.values()){
            if(y >= 900){
                y = 180;
                x += 200;
            }
            mPaint.setXfermode(null);
            canvas.drawText(mode.name(),x + 100,y,mPaint);
            canvas.drawBitmap(drawRectBm(),x,y,mPaint);
            mPaint.setXfermode(new PorterDuffXfermode(mode));
            canvas.drawBitmap(drawCircleBm(),x,y,mPaint);
            y += 120;
        }
        mPaint.setXfermode(null);
        // 还原画布
        canvas.restoreToCount(sc);
    }
}

推荐阅读更多精彩内容