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()保存