自定义View基础

来源:互联网 发布:采购流程及优化 编辑:程序博客网 时间:2024/05/22 00:52

View体系

参考

http://hencoder.com/ui-1-1/

自定义绘制基础

canvas基本方法

public class CustomView1 extends View {     private Paint mPaint = new Paint();    Path mPath = new Path();    public CustomView1(Context context) {        super(context);        // 使用 path 对图形进行描述(这段描述代码不必看懂)       /* mPath.addArc(200, 200, 400, 400, -225, 225);        mPath.arcTo(400, 200, 600, 400, -180, 225, false);*/        mPath.lineTo(400, 542);    }    public CustomView1(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);    }    public CustomView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //抗锯齿        mPaint.setAntiAlias(true);        //设置模式        mPaint.setStyle(Paint.Style.STROKE);//STROKE:线条模式        //设置线条粗细        mPaint.setStrokeWidth(10);        //设置        mPaint.setColor(getResources().getColor(R.color.colorAccent));        //画圆形        //给整个view设置颜色(可以一开始设置背景色和最后设置遮罩颜色)        //canvas.drawColor(getResources().getColor(R.color.colorPrimary));        // 绘制一个圆        //canvas.drawCircle(300, 300, 200, mPaint);        //画矩形        //canvas.drawRect(100,100,200,200,mPaint);        /**         * drawPoint(float x, float y, Paint paint) 画点         * Paint.setStrokeCap(cap) 点的形状(圆形、方形)         *         * drawPoints(float[] pts, int offset, int count, Paint paint)         * drawPoints(float[] pts, Paint paint) 画点(批量)         *         * drawOval(float left, float top, float right, float bottom, Paint paint) 画椭圆         *         * drawLine(float startX, float startY, float stopX, float stopY, Paint paint) 画线         *         * drawLines(float[] pts, int offset, int count, Paint paint)         * drawLines(float[] pts, Paint paint) 画线(批量)         *         * drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint) 画圆角矩形         *         * drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint) 绘制弧形或扇形         *         * drawPath(Path path, Paint paint) 画自定义图形         *         *         */        canvas.drawPath(mPath, mPaint);    }}

这里写图片描述

canvas.drawPath绘制自定义图形

public class CustomView10 extends View {    Paint mPaint=new Paint();    Path mPath=new Path();    public CustomView10(Context context) {        super(context);    }    public CustomView10(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);    }    public CustomView10(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        mPaint.setAntiAlias(true);        /**         * Path 方法第一类:直接描述路径         *         * 添加子图形:         * addOval(float left, float top, float right, float bottom, Direction dir)         * addOval(RectF oval, Direction dir) 添加椭圆         *         * addRect(float left, float top, float right, float bottom, Direction dir)         * ddRect(RectF rect, Direction dir) 添加矩形         *         * addRoundRect(RectF rect, float rx, float ry, Direction dir)         * addRoundRect(float left, float top, float right, float bottom, float rx, float ry, Direction dir)         * addRoundRect(RectF rect, float[] radii, Direction dir)         * addRoundRect(float left, float top, float right, float bottom, float[] radii, Direction dir) 添加圆角矩形         *         * addPath(Path path) 添加另一个 Path         *         *         * 画线(直线或曲线):         *         *lineTo(float x, float y) / rLineTo(float x, float y) 画直线         *         * quadTo(float x1, float y1, float x2, float y2)         * rQuadTo(float dx1, float dy1, float dx2, float dy2) 画二次贝塞尔曲线         *         * cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)         * rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) 画三次贝塞尔曲线         *         * moveTo(float x, float y) / rMoveTo(float x, float y) 移动到目标位置         *         * arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)         * arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)         * arcTo(RectF oval, float startAngle, float sweepAngle) 画弧形         *         *         * addArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle)         * addArc(RectF oval, float startAngle, float sweepAngle)         *         * close() 封闭当前子图形         *         *         * Path 方法第二类:辅助的设置或计算         *         * Path.setFillType(Path.FillType ft) 设置填充方式         *         *         */        mPath.addCircle(100,100,50, Path.Direction.CW);        canvas.drawPath(mPath,mPaint);    }}

直方图

public class CustomView8 extends View {    Paint mPaint=new Paint();    Path mPath=new Path();    public CustomView8(Context context) {        super(context);    }    public CustomView8(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);    }    public CustomView8(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //绘制背景        canvas.drawColor(Color.DKGRAY);        //绘制坐标系        mPaint.setColor(Color.WHITE);        canvas.drawLine(100,10,100,400,mPaint);        canvas.drawLine(100,400,1500,400,mPaint);        //绘制矩形        Rect rect = new Rect(150,380,250,400);        mPaint.setColor(Color.GREEN);        canvas.drawRect(rect,mPaint);        //绘制文字        mPaint.setColor(Color.WHITE);        mPaint.setTextSize(20);        canvas.drawText("AAA",180,420,mPaint);        //绘制矩形        Rect rect1 = new Rect(300,360,400,400);        mPaint.setColor(Color.GREEN);        canvas.drawRect(rect1,mPaint);        //绘制文字        mPaint.setColor(Color.WHITE);        mPaint.setTextSize(20);        canvas.drawText("BBB",330,420,mPaint);        //绘制矩形        Rect rect2 = new Rect(450,60,550,400);        mPaint.setColor(Color.GREEN);        canvas.drawRect(rect2,mPaint);        //绘制文字        mPaint.setColor(Color.WHITE);        mPaint.setTextSize(20);        canvas.drawText("CCC",480,420,mPaint);    }}

这里写图片描述

饼状图

public class CustomView9 extends View {    Paint mPaint=new Paint();    Path mPath=new Path();    public CustomView9(Context context) {        super(context);    }    public CustomView9(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);    }    public CustomView9(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        mPaint.setAntiAlias(true);        mPaint.setColor(Color.BLUE);        mPaint.setStyle(Paint.Style.STROKE);        mPath.moveTo(450,170);        mPath.lineTo(530,170);        mPath.rLineTo(80,50);        canvas.drawText("AAA",610,230,mPaint);        canvas.drawPath(mPath,mPaint);        mPaint.setStyle(Paint.Style.FILL);        RectF rectF = new RectF(110, 100, 500, 500);        canvas.drawArc(rectF,0, 90,true,mPaint);        mPaint.setColor(Color.RED);        RectF rectF1 = new RectF(90, 90, 500, 500);        canvas.drawArc(rectF1,180, 90,true,mPaint);        mPaint.setColor(Color.YELLOW);        RectF rectF2 = new RectF(110, 90, 500, 500);        canvas.drawArc(rectF2,270, 70,true,mPaint);    }}

这里写图片描述

Paint

http://hencoder.com/ui-1-2/

颜色

  1. 基本颜色
  2. ColorFilter
  3. Xfermode

基本颜色

  • mPaint.setColor(Color.BLUE);
  • mPaint.setARGB(12,14,24,54);
  • mPaint.setShader(shader);

setShader(Shader shader) 设置 Shader

  • LinearGradient 线性渐变
  • RadialGradient 辐射渐变
  • SweepGradient 扫描渐变
  • BitmapShader
  • ComposeShader 混合着色器

LinearGradient 线性渐变

    Shader shader = new LinearGradient(100, 100, 500, 500, Color.parseColor("#E91E63"),            Color.parseColor("#2196F3"), Shader.TileMode.REPEAT);    mPaint.setShader(shader);    Rect rect = new Rect(100,200,1000,500);    canvas.drawRect(rect,mPaint);

构造方法:
LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, Shader.TileMode tile)

参数:
x0 y0 x1 y1:渐变的两个端点的位置
color0 color1 是端点的颜色

RadialGradient 辐射渐变

同上

SweepGradient 扫描渐变

同上

BitmapShader

    //图片的起点是(0,0)    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_theme_circle_search);    Shader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.MIRROR);    mPaint.setShader(shader);    Rect rect = new Rect(0,0,300,100);    canvas.drawRect(rect,mPaint);

构造方法:
BitmapShader(Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)

参数:
bitmap:用来做模板的 Bitmap 对象
tileX:横向的 TileMode
tileY:纵向的 TileMode。

ComposeShader 混合着色器

    // 第一个 Shader    Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.ic_action_ciyuan);    Shader shader1 = new BitmapShader(bitmap1, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);    // 第二个 Shader    Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.ic_theme_circle_search);    Shader shader2 = new BitmapShader(bitmap2, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);    // ComposeShader:结合两个 Shader    Shader shader = new ComposeShader(shader2, shader1, PorterDuff.Mode.ADD);    mPaint.setShader(shader);    Rect rect = new Rect(200,200,200+200,200+200);

构造方法:ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode)

参数:
shaderA, shaderB:两个相继使用的 Shader
mode: 两个 Shader 的叠加模式,即 shaderA 和 shaderB 应该怎样共同绘制。它的类型是 PorterDuff.Mode

PorterDuff.Mode:

  • 合成
  • 混合

这里写图片描述

这里写图片描述

ColorFilter

setColorFilter(ColorFilter colorFilter)

ColorFilter 这个类,它的名字已经足够解释它的作用:为绘制设置颜色过滤。颜色过滤的意思,就是为绘制的内容设置一个统一的过滤策略,然后 Canvas.drawXXX() 方法会对每个像素都进行过滤后再绘制出来。

在 Paint 里设置 ColorFilter ,使用的是 Paint.setColorFilter(ColorFilter filter) 方法。 ColorFilter 并不直接使用,而是使用它的子类。它共有三个子类:LightingColorFilter PorterDuffColorFilter 和 ColorMatrixColorFilter。

LightingColorFilter

PorterDuffColorFilter

ColorMatrixColorFilter

以上,就是 Paint 对颜色的第二层处理:通过 setColorFilter(colorFilter) 来加工颜色

setXfermode(Xfermode xfermode)

    Xfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);    Bitmap circleBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_action_ciyuan);    Bitmap rectBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_theme_circle_search);    canvas.drawBitmap(circleBitmap, 0, 0, mPaint); // 画方    mPaint.setXfermode(xfermode); // 设置 Xfermode    canvas.drawBitmap(rectBitmap, 0, 0, mPaint); // 画圆    mPaint.setXfermode(null); // 用完及时清除 Xfermode
  • ComposeShader 混合两个Shader
  • PorterDuffColorFilter 增加一个单色的ColorFilter
  • Xfermode 设置绘制内容和View中已有内容的混合计算方式

效果

setAntiAlias (boolean aa) 设置抗锯齿

快捷方式

Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

在构造方法里直接设置抗锯齿

setStyle(Paint.Style style)

用来设置图形是线条风格还是填充风格的(也可以二者并用)

线条形状

  • setStrokeWidth(float width)设置线条宽度。单位为像素,默认值是 0。
  • setStrokeCap(Paint.Cap cap)设置线头的形状。线头形状有三种:BUTT 平头、ROUND 圆头、SQUARE 方头。默认为 BUTT
  • setStrokeJoin(Paint.Join join)设置拐角的形状。有三个值可以选择:MITER 尖角、 BEVEL 平角和 ROUND 圆角。默认为 MITER
  • setStrokeMiter(float miter)这个方法是对于 setStrokeJoin() 的一个补充,它用于设置 MITER 型拐角的延长线的最大值。所谓「延长线的最大值」

色彩优化

setDither(boolean dither)

设置图像的抖动

setFilterBitmap(boolean filter)

设置是否使用双线性过滤来绘制 Bitmap

setPathEffect(PathEffect effect)

使用 PathEffect 来给图形的轮廓设置效果。对 Canvas 所有的图形绘制有效,也就是 drawLine() drawCircle() drawPath() 这些方法

        PathEffect pathEffect = new DashPathEffect(new float[]{10, 5}, 10);          paint.setPathEffect(pathEffect);        canvas.drawCircle(300, 300, 200, paint);

下面就具体说一下 Android 中的 6 种 PathEffect。PathEffect 分为两类,单一效果的 CornerPathEffect DiscretePathEffect DashPathEffect PathDashPathEffect ,和组合效果的 SumPathEffect ComposePathEffect。

CornerPathEffect

把所有拐角变成圆角

DiscretePathEffect

把线条进行随机的偏离,让轮廓变得乱七八糟。乱七八糟的方式和程度由参数决定

DashPathEffect

使用虚线来绘制线条

PathDashPathEffect

这个方法比 DashPathEffect 多一个前缀 Path ,所以顾名思义,它是使用一个 Path 来绘制「虚线」

SumPathEffect

这是一个组合效果类的 PathEffect 。它的行为特别简单,就是分别按照两种 PathEffect 分别对目标进行绘制。

ComposePathEffect

这也是一个组合效果类的 PathEffect 。不过它是先对目标 Path 使用一个 PathEffect,然后再对这个改变后的 Path 使用另一个 PathEffect。

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

在之后的绘制内容下面加一层阴影

PathEffect dashEffect = new DashPathEffect(new float[]{20, 10}, 0); PathEffect discreteEffect = new DiscretePathEffect(20, 5);  pathEffect = new ComposePathEffect(dashEffect, discreteEffect);canvas.drawPath(path, paint);

setMaskFilter(MaskFilter maskfilter)

为之后的绘制设置 MaskFilter。上一个方法 setShadowLayer() 是设置的在绘制层下方的附加效果;而这个 MaskFilter 和它相反,设置的是在绘制层上方的附加效果。

到现在已经有两个 setXxxFilter(filter) 了。前面有一个 setColorFilter(filter) ,是对每个像素的颜色进行过滤;而这里的 setMaskFilter(filter) 则是基于整个画面来进行过滤。
MaskFilter 有两种: BlurMaskFilter 和 EmbossMaskFilter。

BlurMaskFilter

模糊效果的 MaskFilter

EmbossMaskFilter

浮雕效果的 MaskFilter。

获取绘制的 Path

getFillPath(Path src, Path dst)

getTextPath(String text, int start, int end, float x, float y, Path path) / getTextPath(char[] text, int index, int count, float x, float y, Path path)

drawText() 相关

比如设置文字大小、比如设置文字间隔、比如设置各种文字效果


初始化类

这一类方法很简单,它们是用来初始化 Paint 对象,或者是批量设置 Paint 的多个属性的方法

reset()

重置 Paint 的所有属性为默认值。相当于重新 new 一个,不过性能当然高一些啦

set(Paint src)

把 src 的所有属性全部复制过来。相当于调用 src 所有的 get 方法,然后调用这个 Paint 的对应的 set 方法来设置它们。

setFlags(int flags)

批量设置 flags。相当于依次调用它们的 set 方法

文字的绘制

http://hencoder.com/ui-1-3/

Canvas 绘制文字的方式

Canvas 的文字绘制方法有三个:drawText() drawTextRun() 和 drawTextOnPath()

drawText(String text, float x, float y, Paint paint)

drawText() 是 Canvas 最基本的绘制文字的方法:给出文字的内容和位置, Canvas 按要求去绘制文字

方法的参数很简单: text 是文字内容,x 和 y 是文字的坐标。但需要注意:这个坐标并不是文字的左上角,而是一个与左下角比较接近的位置

drawTextRun()

没用

drawTextOnPath()

沿着一条 Path 来绘制文字

drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)

参数里,需要解释的只有两个: hOffset 和 vOffset。它们是文字相对于 Path 的水平偏移量和竖直偏移量,利用它们可以调整文字的位置。例如你设置 hOffset 为 5, vOffset 为 10,文字就会右移 5 像素和下移 10 像素。

StaticLayout

StaticLayout 的构造方法是 StaticLayout(CharSequence source, TextPaint paint, int width, Layout.Alignment align, float spacingmult, float spacingadd, boolean includepad),其中参数里:

width 是文字区域的宽度,文字到达这个宽度后就会自动换行;
align 是文字的对齐方向;
spacingmult 是行间距的倍数,通常情况下填 1 就好;
spacingadd 是行间距的额外增加值,通常情况下填 0 就好;
includeadd 是指是否在文字上下添加额外的空间,来避免某些过高的字符的绘制出现越界。

如果你需要进行多行文字的绘制,并且对文字的排列和样式没有太复杂的花式要求,那么使用 StaticLayout 就好。


Paint 对文字绘制的辅助

设置显示效果类

setTextSize(float textSize)

设置文字大小。

setTypeface(Typeface typeface)

设置字体。

setFakeBoldText(boolean fakeBoldText)

是否使用伪粗体。

setStrikeThruText(boolean strikeThruText)

是否加删除线。

setUnderlineText(boolean underlineText)

是否加下划线。

setTextSkewX(float skewX)

设置文字横向错切角度。其实就是文字倾斜度的啦。

setTextScaleX(float scaleX)

设置文字横向放缩。也就是文字变胖变瘦。

setLetterSpacing(float letterSpacing)

设置字符间距。默认值是 0

setFontFeatureSettings(String settings)

用 CSS 的 font-feature-settings 的方式来设置文字。

setTextAlign(Paint.Align align)

设置文字的对齐方式。一共有三个值:LEFT CETNER 和 RIGHT。默认值为 LEFT

setTextLocale(Locale locale) / setTextLocales(LocaleList locales)

Locale 直译是「地域」,其实就是你在系统里设置的「语言」或「语言区域」(具体名称取决于你用的是什么手机),比如「简体中文(中国)」「English (US)」「English (UK)」

setHinting(int mode)

设置是否启用字体的 hinting (字体微调)

setElegantTextHeight(boolean elegant)

setSubpixelText(boolean subpixelText)

setLinearText(boolean linearText)

测量文字尺寸类

float getFontSpacing()

获取推荐的行距

即推荐的两行文字的 baseline 的距离。这个值是系统根据文字的字体和字号自动计算的。它的作用是当你要手动绘制多行文字(而不是使用 StaticLayout)的时候,可以在换行的时候给 y 坐标加上这个值来下移文字。

FontMetircs getFontMetrics()

获取 Paint 的 FontMetrics。

FontMetrics 是个相对专业的工具类,它提供了几个文字排印方面的数值:ascent, descent, top, bottom, leading。如图,图中有两行文字,每一行都有 5 条线:top, ascent, baseline, descent, bottom。(leading 并没有画出来,因为画不出来,下面会给出解释)

baseline: 上图中黑色的线。前面已经讲过了,它的作用是作为文字显示的基准线。

ascent / descent: 上图中绿色和橙色的线,它们的作用是限制普通字符的顶部和底部范围。
普通的字符,上不会高过 ascent ,下不会低过 descent ,例如上图中大部分的字形都显示在 ascent 和 descent 两条线的范围内。具体到 Android 的绘制中, ascent 的值是图中绿线和 baseline 的相对位移,它的值为负(因为它在 baseline 的上方); descent 的值是图中橙线和 baseline 相对位移,值为正(因为它在 baseline 的下方)。

top / bottom: 上图中蓝色和红色的线,它们的作用是限制所有字形( glyph )的顶部和底部范围。
除了普通字符,有些字形的显示范围是会超过 ascent 和 descent 的,而 top 和 bottom 则限制的是所有字形的显示范围,包括这些特殊字形。例如上图的第二行文字里,就有两个泰文的字形分别超过了 ascent 和 descent 的限制,但它们都在 top 和 bottom 两条线的范围内。具体到 Android 的绘制中, top 的值是图中蓝线和 baseline 的相对位移,它的值为负(因为它在 baseline 的上方); bottom 的值是图中红线和 baseline 相对位移,值为正(因为它在 baseline 的下方)。

leading: 这个词在上图中没有标记出来,因为它并不是指的某条线和 baseline 的相对位移。 leading 指的是行的额外间距,即对于上下相邻的两行,上行的 bottom 线和下行的 top 线的距离,也就是上图中第一行的红线和第二行的蓝线的距离(对,就是那个小细缝)。

leading 这个词的本意其实并不是行的额外间距,而是行距,即两个相邻行的 baseline 之间的距离。不过对于很多非专业领域,leading 的意思被改变了,被大家当做行的额外间距来用;而 Android 里的 leading ,同样也是行的额外间距的意思。

另外,leading 在这里应该读作 “ledding” 而不是 “leeding” 哦。原因就不说了,我这越扯越远没边了。
FontMetrics 提供的就是 Paint 根据当前字体和字号,得出的这些值的推荐值。它把这些值以变量的形式存储,供开发者需要时使用。

FontMetrics.ascent:float 类型。
FontMetrics.descent:float 类型。
FontMetrics.top:float 类型。
FontMetrics.bottom:float 类型。
FontMetrics.leading:float 类型。
另外,ascent 和 descent 这两个值还可以通过 Paint.ascent() 和 Paint.descent() 来快捷获取。

getTextBounds(String text, int start, int end, Rect bounds)

获取文字的显示范围。

参数里,text 是要测量的文字,start 和 end 分别是文字的起始和结束位置,bounds 是存储文字显示范围的对象,方法在测算完成之后会把结果写进 bounds。

float measureText(String text)

测量文字的宽度并返回。

getTextWidths(String text, float[] widths)

获取字符串中每个字符的宽度,并把结果填入参数 widths。

int breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth)

这个方法也是用来测量文字宽度的。但和 measureText() 的区别是, breakText() 是在给出宽度上限的前提下测量文字的宽度。如果文字的宽度超出了上限,那么在临近超限的位置截断文字

光标相关

getRunAdvance(CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset)

对于一段文字,计算出某个字符处光标的 x 坐标。 start end 是文字的起始和结束坐标;contextStart contextEnd 是上下文的起始和结束坐标;isRtl 是文字的方向;offset 是字数的偏移,即计算第几个字符处的光标。

getOffsetForAdvance(CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance)

给出一个位置的像素值,计算出文字中最接近这个位置的字符偏移量(即第几个字符最接近这个坐标)。

方法的参数很简单: text 是要测量的文字;start end 是文字的起始和结束坐标;contextStart contextEnd 是上下文的起始和结束坐标;isRtl 是文字方向;advance 是给出的位置的像素值。填入参数,对应的字符偏移量将作为返回值返回。

getOffsetForAdvance() 配合上 getRunAdvance() 一起使用,就可以实现「获取用户点击处的文字坐标」的需求。

hasGlyph(String string)

检查指定的字符串中是否是一个单独的字形 (glyph)。最简单的情况是,string 只有一个字母(比如 a)。

范围裁切

http://hencoder.com/ui-1-4/

clipRect()

    canvas.save();      canvas.clipRect(left, top, right, bottom);      canvas.drawBitmap(bitmap, x, y, paint);      canvas.restore(); 

clipPath()

    canvas.save();      canvas.clipPath(path1);      canvas.drawBitmap(bitmap, point1.x, point1.y, paint);      canvas.restore();    canvas.save();      canvas.clipPath(path2);      canvas.drawBitmap(bitmap, point2.x, point2.y, paint);      canvas.restore();

几何变换

  • 使用 Canvas 来做常见的二维变换;
  • 使用 Matrix 来做常见和不常见的二维变换;
  • 使用 Camera 来做三维变换。

使用 Canvas 来做常见的二维变换

Canvas.rotate(float degrees, float px, float py) 旋转

参数里的 degrees 是旋转角度,单位是度(也就是一周有 360° 的那个单位),方向是顺时针为正向; px 和 py 是轴心的位置。

Canvas.scale(float sx, float sy, float px, float py) 放缩

参数里的 sx sy 是横向和纵向的放缩倍数; px py 是放缩的轴心。

skew(float sx, float sy) 错切

参数里的 sx 和 sy 是 x 方向和 y 方向的错切系数。

使用 Matrix 来做变换

Matrix 做常见变换的方式:

  1. 创建 Matrix 对象;
  2. 调用 Matrix 的 pre/postTranslate/Rotate/Scale/Skew() 方法来设置几何变换;
  3. 使用 Canvas.setMatrix(matrix) 或 Canvas.concat(matrix) 来把几何变换应用到 Canvas。

    Matrix matrix = new Matrix();matrix.reset();  matrix.postTranslate();  matrix.postRotate();canvas.save();  canvas.concat(matrix);  canvas.drawBitmap(bitmap, x, y, paint);  canvas.restore();  

把 Matrix 应用到 Canvas 有两个方法: Canvas.setMatrix(matrix) 和 Canvas.concat(matrix)

  • Canvas.setMatrix(matrix):用 Matrix 直接替换 Canvas 当前的变换矩阵,即抛弃 Canvas 当前的变换,改用 Matrix 的变换
  • Canvas.concat(matrix):用 Canvas 当前的变换矩阵和 Matrix 相乘,即基于 Canvas 当前的变换,叠加上 Matrix 中的变换。

不同的系统中 setMatrix(matrix) 的行为可能不一致,所以还是尽量用 concat(matrix)

使用 Matrix 来做自定义变换

Matrix.setPolyToPoly(float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount) 用点对点映射的方式设置变换

poly 就是「多」的意思。setPolyToPoly() 的作用是通过多点的映射的方式来直接设置变换。「多点映射」的意思就是把指定的点移动到给出的位置,从而发生形变。例如:(0, 0) -> (100, 100) 表示把 (0, 0) 位置的像素移动到 (100, 100) 的位置,这个是单点的映射,单点映射可以实现平移。而多点的映射,就可以让绘制内容任意地扭曲。

参数里,src 和 dst 是源点集合目标点集;srcIndex 和 dstIndex 是第一个点的偏移;pointCount 是采集的点的个数(个数不能大于 4,因为大于 4 个点就无法计算变换了)


使用 Camera 来做三维变换

Camera 的三维变换有三类:旋转、平移、移动相机。

Camera.rotate*() 三维旋转

Camera.rotate*() 一共有四个方法: rotateX(deg) rotateY(deg) rotateZ(deg) rotate(x, y, z)

    canvas.save();    camera.save(); // 保存 Camera 的状态      camera.rotateX(30); // 旋转 Camera 的三维空间      camera.applyToCanvas(canvas); // 把旋转投影到 Canvas      camera.restore(); // 恢复 Camera 的状态    canvas.drawBitmap(bitmap, point1.x, point1.y, paint);      canvas.restore(); 

如果你需要图形左右对称,需要配合上 Canvas.translate(),在三维旋转之前把绘制内容的中心点移动到原点,即旋转的轴心,然后在三维旋转后再把投影移动回来

    canvas.save();    camera.save(); // 保存 Camera 的状态      camera.rotateX(30); // 旋转 Camera 的三维空间      canvas.translate(centerX, centerY); // 旋转之后把投影移动回来      camera.applyToCanvas(canvas); // 把旋转投影到 Canvas      canvas.translate(-centerX, -centerY); // 旋转之前把绘制内容移动到轴心(原点)      camera.restore(); // 恢复 Camera 的状态    canvas.drawBitmap(bitmap, point1.x, point1.y, paint);      canvas.restore(); 

Canvas 的几何变换顺序是反的,所以要把移动到中心的代码写在下面,把从中心移动回来的代码写在上面。

Camera.translate(float x, float y, float z) 移动

Camera.setLocation(x, y, z) 设置虚拟相机的位置

它的参数的单位不是像素,而是 inch,英寸

在 Camera 中,相机的默认位置是 (0, 0, -8)(英寸)。8 x 72 = 576,所以它的默认位置是 (0, 0, -576)(像素)。

View绘制顺序

super.onDraw()

如果自定义View 继承View。super.onDraw()方法是空实现

如果自定义View 继承ImageView或者TextView。super.onDraw()方法在自定义View里则会影响绘制结果。

自定义绘制内容在super.onDraw()下面,则会覆盖super.onDraw()绘制内容。反之,则会被super.onDraw()所影响

dispatchDraw():绘制子 View 的方法

这个方法只适用于ViewGroup,如果直接在ViewGroup的onDraw()中绘制。此时如果ViewGroup有子View,则会被子视图的覆盖掉。所以一般会使用ViewGroup的dispatchDraw()方法绘制。

  • ViewGroup也有super.dispatchDraw()。同super.onDraw()
  • 如果要在ViewGroup的onDraw()中绘制,需要关闭setWillNotDraw(false);(注意:有的ViewGroup已经默认关闭(ScrollView))

绘制过程简述

  1. 背景
  2. 主体(onDraw())
  3. 子 View(dispatchDraw())
  4. 滑动边缘渐变和滑动条
  5. 前景(onDrawForeground())

onDrawForeground()

这个方法是 API 23 才引入的

写在 super.onDrawForeground() 的下面

如果你把绘制代码写在了 super.onDrawForeground() 的下面,绘制代码会在滑动边缘渐变、滑动条和前景之后被执行,那么绘制内容将会盖住滑动边缘渐变、滑动条和前景。

写在 super.onDrawForeground() 的上面

如果你把绘制代码写在了 super.onDrawForeground() 的上面,绘制内容就会在 dispatchDraw() 和 super.onDrawForeground() 之间执行,那么绘制内容会盖住子 View,但被滑动边缘渐变、滑动条以及前景盖住

draw() 总调度方法

除了 onDraw() dispatchDraw() 和 onDrawForeground() 之外,还有一个可以用来实现自定义绘制的方法: draw()。

public void draw(Canvas canvas) {      drawBackground(Canvas); // 绘制背景(不能重写)    onDraw(Canvas); // 绘制主体    dispatchDraw(Canvas); // 绘制子 View    onDrawForeground(Canvas); // 绘制滑动相关和前景  }

注意

  • 出于效率的考虑,ViewGroup 默认会绕过 draw() 方法,换而直接执行 dispatchDraw(),以此来简化绘制流程。所以如果你自定义了某个 ViewGroup 的子类(比如 LinearLayout)并且需要在它的除 dispatchDraw() 以外的任何一个绘制方法内绘制内容,你可能会需要调用 View.setWillNotDraw(false) 这行代码来切换到完整的绘制流程(是「可能」而不是「必须」的原因是,有些 ViewGroup 是已经调用过 setWillNotDraw(false) 了的,例如 ScrollView)。
  • 有的时候,一段绘制代码写在不同的绘制方法中效果是一样的,这时你可以选一个自己喜欢或者习惯的绘制方法来重写。但有一个例外:如果绘制代码既可以写在 onDraw() 里,也可以写在其他绘制方法里,那么优先写在 onDraw() ,因为 Android 有相关的优化,可以在不需要重绘的时候自动跳过 onDraw() 的重复执行,以提升开发效率。享受这种优化的只有 onDraw() 一个方法。
原创粉丝点击