2023年7月15日发(作者:)
⾃定义View-基础先扯⼀点题外话,就现在的 Android 市场来说,可以说是不容乐观的,只不过是相对的,Android 的坑位有限,⼈⼜相对⽐较多,加上资本寒冬,像我⼀样的菜鸟是最为令⼈担忧的。那么能怎么办呢?只有进阶到⾼级才⾏,才能混的下去,⾼级 Android ⼯程师的市场还是很⼴阔的,所以⼀起努⼒吧,少年们!想进阶到⾼级,⾃定义 View 这部分是必须要攻克的,这篇也算是开篇,主要翻译⼀下官⽅⽂档,在补⼀下基础部分,像坐标系,位置获取⽅式,颜⾊使⽤⽅式等,下⾯就动起来吧!main1. View 简介1.1 View 介绍public class View
extends Object implements ck, ck, AccessibilityEventSourceView 继承 Object,并实现了⼀些接⼝,现在对这些有⼀个印象就⾏,等具体分析源码时,再来看。This class represents the basic building block for user interface components. A View occupies arectangular area on the screen and is responsible for drawing and event handling. View is thebase class for widgets, which are used to create interactive UI components (buttons, text fields, etc.). TheViewGroup subclass is the base class for layouts, which are invisible containers > that hold other Views (orother ViewGroups) and define their layout 的定义:View 是⽤户交互组件的基本构建块。View 占据屏幕上的⼀个矩形区域,并且负责绘制和处理事件。View 是组件的基类,这些组件⽤来产⽣交互功能(如按钮。⽂本框等)。ViewGroup 是 View 的⼦类,它是⽤来管理布局的基类,它是不可见的,⽤来装载其他的⼦ View或者其他的 ViewGroup,并且可以设置布局的属性。通过这段话对 View 有了⼀个初步的认识,平时我们在 XML 中定义的布局就是⼀个 ViewGroup,界⾯的顶级 View,也是⼀个ViewGroup(DecorView 是⼀个 FrameLayout)Using Views窗⼝中的所有视图都排列在 View 的树结构中,可以通过代码或通过在⼀个或多个XML布局⽂件中指定视图来添加 View 树结构中。 有许多专门的视图⼦类充当控件或能够显⽰⽂本,图像或其他内容。Once you have created a tree of views, there are typically a few types of common operations you may wish to perform:⼀旦创建了⼀个树结构,会有⼀系列的通⽤操作,主要包括以下⼏点:Set properties(设置属性,如在给 TextView 设置⽂本)Set focus(设置焦点)Set up listeners(设置监听,如给 Button 设置 kListener)Set visibility(设置 View 是否可见 setVisibility(int))Implementing a Custom View(⾃定义 View)官⽅⽂档中这部分实际上就是给了⼀个⼤体的说明个,并不是详细的⼀个⾃定义 View 的教程。To implement a custom view, you will usually begin by providing overrides for some of the standardmethods that the framework calls on all views. You do not need to override all of these methods. In fact,you can start by just overriding onDraw().为了实现⼀个⼦⾃定义 View,你将要重写 View 所调⽤的框架中的标准⽅法,不需要全部重写,事实上,仅仅调⽤ onDraw ⽅法就⾏。也就是说,通过调⽤ onDraw ⽅法就可以完成简单的⾃定义 View,对于复杂的,需要我们重写 View 的⽐较重要的三个⽅法,即 onMeasure、onLayout、onDraw。官⽅给出了⼀个表格,这个表格是对 View 中主要的⽅法进⾏了⼀个归类,可以作为⾃定义 View 操作的⼀个主要参考和指导。这⾥就不翻译了,相信⾃⼰可以看明⽩。image1.2 View 属性这⾥的属性并不是 View 可以设置属性,⽽是官⽅⽂档介绍的 View 相关的⼀些东西,下⾯⼀起来看下。建议对这部分通读⼀下,对 View 会有⼀个整体的感知。IDs视图可能会有⼀个与它相关的 integer 类型的 id,即我们使⽤布局时定义的那个 id。这些 id 是在布局 XML ⽂件中设置的,通过这些 id 能够找到树结构中的 对应的 View,使⽤的⽅式也是我们很熟悉的:在布局⽂件中定义⼀个 Button,设置 id,然后就可以通过 id 获取这个 Button。Button myButton = findViewById(_button);这些 id 并不⼀定是唯⼀的,但是在⼀个树结构中最好还是保持唯⼀性,因为当你找⼀个⽂件时,会很⽅便,否当你点击查找时,可能会弹出很多⽂件,导致不好筛选。Position⼀个 View 的⼏何形状是矩形的。⼀个 View 有位置表⽰,通常是通过左上形式的坐标系,也就是 X 轴右向是正的,Y 轴向下是正的,并且有两个尺⼨,宽和⾼。位置信息和尺⼨信息都是以像素为单位的。获取位置信息是通过 getLeft() 和 getTop() 这两个⽅法。getLeft() ⽅法视图的 X 坐标,getTop() ⽅法返回的是 Y 坐标。这两个坐标返回的值都是相对⼀⽗布局的。例如,getLeft() 返回值是 20,意味着这个 view 位于它的直接⽗布局左边缘的右侧 20 个像素的位置。另外,⼀些很⽅便的⽅法可以⽤来计算位置信息,可以避免⾃⼰计算的过程。getRight() 和 getBottom()。getRight() = getLeft() + getWidth()。getBottom() = getTop() + getHeight()。这些在后⾯介绍坐标系时会详细介绍下。Size, padding and margins⼀个 view 的尺⼨⼤⼩表达为宽和⾼,它通常拥有两对宽和⾼。第⼀对:测量的宽和⾼,这些尺⼨定义⼀个 view 在⽗布局中占据的位置有多⼤,通过调⽤ getMeasuredWidth() 和 getMeasuredHeight()这两个⽅法可以获取。第⼆对:就是简单宽和⾼,有时也叫绘制的宽和⾼。这些尺⼨定义 view 早屏幕上实际的尺⼨,时机是在绘制时,在 layout 之后。这两个尺⼨和测量的宽和⾼可能是⼀样的,通过调⽤ getWidth() 和 getHeight() 这两个⽅法获取。⼀个 view 可以定义 padding,但是它不提供 margins。margins 是 ViewGroup 提供的布局限制的。Layout布局的过程包括两个:测量和布局。测量的过程是在 measure(int, int) ⽅法中实现的,并且是⾃上向下遍历树结构的过程。每个 view 都会把尺⼨规则在这个递归过程中传递出去。在测量结束之后,每个 view 都会储存它的测量值。第⼆个过程发⽣在 layout(int, int, int, int) 这个⽅法中,并且它和测量过程类似。在这个过程中,每个⽗布局都会通过在测量过程中得到的尺⼨来给⼦布局分配位置。但完成测量过程时,即在 measure() ⽅法返回时,它的 getMeasuredWidth() 和 getMeasuredHeight() ⼀定是能够获取到值的,它的⼦布局也是⼀样。 ⼀个⼦ view 的测量宽度和测量⾼度必须和⽗布局的限制保持⼀致,例如⽗布局设置了⼤⼩,⼦布局不能的⼤⼩不能超过⽗布局的⼤⼩。这可以保证在测量结束时,所有的⽗布局能够接受⼦布局的测量尺⼨。⼀个⽗布局可能测量多次。例如⽗布局可能测量每个未指定尺⼨的⼦布局,找到他们需要设置为多⼤,然后再次调⽤ measure() ⽅法来判断这些⼦布局的总⼤⼩是否过⼤还是过⼩。测量过程中使⽤两个类来表述尺⼨,eSpec ⽤来告诉⽗布局如何测量和布局。LayoutParams 类⽤来描述 view 的宽和⾼有多⼤,对于每个 view,可以指定下⾯中⼀种规格:(1)具体的数值(2)MATCH_PARENT,表⽰ view 的⼤⼩和⽗布局⼤⼩相同(需要减去padding的⼤⼩)(3)WRAP_CONTENT,表⽰刚好包含内容的⼤⼩(需要加上padding的⼤⼩)不同的 ViewGroup 有不同的 LayoutParams 的⼦类。例如,AbsoluuteLayout 布局有⾃⼰的 LayoutParams,包含 X 和 Y 的值。测量规格⽤来从⽗布局传到⼦布局当中,有下⾯三种规格:(1)UNSPECIFIED: ⽤于⽗布局来决定⼦布局的尺⼨的规格,例如,LinearLayout 可能调⽤ measure(),测量它的⼦布局,⼦布局的⾼度测量规格是 UNSPECIFIED,宽度是准确的值,为240,通过这两个规格来告诉⼦布局的宽度和⾼度,主要⾼度部分,⽗布局会计算好,帮助⼦布局确定尺⼨。(2)EXACTLY: ⽤于⽗布局来给⼦布局设置⼀个准确的尺⼨值。⼦布局使⽤这个尺⼨,并且保证所有的控件都在这个尺⼨内。(3)AT_MOST: ⽤于⽗布局给⼦布局设置⼀个最⼤的约束。⼦布局必须保证它和所有的控件都在这个尺⼨范围内To initiate a layout, call requestLayout(). This method is typically called by a view on itself when it believes that is can no longerfit within its current bounds.为了初始化⼀个布局,可以通过调⽤ requestLayout() ⽅法。这个⽅法通常被⼀个 view ⾃⼰调⽤,调⽤的时机是它不在它当前的区域时,换句话说,也就是 view 的位置发⽣变化时,可以通过这个⽅法进⾏重新布局。Drawing绘制的过程,通过遍历树结构,并且记录需要更新的view的绘制命令来进⾏的,在这之后,整个树的绘图命令被发送到屏幕,剪切到新损坏的区域,意思是就是视图会被显⽰到屏幕上待显⽰的区域。这个树结构记录并按顺序绘制,⽗布局先绘制,⼦布局后绘制,⼦控件绘制的顺序也是按照他们在树结构中出现的先后顺序来。如果为⼀个 view设置了背景(Drawable),那么 这个 View 会先绘制背景,然后在绘制本⾝。⼦ view 的绘制顺序可以进⾏⾃定义,通过 setZ(float) ⽅法设置Z 值。强制⼀个 view 重绘,可以通过调⽤ invalidate() ⽅法。Event Handling and Threading⼀个 View 的基本循环如下:⼀个事件传递过来,并分发给合适的 view,这个 view 处理这个事件并回调相应的监听⽅法。在处理事件的过程当中,视图的区域(尺⼨)可能发⽣改变,这时视图会调⽤ requestLayout() 这个⽅法。类似的,如果在处理事件的过程中,视图的外观发⽣改变,这时会调⽤ invalidate() ⽅法。⽆论是 requestLayout() 还是 invalidate() 被调⽤,系统框架会合适的处理视图结构树的测量,布局,绘制。注意:整个树结构是单线程的,当有⽅法作⽤在 view 上时,必须在 UI 线程(主线程)上进⾏。如果想在其他线程⼯作并且其他线程想要更新视图的状态,可以使使⽤ Handler。Focus Handling系统框架将会处理⽤户输⼊时的焦点。包括当 view 被移除或者隐藏时,⼀个新的 view 变得可见时。通过 isFocusable() ⽅法可以知道视图是否处于获取焦点状态。为了改变⼀个 view 能够获取焦点,通过调⽤ setFocusable(boolean)⽅法。当处于触摸状态的view,可以通过调⽤isFocusableInTouchMode() ⽅法判断是否处于获取焦点状态,可以通过调⽤ setFocusableInTouchMode(boolean) ⽅法来改变焦点状态。拥有焦点的时刻是通过⼀个算法,这个算法来找到最近的⼀个在指定⽅向的控件。在⼀些情况下,默认的算法并不能够匹配开发者的需求。在这种情况下,可以提供⼀个准确的复写来完成,在XML ⽂件中定义属性。包括以下⼏个属性:nextFocusDownnextFocusLeftnextFocusRightnextFocusUp当需要使⼀个 view 获取焦点是,可以通过调⽤ requestFocus() ⽅法。Touch Mode当⼀个⽤户控制⼀个交互动作时,通过像 D-pad 这样的⽅向键,有需要给定这些控件以焦点,如 Button 按钮,所⽤⽤户能够看到什么可以输⼊。如果设备有触摸功能,⽤户可以通过触摸开始进⾏交互操作,就没有必要时刻保持⾼亮,或者给指定的 view 保持焦点,这种⽅式就是所谓的触摸模式。对于⼀个触摸设备,⼀旦⽤户触摸屏幕,设备将进⼊触摸状态,从这个时刻起,只有 哪些处于 isFocusableInTouchMode() ⽅法返回 true的view才是获取焦点状态的,如⽂本编辑框。其他的 view 可以触摸,如 button,但是不占有焦点,它们只会触发监听事件。任意时间,⽤户点击了⽅向键,如 D-pad,这个设备键退出触摸模式,并找到⼀个 view,并会获取焦点,所以⽤户能够再次和⽤户交互⼊⼝进⾏交互交互动作,不需要再次触摸屏幕。触摸状态由 Activity 来维护,通过调⽤ isInTouchMode() ⽅法来判断设备当前是否处于触摸状态。Scrolling框架为 view 提供基本的滑动操作,可以滑动内部的内容。包括记录 X 和 Y 向 的滑动,可以查看 scrollBy(int, int), scrTags不像 ID, tags 不是⽤于识别 view 的,tags ⽤于标记和这个 view 相关的额外信息的。常⽤于存储和这个view相关的信息,它⽐使⽤单独的结构来存储更加⽅便。tags 可以在 XML ⽂件中使⽤字符序列定义⼀个单独的 tag,标签是 android:tag,或者使⽤多个标签,以
PropertiesView 类暴露⼀个 ALPHA 属性(透明度属性),同时也有⼀些平移旋转属性,如 TRANSLATION_X, TRANSLATION_Y。这些属性在类中都有 setter/getter ⽅法可以设置。这些属性可以设置和 view 渲染属性相关的状态。这些属性也可以⽤于动画相关的设置,可以在动画部分查看详细信息。Animation从 Android 3.0开始,给 view 添加动画的最好⽅式就是使⽤ ion 包下的 APIs.这些动画基类改变 view 的实际属性,如 透明度和 X轴 平移量。和 3.0之前的动画基类相⽐,仅仅是改变 view 在屏幕上的显⽰效果,并没有改变 view 的属性。特别的,通过ViewPropertyAnimator 类使得这些 view 的属性使⽤起来更加简单和有效。相对的,你可以使⽤ 3.0之前的动画类来进⾏ view 的渲染。可以将⼀个动画附着到 view 上,使⽤ setAnimation(Animation) 或者startAnimation(Animation) ⽅法。这个动画可以缩放,旋转,平移和透明度,并能够随时间改变 view 属性。如果这个动画被附着到⼀个 view上,并且 view 上包含⼦类,这个动画会影响这个节点之下的所有⼦类。当开启动画之后,框架会开始重绘这个 view,直到 动画结束Security⼀个应⽤有必要确认⼀个动作,告知⽤户⽤户的内容,如保证权限请求,点击⼴告的操作等等。不幸的是,有很多应⽤尝试引导⽤户完成这些⼀些动作,并且在没有意识的情况下,通过在这个 view 下隐藏⽬的。作为补救措施,框架提供⼀些触摸的过滤机制,来提⾼ view 的安全性,来保证⼀些敏感的访问操作。为了能够进⾏触摸的过滤操作,通过调⽤ setFilterTouchesWhenObscured(boolean) ⽅法,或者设置android:filterTouchesWhenObscured 属性为 true,当可以进⾏过滤时,框架将会摒弃触摸操作,当view 的窗⼝被另外⼀个 窗⼝占据时。这样,view 将不会接受触接收触摸操作,当⼀个 toast ,dialog,或者其他的窗⼝出想在这个 view 的窗⼝的上⾯时。为了更好地控制安全性的问题,考虑重写 onFilterTouchEventForSecurity(MotionEvent) ⽅法来实现你的安全策略,可以查看_WINDOW_IS_OBSCURED 这个属性。这个⽅法必须在开始创建这个 UI 元素的线程中调⽤,通常就是应⽤的主线程。2. View 分类上⾯把官⽅对 view 的描述做了⼀遍翻译,对 view 有了⼀个全⾯的了解,下⾯看下 view 的分类。classes上⾯⼀张图,就是 view 的树形结构,可以有很多层,然后通过⼀层⼀层遍历,最后绘制出来。当然,嵌套层次过多时,就会出现过度绘制,会变的卡顿,所以在设计布局时,避免多层嵌套使⽤。 view 的三个主要过程,measure、layout、draw 都是通过这个树结构从根节点⼀层⼀层递归遍历完成的。另外,根布局就是 DecorView,是 FrameLayout。3. View 的位置描述3.1 坐标系屏幕坐标系和数学坐标系的区别:由于移动设备⼀般定义屏幕左上⾓为坐标原点,向右为x轴增⼤⽅向,向下为y轴增⼤⽅向, 所以在⼿机屏幕上的坐标系与数学中常见的坐标系是稍微有点差别的,详情如下:cordinate实际屏幕上的默认坐标系如下: PS: 假设其中棕⾊部分为⼿机屏幕view_phone3.2 位置获取(1)4个顶点的位置描述分别由4个值决定:(请记住:View的位置是相对于⽗控件⽽⾔的)positionTop:⼦View上边界到⽗view上边界的距离Left:⼦View左边界到⽗view左边界的距离Bottom:⼦View下边距到⽗View上边界的距离Right:⼦View右边界到⽗view左边界的距离(2)获取4个顶点可以通过⼀下4个函数: getTop(); //获取⼦View左上⾓距⽗View顶部的距离
getLeft(); //获取⼦View左上⾓距⽗View左侧的距离
getBottom(); //获取⼦View右下⾓距⽗View顶部的距离
getRight(); //获取⼦View右下⾓距⽗View左侧的距离
(3) MotionEvent中 getXX 和 getRaw 的区别: (); //触摸点相对于其所在组件坐标系的坐标 (); X(); //触摸点相对于屏幕默认坐标系的坐标 Y();different3.3 Android 中的⾓度与弧度为了精确描述⼀个⾓的⼤⼩引⼊了⾓度与弧度的概念。名称⾓度弧度如图:定义两条射线从圆⼼向圆周射出,形成⼀个夹⾓和夹⾓正对的⼀段弧。当这段弧长正好等于圆周长的360分之⼀时,两条射线的夹⾓的⼤⼩为1度.两条射线从圆⼼向圆周射出,形成⼀个夹⾓和夹⾓正对的⼀段弧。当这段弧长正好等于圆的半径时,两条射线的夹⾓⼤⼩为1弧度.imageimage转换公式:rad 是弧度, deg 是⾓度公式rad = deg x π / 180deg = rad x 180 / π维基百科的公式:例⼦2π = 360 x π / 180360 = 2π x 180 / πrad 是弧度, deg 是⾓度image在常见的数学坐标系中⾓度增⼤⽅向为逆时针,在默认的屏幕坐标系中⾓度增⼤⽅向为顺时针。image4 Android 中 color4.1 简介安卓⽀持的颜⾊模式:颜⾊模式ARGB8888ARGB4444RGB565Alpha8四通道⾼精度(32位)四通道低精度(16位)屏幕默认模式(16位)仅有透明通道(8位)备注PS:其中字母表⽰通道类型,数值表⽰该类型⽤多少位⼆进制来描述。如ARGB8888则表⽰有四个通道(ARGB),每个对应的通道均⽤8位来描述。注意:我们常⽤的是ARGB8888和ARGB4444,⽽在所有的安卓设备屏幕上默认的模式都是RGB565,请留意这⼀点。以ARGB8888为例介绍颜⾊定义:类型A(Alpha)R(Red)G(Green)B(Blue)解释透明度红⾊绿⾊蓝⾊0(0x00)透明⽆⾊⽆⾊⽆⾊255(0xff)不透明红⾊绿⾊蓝⾊其中 A R G B 的取值范围均为0255(即16进制的0x000xff)A 从0x00到0xff表⽰从透明到不透明。RGB 从0x00到0xff表⽰颜⾊从浅到深。当RGB全取最⼩值(0或0x000000)时颜⾊为⿊⾊,全取最⼤值(255或0xffffff)时颜⾊为⽩⾊4.2 ⼏种创建或使⽤颜⾊的⽅式(1)java中定义颜⾊ int color = ; //灰⾊由于Color类提供的颜⾊仅为有限的⼏个,通常还是⽤ARGB值进⾏表⽰。 int color = (127, 255, 0, 0); //半透明红⾊
int color = 0xaaff0000; //带有透明度的红⾊(2)在xml⽂件中定义颜⾊在/res/values/ ⽂件中如下定义:
#ff0000 //⾼精度 - 不带透明通道红⾊ #aaff0000 //⾼精度 - 带透明通道红⾊(3)在java⽂件中引⽤xml中定义的颜⾊: int color = getResources().getColor(r);
int color = getColor(r); //API 23 及以上⽀持该⽅法(4)在xml⽂件(layout或style)中引⽤或者创建颜⾊ android:background="@color/red" //引⽤在/res/values/ 中定义的颜⾊
android:background="#ff0000" //创建并使⽤颜⾊4.3 颜⾊拾取⼯具颜⾊都是⽤RGB值定义的,⽽我们⼀般是⽆法直观的知道⾃⼰需要颜⾊的值,需要借⽤取⾊⼯具直接从图⽚或者其他地⽅获取颜⾊的RGB值。(1)屏幕取⾊⼯具取⾊调⾊⼯具,可以从屏幕取⾊或者使⽤调⾊板调制颜⾊,⾮常⼩⽽精简。点击这⾥获取屏幕取⾊⼯具(2)Picpick功能更加强⼤的⼯具:PicPick。PicPick具备了截取全屏、活动窗⼝、指定区域、固定区域、⼿绘区域功能,⽀持滚动截屏,屏幕取⾊,⽀持双显⽰器,具备⽩板、屏幕标尺、直⾓座标或极座标显⽰与测量,具备强⼤的图像编辑和标注功能。点击这⾥获取PicPick4.4 颜⾊混合模式(Alpha通道相关)通过前⾯介绍我们知道颜⾊⼀般都是四个通道(ARGB)的,其中(RGB)控制的是颜⾊,⽽A(Alpha)控制的是透明度。因为我们的显⽰屏是没法透明的,因此最终显⽰在屏幕上的颜⾊⾥可以认为没有Alpha通道。Alpha通道主要在两个图像混合的时候⽣效。默认情况下,当⼀个颜⾊绘制到Canvas上时的混合模式是这样计算的:(RGB通道) 最终颜⾊ = 绘制的颜⾊ + (1 - 绘制颜⾊的透明度) × Canvas上的原有颜⾊。注意:1.这⾥我们⼀般把每个通道的取值从0(0x00)到255(0xff)映射到0到1的浮点数表⽰。2.这⾥等式右边的“绘制的颜⾊"、“Canvas上的原有颜⾊”都是经过预乘了⾃⼰的Alpha通道的值。如绘制颜⾊:0x88ffffff,那么参与运算时的每个颜⾊通道的值不是1.0,⽽是(1.0 * 0.5333 = 0.5333)。 (其中0.5333 = 0x88/0xff)使⽤这种⽅式的混合,就会造成后绘制的内容以半透明的⽅式叠在上⾯的视觉效果。其实还可以有不同的混合模式供我们选择,⽤rmode,指定不同的。下表是各个PorterDuff模式的混合计算公式:(D指原本在Canvas上的内容dst,S指绘制输⼊的内容src,a指alpha通道,c指RGB各个通道)混合模式ADDCLEARDARKENDSTDST_ATOPDST_INDST_OUTDST_OVERLIGHTENMULTIPLYSCREENSRCSRC_ATOPSRC_INSaturate(S + D)[0, 0]计算公式[Sa + Da - SaDa, Sc(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)][Da, Dc][Sa, Sa * Dc + Sc * (1 - Da)][Sa * Da, Sa * Dc][Da * (1 - Sa), Dc * (1 - Sa)][Sa + (1 - Sa)Da, Rc = Dc + (1 - Da)Sc][Sa + Da - SaDa, Sc(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)][Sa * Da, Sc * Dc][Sa + Da - Sa * Da, Sc + Dc - Sc * Dc][Sa, Sc][Da, Sc * Da + (1 - Sa) * Dc][Sa * Da, Sc * Da]SRC_OUT混合模式SRC_OVERXOR[Sa * (1 - Da), Sc * (1 - Da)][Sa + (1 - Sa)Da, Rc = Sc + (1 - Sa)Dc]计算公式[Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc]⽤⽰例图来查看使⽤不同模式时的混合效果如下(src表⽰输⼊的图,dst表⽰原Canvas上的内容):image4.4 颜⾊相关其他 (也⽐较重要,所以拷贝过来,为后⾯使⽤ Pain 做铺垫)(1) 填充颜⾊之前说过*指定了绘制的区域。⽽区域⾥的填充颜⾊是由Paint来指定的。or指定纯⾊。der可指定:BitmapShader, LinearGradient, RadialGradient, SweepGradient, ComposeShader。BitmapShader:图⽚填充。LinearGradient, RadialGradient, SweepGradient:渐变填充。ComposeShader:叠加前⾯的某两种。可选择PorterDuff混合模式。如果既调⽤了setColor,⼜调⽤了setShader,则setShader⽣效。如果同时⽤setColor或setAlpha设置了透明度,则透明度也会⽣效。(会和Shader的透明度叠加)如果使⽤drawBitmap输⼊⼀个只有alpha的图⽚(可⽤tAlpha⽅法获得),则会以alpha图⽚为mask,绘制出shader/color的颜⾊。(2)ColorFilter通过ColorFilter可以对⼀次绘制的所有像素做⼀个通⽤处理。orFilter: LightingColorFilter, PorterDuffColorFilter, ColorMatrixColorFilter。这可以整体上改变这⼀次draw的内容,⽐如让颜⾊更暗、更亮等。这⾥重点介绍下ColorMatrixColorFilter。ColorMatrix是4x5矩阵,定义其每个元素如下:{ a, b, c, d, e,f, g, h, i, j,k, l, m, n, o,p, q, r, s, t }则ColorMatrix的最终运算⽅式如下:R' = aR + bG + cB + dA + e;G' = fR + gG + hB + iA + j;B' = kR + lG + mB + nA + o;A' = pR + qG + rB + sA + t;(3)绘图API架构整个绘制流⽔线⼤概如下:(我们能定义的部分⽤蓝⾊表⽰)draw_api考虑动画实现的时候⼀般从两个⾓度来思考:宏观⾓度:有⼏个变化量,分别是什么。动画从开始到结束的流程。微观⾓度:从某⼀帧上去想,在变化量为某个数值时的图像,该怎么绘制。把这两者分开去想,就会⽐较清晰。参考AndroidNote⾃定义View基础 - 最易懂的⾃定义View原理系列(1)ViewViewGroup【安卓图形动画】
发布者:admin,转转请注明出处:http://www.yc00.com/xiaochengxu/1689425548a246196.html
评论列表(0条)