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);
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容