安卓自定义控件(一)Canvas、Paint、Shader、Xfermode

关于自定义控件,之前就写过一篇自定义控件,上图下字的Button,图片任意指定大小,但是使用效果还是让人感觉不幸福,这次索性彻彻底底地对自定义控件做一次彻彻底底的总结。

我会花4篇博客来介绍自定义控件,由潜入深慢慢学:
工具类:ViewHelper(View处理常用方法封装)
安卓自定义控件(一)Canvas、Paint、Shader、Xfermode
安卓自定义控件(二)BitmapShader、ShapeDrawable、Shape
安卓自定义控件(三)实现自定义View
4、onLayout(实现自定义ViewGroup)。
这一篇博客就先介绍一下onDraw方法。

概述

自定义控件有三个比较重要的方法

  • onDraw 就是在绘制View的样式。
  • onMeasure 算出自己需要占用多大的面积。
  • onLayout 按照我们想要的规则自定义子View排列。

因为View设计出来肯定是给别人看的,首先我们考虑绘制View,而绘制View就必须先学会Canvas(画布)和Paint(画笔);然后就是设置View的大小,让我们的控件更加强大,更加通用,就像Button,它可以指定大小,也可以不指定;如果你设计的是ViewGroup,那可能还得考虑每一个子View的位置,那就得考虑重写onLayout方法。

最简单的自定义View

源码:

写一个最简单的自定义控件,鼓励一下自己吧。

/**
 * 矩形
 * Created by ChenSS on 2016/11/24.
 */
public class RectView extends View{
    public RectView(Context context) {
        super(context);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint paint = new Paint();
        paint.setColor(Color.RED);
        canvas.drawRect(60, 60, 180, 180, paint);
        //或者
        //canvas.drawRect(new Rect(200,200,300,300),paint);
    }
}

在Activity使用我们的自定义View:

public class RectActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new RectView(this));
    }
}

这里写图片描述
这样就写完了,是不是感觉自己棒棒哒?我们实现了一个最简单的自定义View,不过它非常弱小,只是一个矩形,甚至连长宽都被我们写死了。

小结:

这个自定义控件虽然有点Low,但是我们也有一些收获:

  1. onDraw的参数是一个Canvas(画布),画布决定了绘制的是一个什么样的图形;
  2. 画布的使用需要有Paint(画笔)的配合,画笔决定了绘制的颜色;
  3. 此外,还会涉及到Color(颜色),Bitmap(位图),Shader(着色),Shape(形状)等等。

以上是一个粗略总结,查看API会发现,上面提到的类都属于 android.graphics这个包,接下去就去瞅一瞅他们到底有什么本事。

认识graphics中常用的类

1.Color(颜色)类

  Android系统中颜色的常用表示方法有以下4种:
  (1)int color = Color.BLUE;
  (2)int color = Color.argb(150,200,0,100);
  (3)layout.setBackgroundColor(R.color.white),引用颜色资源;
  (4)Color.parseColor(“#365663”)。

  在实际应用中,我们常用的颜色有以下几种,其颜色常量及其表示的颜色如下所示:
  

  • Color.BLACK 黑色
  • Color.GREEN 绿色
  • Color.BLUE 蓝色
  • Color.LTGRAY浅灰色
  • Color.CYAN 青绿色
  • Color.MAGENTA 红紫色
  • Color.DKGRAY 灰黑色
  • Color.RED 红色
  • Color.YELLOW 黄色
  • Color.TRANSPARENT 透明
  • Color.GRAY 灰色
  • Color.WHITE 白色

2.Paint(画笔)类

  要绘制图形,首先得调整画笔,按照自己的开发需要,设置画笔的相关属性。Pain类的常用属性设置方法如下:

  • setAntiAlias(); //设置画笔的锯齿效果
  • setColor(); //设置画笔的颜色
  • setARGB(); //设置画笔的A、R、G、B值
  • setAlpha(); //设置画笔的Alpha值
  • setStyle(); //设置画笔的风格(空心或实心)
  • setStrokeWidth(); //设置空心边框的宽度
  • getColor(); //获取画笔的颜色
  • setTextScaleX(float scaleX) // 设置文本缩放倍数,1.0f为原始
  • setTextSize(float textSize) // 设置字体大小
  • setUnderlineText(booleanunderlineText) // 设置下划线

3.Canvas(画布)类

  画笔属性设置好之后,还需要将图像绘制到画布上。Canvas绘制常用图形的常用方法如下:  

  • 绘制直线:canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint);
  • 绘制矩形:canvas.drawRect(float left, float top, float right, float bottom, Paint paint);
  • 绘制圆形:canvas.drawCircle(float cx, float cy, float radius, Paint paint);
  • 绘制字符:canvas.drawText(String text, float x, float y, Paint paint);
  • 绘制图形:canvas.drawBitmap(Bitmap bitmap, float left, float top, Paint paint);
  • 绘制扇形(在一个矩形区域的两个角之间绘制一个弧。):drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
  • 绘制圆角矩形:drawRoundRect(RectF rect, float rx, float ry, Paint paint)
  • 绘制椭圆:drawOval(float left, float top, float right, float bottom, Paint paint)

补充:绘制任意多边形

其实画笔还有其它功能,这里就不再展开,这里再补充一个绘制多边形的案例。

/**
 * 绘制任意多边形(简单地绘制了一个三角形,我比较懒)
 * Created by ChenSS on 2016/11/24.
 */
public class RectView extends View{
    public RectView(Context context) {
        super(context);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint paint = new Paint();
        //设置空心
        paint.setStyle(Paint.Style.STROKE);
        Path path1=new Path();
        path1.moveTo(200, 800);
        path1.lineTo(800, 800);
        path1.lineTo(800, 200);
        path1.close();//封闭
        canvas.drawPath(path1, paint);
    }
}

duobianxing

Shader(着色器、渲染)

绘制完图形,接下来就要考虑颜色的问题了,虽然Color很强大,但是也并不能满足我们全部的问题,就比如:颜色渐变,这时候就要依靠Shader了,Shader直译是着色器,我就称呼它着色器吧。

Shader它主要实现颜色的渲染效果,此外还提供了颜色渐变、图片拉伸、平铺的功能,它包含以下5个子类:BitmapShader, ComposeShader, LinearGradient, RadialGradient, SweepGradient。

LinearGradient线性渲染

Create a shader that draws a linear gradient along a line.
创建一个着色器,颜色沿一条直线进行渐变。

        //创建线性渲染对象
        int mColorLinear[] = {Color.RED, Color.GREEN, Color.BLUE, Color.WHITE};
        Shader mShader = new LinearGradient(0, 0, 100, 100, mColorLinear, null, Shader.TileMode.REPEAT);

构造函数:

LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions, Shader.TileMode tile)

  • float x0: 渐变起始点x坐标
  • float y0:渐变起始点y坐标
  • float x1:渐变结束点x坐标
  • float y1:渐变结束点y坐标
  • int[] colors:颜色 的int 数组
  • float[] positions: 相对位置的颜色数组,可为null, 若为null,可为null,颜色沿渐变线均匀分布
  • Shader.TileMode tile: 渲染器平铺模式

LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, Shader.TileMode tile)

  • float x0: 渐变起始点x坐标
  • float y0:渐变起始点y坐标
  • float x1:渐变结束点x坐标
  • float y1:渐变结束点y坐标
  • int color0: 起始渐变色
  • int color1: 结束渐变色
  • Shader.TileMode tile: 渲染器平铺模式

SweepGradient梯度渲染

A subclass of Shader that draws a sweep gradient around a center point.
创建一个着色器,颜色沿着某个点的旋转方向进行渐变。

        //创建线性渲染对象
        int mColorLinear[] = {Color.RED, Color.GREEN, Color.BLUE, Color.WHITE};
        mSweepGradient = new SweepGradient(240, 360,mColorLinear , null);   

构造函数:

SweepGradient(float cx, float cy, int[] colors, float[] positions)

  • cx 渲染中心点x 坐标
  • cy 渲染中心y 点坐标
  • colors 围绕中心渲染的颜色数组,至少要有两种颜色值
  • positions 相对位置的颜色数组,可为null, 若为null,可为null,颜色沿渐变线均匀分布

SweepGradient(float cx, float cy, int color0, int color1)

  • cx 渲染中心点x 坐标
  • cy 渲染中心点y 坐标
  • color0 起始渲染颜色
  • color1 结束渲染颜色

RadialGradient环形渲染

Create a shader that draws a radial gradient given the center and radius.
创建一个着色器,颜色沿着半径方向进行渐变。

        //颜色渐变效果为一圈一圈的圆环
        int mColorRadial[] = {Color.GREEN, Color.RED, Color.BLUE, Color.WHITE};
        Shader mShader = new RadialGradient(350, 325, 75, mColorRadial, null, Shader.TileMode.REPEAT);

构造函数:

RadialGradient(float centerX, float centerY, float radius, int[] colors, float[] stops, Shader.TileMode tileMode)

  • float x: 圆心X坐标
  • float y: 圆心Y坐标
  • float radius: 半径
  • int[] colors: 渲染颜色数组
  • floate[] positions: 相对位置数组,可为null, 若为null,可为null,颜色沿渐变线均匀分布
  • Shader.TileMode tile:渲染器平铺模式

RadialGradient(float centerX, float centerY, float radius, int centerColor, int edgeColor, Shader.TileMode tileMode)

  • float x: 圆心X坐标
  • float y: 圆心Y坐标
  • float radius: 半径
  • int color0: 圆心颜色
  • int color1: 圆边缘颜色
  • Shader.TileMode tile:渲染器平铺模式

ComposeShader组合渲染

看它的构造方法,显然这个类的作用是Shader组合使用的方法,不过后面两个参数很陌生,后面我们再做介绍
ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode)
ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode)

BitmapShader位图渲染

可以设置图片的CLAMP(拉伸)、REPEAT(重复)、MIRROR( 镜像),关于Shader.TileMode后面也会具体介绍
BitmapShader(Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)

在自定义控件中使用着色器

/**
 * 颜色渲染
 * Created by ChenSS on 2016/11/24.
 */
public class RectView extends View {
    public RectView(Context context) {
        super(context);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //创建线性渲染对象
        int mColorLinear[] = {Color.RED, Color.GREEN, Color.BLUE, Color.WHITE};
        Shader mShader = new LinearGradient(0, 0, 100, 100, mColorLinear, null, Shader.TileMode.REPEAT);
        Paint paint = new Paint();
        paint.setShader(mShader);
        paint.setColor(Color.RED);
        canvas.drawRect(60, 60, 800, 800, paint);
    }
}

这里写图片描述

Shader.TileMode(平铺模式)

emun Shader.TileMode这是一个枚举类,决定了图形的平铺方式,就像我们电脑上更换桌面图片,可以选择拉伸、平铺等等。

3种模式:
static final Shader.TileMode CLAMP: 边缘拉伸。
replicate the edge color if the shader draws outside of its original bounds

static final Shader.TileMode MIRROR:横向不断翻转重复,纵向不断翻转重复,不断对称变化铺满。
repeat the shader’s image horizontally and vertically, alternating mirror images so that adjacent images always seam

Static final Shader.TillMode REPETA:在水平方向和垂直方向重复摆放,两个相邻图像间有缝隙缝隙。
repeat the shader’s image horizontally and vertically

具体的效果,这里不好展开说明,具体案例我放到了下一篇博客。自定义控件(二)

Xfermode(遮罩图层)

前面介绍了ComposeShader组合渲染,不过只列出了两个构造方法,现在开始介绍这几个陌生的家伙:PorterDuff、Xfermode、PorterDuff.Mode。

查看API你会发现Xfermode有3个子类,不过,有两个类已经过时了,而且,在Android Studio上也只能使用其中一个:PorterDuffXfermode,我就只介绍PorterDuffXfermode的使用。

Xfermode能做什么?从下面的效果图,我们可以看出,Xfermode大概就是做一些运算,给他两张图片,然后做出一个组合之后的效果。

效果图

这里写图片描述

API截图:

这里写图片描述

常量介绍:

1.PorterDuff.Mode.CLEAR
所绘制不会提交到画布上。
2.PorterDuff.Mode.SRC
显示上层绘制图片
3.PorterDuff.Mode.DST
显示下层绘制图片
4.PorterDuff.Mode.SRC_OVER
正常绘制显示,上下层绘制叠盖。
5.PorterDuff.Mode.DST_OVER
上下层都显示。下层居上显示。
6.PorterDuff.Mode.SRC_IN
取两层绘制交集。显示上层。
7.PorterDuff.Mode.DST_IN
取两层绘制交集。显示下层。
8.PorterDuff.Mode.SRC_OUT
取上层绘制非交集部分。
9.PorterDuff.Mode.DST_OUT
取下层绘制非交集部分。
10.PorterDuff.Mode.SRC_ATOP
取下层非交集部分与上层交集部分
11.PorterDuff.Mode.DST_ATOP
取上层非交集部分与下层交集部分
12.PorterDuff.Mode.XOR
异或:去除两图层交集部分
13.PorterDuff.Mode.DARKEN
取两图层全部区域,交集部分颜色加深
14.PorterDuff.Mode.LIGHTEN
取两图层全部,点亮交集部分颜色
15.PorterDuff.Mode.MULTIPLY
取两图层交集部分叠加后颜色
16.PorterDuff.Mode.SCREEN
取两图层全部区域,交集部分变为透明色
17.PorterDuff.Mode.ADD Saturate(S + D)
18.PorterDuff.Mode.OVERLAY

Xfermode测试Demo

/**
 * Xfermode的使用
 * Created by ChenSS on 2016/11/24.
 */
public class RectView extends View {
    private int screenW, screenH;
    private int width = 200;
    private int height = 200;
    private Paint mPaint;
    private Bitmap srcBitmap, dstBitmap;
    private PorterDuffXfermode xfermode;

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

    public RectView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //ViewHelper是我写的工具类,顶部已经给出链接
        screenW = ViewHelper.getScreenW(context);
        screenH = ViewHelper.getScreenH(context);
        srcBitmap = makeSrc(width, height);
        dstBitmap = makeDst(width, height);
        //修改Mode常量,查看不同的效果
        xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
        mPaint = new Paint();
        mPaint.setFilterBitmap(false);
        mPaint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //左边的图片
        canvas.drawBitmap(srcBitmap, (screenW / 3 - width) / 2, (screenH / 2 - height) / 2, mPaint);
        //中间的图片
        canvas.drawBitmap(dstBitmap, (screenW / 3 - width) / 2 + screenW / 3, (screenH / 2 - height) / 2, mPaint);

        //创建一个图层,关于图层的使用,结尾附上其他人的博客链接
        int sc = canvas.saveLayer(0, 0, screenW, screenH, 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);

        //合并两个图片
        canvas.drawBitmap(dstBitmap, (screenW / 3 - width) / 2 + screenW / 3 * 2, (screenH / 2 - height) / 2, mPaint);
        //设置Paint的Xfermode
        mPaint.setXfermode(xfermode);
        canvas.drawBitmap(srcBitmap, (screenW / 3 - width) / 2 + screenW / 3 * 2, (screenH / 2 - height) / 2, mPaint);
        mPaint.setXfermode(null);

        // 还原画布
        canvas.restoreToCount(sc);
    }


    /**
     * 绘制一个圆形的Bitmap
     */
    private Bitmap makeDst(int w, int h) {
        Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(bm);
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(Color.RED);
        c.drawOval(new RectF(0, 0, w * 3 / 4, h * 3 / 4), paint);
        return bm;
    }

    /**
     * 绘制一个矩形的Bitmap
     */
    private Bitmap makeSrc(int w, int h) {
        Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(bm);
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(Color.GREEN);
        c.drawRect(w / 3, h / 3, w * 19 / 20, h * 19 / 20, paint);
        return bm;
    }
}

这里写图片描述

代码中,我将PorterDuff.Mode设置为DST_IN,他是取第二个图层(DST)的交集部分,大家可以修改Mode值,查看每个常量的效果。

ComposeShader组合渲染Demo

ComposeShader的放到这里来介绍,因为它的构造函数我们还不理解,了解了PorterDuff.Mode每一个常量的作用之后,ComposeShader的功能也比较好理解一些。
它的作用就是根据PorterDuff.Mode模式,组合使用两个Shader。

/**
 * ComposeShader对象的使用
 * Created by ChenSS on 2016/11/24.
 */
public class RectView extends View {
    private LinearGradient mLinearGradient;
    private BitmapShader mBitmapShader;
    private ComposeShader mComposeShader;
    private Paint mPaint;
    private Bitmap mBitmap;
    private int mWidth, mHeight;

    public RectView(Context context) {
        super(context);
        //得到图像
        mBitmap = ViewHelper.findBitmapById(context, R.mipmap.view_robot);
        mWidth = mBitmap.getWidth();
        mHeight = mBitmap.getHeight();

        //创建LinearGradient对象
        int mColorLinear[] = {Color.WHITE,  Color.BLUE, Color.WHITE};
        mLinearGradient = new LinearGradient(0, 0, mWidth, mHeight, mColorLinear, null, Shader.TileMode.REPEAT);

        // 创建BitmapShader对象
        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);

        // 混合渲染 将两个效果叠加,Mode取值DST_ATOP
        mComposeShader = new ComposeShader(mBitmapShader, mLinearGradient, PorterDuff.Mode.DST_ATOP);

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.GRAY);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 绘制混合渲染效果
        mPaint.setShader(mComposeShader);
        canvas.drawRect(0, 0, mWidth, mHeight, mPaint);
    }
}

这里写图片描述

Xfermode实战的初步设想:

我们先画一个圆,然后再给他一张Bitmap,然后通过Xfermode,把Bitmap圆形的部分显示出来,实现一个圆形的ImageView。

安卓自定义控件(一)就到此结束,简单地介绍了一下图形绘制、颜色的使用,下一篇进一步介绍BitmapShader位图渲染和Xfermode的实际使用,然后再想办法做出各种形状的图片。

Canvas图层也是十分重要的,这里就不具体展开,这里给出其他人博客的链接。
android canvas layer (图层)详解与进阶

 

posted on 2016-11-25 00:13  疯狂的妞妞  阅读(467)  评论(0编辑  收藏  举报

导航