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

2019-10-16  本文已影响0人  海阔天空cqk

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

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

上一篇下一篇

猜你喜欢

热点阅读