Android自定义组件相关基础总结

自定义绘制知识的四个级别

一、Canvas 的 drawXXX() 系列方法及 Paint 最常见的使用

    |--简单图形的绘制:canvas.drawXXX()及paint的一些常用参数设置

    |--自定义图形绘制Path,它有两类方法:

      |--直接描述路径的

          |--添加子图形 path.addXxx()

          |--画线(直线或曲线)path.xxxTo(), close()或lineto()可图形封闭。特殊的方法:画弧 arcTo() 和 addArc()

      |--辅助的设置或计算,其中一种setFillType(FillType fillType)。FillType四中算法

            |--EVEN_ODD 奇偶原则(无方向要求):奇数,则这个点被认为在图形内部;如果是偶数,则这个点被认为在图形外部

            |--WINDING (默认值)非零环绕数原则(有方向):以 0 初始值,遇到顺时针的交点结果加 1,遇到每个逆时针的交点把结果减 1,得到的结果不是 0,这个点在图形内部,为0在外部

            |--INVERSE_EVEN_ODD

            |--INVERSE_WINDING

    |--绘制图像canvas.drawBitmap()

    |--绘制文字,三种方式

        |--drawText() 给出文字的内容和位置, Canvas 按要求去绘制文字,(x,y) 绘制的左下角,y为baseline

        |--drawTextRun()  外国用

        |--drawTextOnPath() 沿着path绘制, 使用的 Path 拐弯处全用圆角,别用尖角

        |--自动换行 staticLayout.draw(canvas);

二、paint 的完全攻略

Paint 的 API 大致可以分为 4 类:

    |--颜色

        |--基本颜色

            |--canvas.drawColor()等

            |--canvas.drawBitmap()

            |--通过paint设置,方法有两种

                |--直接用 Paint.setColor/ARGB() 来设置颜色

                |--另一种是使用 Shader 来指定着色方案,paint.setShader()

                    |--子类LinearGradient 线性渐变

                    |--子类 RadialGradient 辐射渐变

                    |--子类 SweepGradient 扫描渐变

                    |--子类 BitmapShader 图像着色

                    |--子类ComposeShader 混合着色器,两着色器叠加模式(PorterDuff.MODE.xxx,18种)

        |--ColorFilter 通过 paint.setColorFilter()

            |--LightingColorFilter 模拟简单的光照效果

            |--PorterDuffColorFilter 使用一个指定的颜色和一种指定的 PorterDuff.Mode 来与绘制对象进行合成

            |--ColorMatrixColorFilter 使用一个 ColorMatrix 来对颜色进行处理

        |--Xfermode paint.setXfermode() 其实就是要你以绘制的内容作为源图像,以 View 中已有的内容作为目标图像,选取一个  PorterDuff.Mode 作为绘制内容的颜色处理方案

            |--子类PorterDuffXfermode  注意事项:

                |--1.使用离屏缓冲 Canvas.saveLayer(),绘制完restoreToCount(id)

                |--2.控制好透明区域,必须足够覆盖到要和它结合绘制的内容

    |--效果 paint效果类的 API ,指的就是抗锯齿、填充/轮廓、线条宽度等等这些

        |--paint.setAntiAlias (boolean aa) 设置抗锯齿

        |--paint.setStyle(Paint.Style style)图形或线条风格

        |--设置线条形状,有 4 个方法:

            |--setStrokeWidth(float width), 宽度

            |--setStrokeCap(Paint.Cap cap),  设置线头的形状。线头形状有三种:BUTT 平头、ROUND 圆头、SQUARE 方头。默认为 BUTT

            |--setStrokeJoin(Paint.Join join), 设置拐角的形状。有三个值可以选择:MITER 尖角、 BEVEL 平角和 ROUND 圆角。默认为 MITER

            |--setStrokeMiter(float miter) 。这个方法是对于 setStrokeJoin() 的一个补充,它用于设置 MITER 型拐角的延长线的最大值。超过角度时,尖角变平角

        |--色彩优化

            |--paint.setDither(boolean dither)设置图像的抖动

            |--paint.setFilterBitmap(boolean filter)设置是否使用双线性过滤来绘制 Bitmap 。

        |--paint.setPathEffect(PathEffect effect) 使用 PathEffect 来给图形的轮廓设置效果。对 Canvas 所有的图形绘制有效

            |--CornerPathEffect 把所有拐角变成圆角

            |--DiscretePathEffect 把线条进行随机的偏离,让轮廓变得乱七八糟。

            |--DashPathEffect 使用虚线来绘制线条

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

            |--SumPathEffect 组合效果类的 PathEffect ,就是分别按照两种 PathEffect 分别对目标进行绘制

            |--ComposePathEffect 组合效果类的 PathEffect ,先对目标 Path 使用一个 PathEffect,然后再对这个改变后的 Path 使用另一个 PathEffect

        |--setShadowLayer() 在之后的绘制内容下面加一层阴影,即绘制层下方的附加效果

        |--setMaskFilter() 整个图像的过滤,为之后的绘制设置 MaskFilter,设置的是在绘制层上方的附加效果

            |--BlurMaskFilter 模糊效果的 MaskFilter,四种参数:NORMAL: 内外都模糊绘制 SOLID: 内部正常绘制,外部模糊 INNER: 内部模糊,外部不绘制 OUTER: 内部不绘制,外部模糊(什么鬼?)

            |--EmbossMaskFilter 浮雕效果的 MaskFilter。

        |--获取绘制的 Path 根据 paint 的设置,计算出绘制 Path 或文字时的实际 Path

            |--getFillPath(Path src, Path dst)所谓实际 Path ,指的就是 drawPath() 的绘制内容的轮廓,要算上线条宽度和设置的 PathEffect。若使用默认线条宽度为0时,两path相等

            |--getTextPath 文字的 Path」。文字的绘制,虽然是使用 Canvas.drawText() 方法,但其实在下层,文字信息全是被转化成图形,对图形进行绘制的

    |--drawText()相关  Paint 有些设置是文字绘制相关的

        |--效果类

            |--setTextSize(float textSize) 设置文字大小。

            |--setTypeface(Typeface typeface)设置字体。 paint.setTypeface(Typeface.createFromAsset(getContext().getAssets(), "Satisfy-Regular.ttf"))

            |--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。

            |--Locale 直译是「地域」,其实就是你在系统里设置的「语言」或「语言区域」

            |--setLinearText(boolean linearText)

        |--测量文字尺寸类

            |--paint.getFontSpacing() 获取推荐的行距

            |--paint.getFontMetrics() FontMetrics 是个相对专业的工具类,它提供了几个文字排印方面的数值:ascent,    descent, top,

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

                FontMetrics 和 getFontSpacing()比,bottom - top + leading 的结果是要大于 getFontSpacing() 的返回值的.

                但getFontSpacing显示效果更好,一般使用getFontSpanceing

            |--paint.getTextBounds(String text, int start, int end, Rect bounds) 获取文字的显示范围,放在bounds里(显示)

            |--measureText(String text) 测量文字的宽度并返回(占用)

            |--getTextWidths(String text, float[] widths)获取字符串中每个字符的宽度,并把结果填入参数 widths。

            |--breakText() 这个方法也是用来测量文字宽度的。但和 measureText() 的区别是, breakText() 是在给出宽

                度上限的前提下测量文字的宽度。如果文字的宽度超出了上限,那么在临近超限的位置截断文字

        |--光标相关 EditText 以及类似的场景,会需要绘制光标。API 23 引入了两个新的方法:getRunAdvance() getOffsetForAdvance()

        |--hasGlyph(String string) 检查指定的字符串中是否是一个单独的字形 (glyph)

    |--初始化

        |--paint.reset()重置

        |--paint.set(Paint src) 把 src 的所有属性全部复制过来

        |--setFlags(int flags) 批量设置 flags

---------------------

pathMeasure相关

pathMeasure.getLength() 获取长度

pathMeasure.getSegment(200, 1000, dst, true);  //截取一部分存入dst中,并且使用moveTo保持截取得到的Path第一个点位置不变。

pathMeasure.getPosTan(mLength,pos,tan); //获取路径某点的坐标(pos[0], pos[1])和正切值,弧度值=atan2(tan[1], tan[0]),可求得角度 =弧度值*180/Math.PI;   

-------------------

三、Canvas 对绘制的辅助——范围裁切和几何变换

    |--范围裁切:clipRect() 和 clipPath()。裁切方法之后的绘制代码,都会被限制在裁切范围内。clipPath()时,记得要加上 Canvas.save() 和 Canvas.restore() 来及时恢复绘制范围

    |--几何变换:有三种

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

            |--Canvas.translate(float dx, float dy) 平移

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

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

            |-- skew(float sx, float sy) 错切

        |--使用 Matrix 来做常见和不常见的二维变换。

                1.创建 Matrix 对象;

                2.调用 Matrix 的 pre/postTranslate/Rotate/Scale/Skew() 方法来设置几何变换;

                3.使用 Canvas.setMatrix() 或 Canvas.concat() 来把几何变换应用到 Canvas。

            |--常见变换:

                |--Canvas.setMatrix(matrix):用 Matrix 直接替换 Canvas 当前的变换矩阵,即抛弃

                Canvas 当前的变换,改用 Matrix 的变换(注:根据下面评论里以及我在微信公

                众号中收到的反馈,不同的系统中  setMatrix(matrix) 的行为可能不一致,所以

                还是尽量用 concat(matrix) 吧);

                |--Canvas.concat(matrix):用 Canvas 当前的变换矩阵和 Matrix 相乘,即基于 Canvas 当

                前的变换,叠加上 Matrix 中的变换

            |--自定义变换:使用setPolyToPoly() 方法 用点对点映射的方式设置变换

                    把指定的点移动到给出的位置,从而发生形变,采集点的个数不超过4个

        |-- 使用 Camera 来做三维变换,Camera 的三维变换有三类:旋转、平移、移动相机

            |--Camera.rotate*() 三维旋转

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

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

四、使用不同的绘制方法来控制绘制顺序

绘制内容相互遮盖的情况是很普遍的,后绘制的模块的覆盖前面的绘制

绘制顺序:背景色(设置backgroud)、主体(ondraw())、子view(dispatchDraw())、滑动边缘渐变和滑动条(onDrawForeground())、前景色(onDrawForeground())

draw() 指定绘制方法调用

onDraw() 主体

dispatchDraw() 子view

ondrawForeground() 前景

另有些ViewGroup会,会启动绘制优化,导致onDraw不执行,直接绘制子view

--------------------------------------------------

属性动画 Property Animation,两种方式:

    |--自带的animate  使用方式

        |-- view.animate.xxx

    |--ObjectAnimator  ,使用方式

        |--1.如果是自定义控件,需要添加 setter / getter 方法;

        |--2.用 ObjectAnimator.ofXXX() 创建 ObjectAnimator 对象;

        |--3.用 start() 方法执行动画。

    |--ValueAnimator 最基本的轮子。很多时候,你用不到它,只是在你使用一些第三方库的控件,而你想要做动画的属性却没有 setter / getter 方法的时候,会需要用到它

通用的属性:

    |--setDuration(int duration) 设置动画时长 单位是毫秒。

    |--加速器

        |--AccelerateDecelerateInterpolator 先加速再减速。这是默认的 Interpolator

        |--LinearInterpolator 匀速。

        |--AccelerateInterpolator 持续加速。

        |--DecelerateInterpolator 持续减速直到 0

        |--AnticipateInterpolator 先回拉一下再进行正常动画轨迹。效果看起来有点像投掷物体或跳跃等动作前的蓄力

        |--OvershootInterpolator 动画会超过目标值一些,然后再弹回来

        |--AnticipateOvershootInterpolator 上面这两个的结合版:开始前回拉,最后超过一些然后回弹。

        |--BounceInterpolator 在目标值处弹跳,有点像玻璃球掉在地板上的效果。

        |--CycleInterpolator 这个也是一个正弦 / 余弦曲线,不过它和 AccelerateDecelerateInterpolator 的区别是,它可以自定义曲线的周期,所以动画可以不到终点就结束,也可以到达终点后回弹,回弹的次数由曲线的周期决定,曲线的周期由 CycleInterpolator() 构造方法的参数决定

        |--PathInterpolator 自定义动画完成度 / 时间完成度曲线。用这个 Interpolator 你可以定制出任何你想要的速度模型。定制的方式是使用一个 Path 对象来绘制出你要的动画完成度 / 时间完成度曲线

        Android 5.0 (API 21)引入了三个新的 Interpolator 模型,并把它们加入了 support v4 包中。这三个新的 Interpolator 每个都和之前的某个已有的 Interpolator 规则相似,只有略微的区别

        |--FastOutLinearInInterpolator 加速运动,只不过 FastOutLinearInInterpolator 的曲线公式是用的贝塞尔曲线,而 AccelerateInterpolator 用的是指数曲线。具体来说,它俩最主要的区别是  FastOutLinearInInterpolator 的初始阶段加速度比 AccelerateInterpolator 要快一些

        |--FastOutSlowInInterpolator 先加速再减速 FastOutSlowInInterpolator 用的是贝塞尔曲线,AccelerateDecelerateInterpolator 用的是正弦 / 余弦曲线。具体来讲, FastOutSlowInInterpolator 的前期加速度要快得多

        |--LinearOutSlowInInterpolator 持续减速。它和 DecelerateInterpolator 比起来,同为减速曲线,主要区别在于 LinearOutSlowInInterpolator 的初始速度更高

    |--监听器  由于ObjectAnimator 支持使用 pause() 方法暂停,所以它还多了一个 addPauseListener() /  removePauseListener() 的支持;而 ViewPropertyAnimator 则独有 withStartAction() 和 withEndAction() 方法,可以设置一次性的动画开始或结束的监听

        |--ViewPropertyAnimator.setListener() / ObjectAnimator.addListener(),四个回调方法

            |--onAnimationStart 当动画开始执行时方法被调用

            |--onAnimationEnd 当动画结束时,这个方法被调用

            |--onAnimationCancel 当动画被通过 cancel() 方法取消时,这个方法被调用。需要说明一下的是,就算动画被取消,onAnimationEnd() 也会被调用。

            |--onAnimationRepeat 当动画通过 setRepeatMode() / setRepeatCount() 或 repeat() 方法重复执行时,这个方法被调用。由于 ViewPropertyAnimator 不支持重复,所以这个方法对 ViewPropertyAnimator 相当于无效。

        |--ViewPropertyAnimator.setUpdateListener() / ObjectAnimator.addUpdateListener(),一个回调方法

            |--onAnimationUpdate(ValueAnimator animation) 当动画的属性更新时(不严谨的说,即每过 10 毫秒,动画的完成度更新时),这个方法被调用

---------------------------

针对特殊类型的属性来做属性动画,它可以让你「做到本来做不到的动画] 

    |--TypeEvaluator

        |--ArgbEvaluator 颜色的

        |--自定义 Evaluator 重新evaluate方法

        |--ofObject() 借助于 TypeEvaluator,属性动画就可以通过 ofObject() 来对不限定类型的属性做动画

            |--1.为目标属性写一个自定义的 TypeEvaluator

            |--2.使用 ofObject() 来创建 Animator,并把自定义的 TypeEvaluator 作为参数填入

        |--ofMultiInt()/ofMultiFloat() 不常用

针对复杂的属性关系来做属性动画,它可以让你「能做到的动画做起来更简单」

    |--使用 PropertyValuesHolder 来对多个属性同时做动画

    |--使用 AnimatorSet 来同时管理调配多个动画;按照要求的顺序来工作

    |--PropertyValuesHolder 的进阶使用:使用 PropertyValuesHolder.ofKeyframe() 来把一个属性拆分成多段,执行更加精细的属性动画。

-------------------------自定义view-布局

测量尺寸measure-->调用onMeasure()方法,方法里做的事不一样

    |--子view进行测量

    |--遍历所有子view,调用子view的measure,调用onMeasure

        |--子view进行测量

布局 layout

    |--子view,onLayout()空实现

    |--viewGroup,调用子view的layout方法 

重写onMeasure,计算宽高尺寸,然后setMeasuredDimension()保存

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

推荐阅读更多精彩内容

  • 系列文章之 Android中自定义View(一)系列文章之 Android中自定义View(二)系列文章之 And...
    YoungerDev阅读 2,152评论 0 4
  • 系列文章之 Android中自定义View(一)系列文章之 Android中自定义View(二)系列文章之 And...
    YoungerDev阅读 4,367评论 3 11
  • 1.UML概念:Unified Modeling Language(UML),又称统一建模语言或标准建模语言。是一...
    心底碎片阅读 325评论 0 1
  • 第二十一节 灭周(二) 秦庄襄王在酝酿什么,此时可以说只有赵国准确判断出来了。而故事的当事国——东周,也已经感受到...
    狼2639阅读 129评论 0 1
  • 前言: 大概12月底的时候我接了的分期乐流量产品上线需求。这是我第一个相对完整,并且独立操作的产品。整个做下来,收...
    乔阳阅读 1,012评论 0 0