Android View 系统 3
来源:互联网 发布:hr工资计算软件 编辑:程序博客网 时间:2024/06/06 15:03
View的显示
每一个View的显示都要经历三个过程:测量(Measure)、布局(Layout)、绘制(Draw)。这三个过程的执行时机就是由前面提到的ViewRootImpl
来控制的,同时每个继承自View
的子类都可以继承下面三个方法来重写这三个流程,实现自己的显示内容:
class MyView extends View { ... @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {...} @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) {...} @Override protected void onDraw(Canvas canvas) {...}}
View树的第一次刷新
前面提到WindowManagerGlobal.addView()
的实现里为每个View树创建了一个ViewRootImpl
,并且最后调用了ViewRootImpl.setView()
将根View以及窗口配置参数传递给了ViewRootImpl
,而ViewRootImpl.setView()
的实现里就触发了View树的第一次刷新:
frameworks/base/core/java/android/view/ViewRootImpl.javapublic void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { ... // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system. requestLayout(); ... res = mWindowSession.addToDisplay(...; ...}public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); }}void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); }}void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); ... }}final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); }}final TraversalRunnable mTraversalRunnable = new TraversalRunnable();void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; ... performTraversals(); ... }}
setView()
里会调用requestLayout()
请求一次窗口布局,同时还调用了mWindowSession.addToDisplay
请求WindowManagerService
创建底层管理的窗口WindowState
。requestLayout()
里会先通过checkThread()
确认执行刷新的线程与创建ViewRootImpl
的线程一致。从addView()
分析的逻辑,这里肯定是一个线程,一般就是应用的主线程。后面刷新View的时候需要注意必须从主线程刷新View。requestLayout()
里还调用了scheduleTraversals()
,scheduleTraversals()
里主要通过Choreographer
定时了一个mTraversalRunnable
任务。Choreographer
是基于VSNC实现的一个控制类,VSNC的主要原理是每隔一个固定的时间(一般为16ms,保证每秒60帧的刷新率)设置一个高优先级中断,在中断的时候处理各种有序任务,这样所有的任务就可以按照固定的频率进行处理。VSNC可以用来进行控制界面刷新、动画、输入事件处理,使用VSNC可以使界面显示更加平滑、流畅。Choreographer.postCallback()
就是将一个Runnable
任务添加到有序任务队列里,当下次VSNC中断到来时执行任务队列里的所有任务,在这里是TraversalRunnable
。ViewRootImpl
将每次的刷新任务封装到TraversalRunnable里,每次刷新任务执行的时候调用一次doTraversal()
,并在doTraversal()
里调用performTraversals()
执行真正的组织刷新操作。
frameworks/base/core/java/android/view/ViewRootImpl.javaprivate void performTraversals() { ... if (mFirst || ...) { ... //第一次刷新请求窗口布局 relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); ... } ... //执行测量操作 performMeasure(...); ... //执行布局操作 performLayout(...); ... //执行绘画操作 performDraw(); }private int relayoutWindow(...) throws RemoteException { ... //通过WindowSession将请求传递给WindowManagerService int relayoutResult = mWindowSession.relayout(...);}
relayoutWindow
如果是第一次请求刷新,会先通过relayoutWindow()
请求WindowManagerService
为窗口创建Surface,后面该View树所有的内容都会绘制在这个Surface上。performMeasure
从根View开始测量View树中每个View的大小。performLayout
对View树进行布局,确认父View里每个子View的位置。performDraw
绘画View树里的所有View。
View 的测量
View 的测量过程就是计算View的显示大小的过程,ViewRootImpl.performMeasure()
就是从根View开始,对View树中的每个View进行测量。
设置大小
在布局文件中每个View可以通过layout_width
和layout_height
两个属性指定View的大小:
<Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="这是match_parent宽度" /><Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="这是wrap_content宽度" /><Button android:layout_width="90dp" android:layout_height="wrap_content" android:text="这是90dp宽度" />
layout_width
和layout_height
两个属性的值可以为3种:
- match_parent/fill_parent: 大小为父View允许的最大值(fill_parent 为 Android2.3 之前使用)
- wrap_content: 大小为该View实际需要的大小
- 固定大小: 大小固定为某个具体值,可以使用的单位有
dp/dip
、px
、pt
、in
、mm
(单位参考
)
如下为使用上面三种类型指定View宽度的效果
测量大小
上面介绍的是在布局资源中设置View 的大小,但是View只有在经过测量过程才能够确定最终的显示大小。父View在测量一个子View的大小时,会调用子View的onMeasure
方法,子View可以重写这个方法实现自己的测量计算:
class MyView extends View { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 获取测量模式 int widthMode = View.MeasureSpec.getMode(widthMeasureSpec); int width = View.MeasureSpec.getSize(widthMeasureSpec); int height = View.MeasureSpec.getSize(heightMeasureSpec); switch (widthMode) { case MeasureSpec.UNSPECIFIED: //父View未指定大小,子View可以设置任意大小 width = 160; break; case MeasureSpec.EXACTLY: //父View已经设置了子View的具体大小,子View无法再更改 break; case MeasureSpec.AT_MOST: //父View指定了子View大小的上限,子View可以在该上限内任意设置 width = width / 2; break; } ... //设置最终计算完的大小 setMeasuredDimension(width, height); }}
onMeasure
方法的两个参数widthMeasureSpec
、heightMeasureSpec
是父View为子View计算过的宽高,这两个参数的值是经过View.MeasureSpec
类封装过的,我们可以通过View.MeasureSpec.getMode
获得父View指定的测量模式,通过View.MeasureSpec.getSize
获得父View计算的测量大小。测量模式有如下三种:
- MeasureSpec.UNSPECIFIED 父View未指定大小,子View可以设置任意大小
- MeasureSpec.EXACTLY 父View已经设置了子View的具体大小,子View无法再更改
- MeasureSpec.AT_MOST 父View指定了子View大小的上限,子View可以在该上限内任意设置
最后要记得调用View.setMeasuredDimension
设置最终计算完的View大小。
View测量的大小可以通过 View.getMeasuredWidth()
和View.getMeasuredHeight()
获得:
int measuredWidth = view.getMeasuredWidth();int measuredHeight = view.getMeasuredHeight();
内边距与外边距
上面介绍的是View测量后的大小,但这并不是一个View会占据的最终大小,还需要考虑上View的内边距。如下为View的内边距与外边距区域示意图:
内边距
内边距为View显示主体内容(如:TextView
的文本内容、ImageView
的图片内容、ViewGroup
等View容器的子View等)时在上、下、左、右四个边上缩进的距离。
View在测量时只会根据自己所要显示的主体内容所需要的大小进行测量,得出的大小一般称为测量尺寸。测量尺寸通过View.getMeasuredWidth()
和View.getMeasuredHeight()
获得。
父View在子View测量完后还需要加上子View设置的内边距,得到该子View的绘制尺寸。测量尺寸可以通过View.getWidth()
和View.getHeight()
获得。外边距
外边距为View在ViewGroup中布局时与其他View的最小间隔距离,在View自身测量、绘制时不会考虑,只有在父View中进行布局时才会考虑。
在布局资源文件里可以对View设置内边距和外边距:
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@android:color/red" android:padding="10dp" android:layout_margin="10dp"/>
注意: 通过android:background
属性给View设置背景时,该背景会覆盖包含内边距在内的绘制区域,不会覆盖外边距的区域。
View 的布局
View的布局就是父View确定每个子View的显示位置的过程,布局过程是从ViewRootImpl.performLayout()
开始的,从根View开始请求View树中的每个ViewGroup进行布局操作。
设置布局位置
Android系统提供的LinearLayout
、RelativeLayout
等布局类,可以xml配置文件里就可以进行布局配置:
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Text1"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Text2"/></LinearLayout><RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:text="Text3"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:text="Text4"/></RelativeLayout>
LinearLayout
可以设置子View按从水平方向或者垂直方向进行顺序布局RelativeLayout
可以让子View设置停靠在父View中的任意位置,或者与相对其他子View进行布局- 其他系统提供的布局类如
GridLayout
、ListView
等也可以在xml文件里进行布局配置
通知View布局位置
每个子View被父View布局的时候都会通过onLayout()
方法收到布局的结果
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { ...}
boolean changed
参数表示本次布局相对上一次布局有没有变化,如果是第一次布局,这个值就是true
。int left, int top, int right, int bottom
几个参数表示该View在父View中上、下、左、右的位置。- 如果该View是一个ViewGroup,需要在自己的
onLayout()
里调用所有子View的layout()
方法。 - 如果View显示完成后受到事件触发,需要重新调整布局,调用一次
View.requestLayout()
就可以进行一次View树的布局操作,新的布局操作会在下一次VSYNC中断到来时触发。
View 的绘画
View 的绘画同样也是从ViewRootImpl.performDraw()
开始的,从根View开始绘制View树中的每个子View。每个View都需要继承View.onDraw()
方法来实现自己的绘画操作。Canvas提供了绘画线条、文字、图片等的接口。
class MyView extends View { @Override protected void onDraw(Canvas canvas) { canvas.drawLine(x, y); canvas.drawText(str); canvas.drawBitmapMesh(mBitmap); }}
当View状态发生变化需要重新绘画时,可以调用View.invalidate()
方法触发一次绘画操作,下次VSYNC到来时这个View的onDraw()
方法就会调用。
设置View的可见性
通过View.setVisibility(int visibility)
可以设置View的可见性,可以传入的参数如下:
View.VISIBLE
View可见,正常显示View.INVISIBLE
View不可见,但是在进行布局时仍然会考虑,并占据一定区域View.GONE
View不可见,并且进行布局时也不会考虑,不占据认可区域
- Android View 系统 3
- android--View系统解析
- Android View系统解析
- android view坐标系统
- Android View 系统 1
- Android View 系统 2
- android view系统整体介绍
- Android View系统解析(上)
- Android View系统解析(下)
- Android View系统解析(下)
- Android View系统解析(上)
- Android View系统解析(下)
- Android View系统解析(上)
- Android View系统解析(下)
- Android View系统解析(下)
- Android View系统解析(上)
- Android View系统解析(上)
- Android View系统解析(下)
- JAVA多线程总结
- 水仙花数问题
- 2018:又一个区块链大年
- 智库Reform敦促英国政府对区块链技术进行身份服务应用研究
- CFTC主席:加密货币不同于我们熟悉的其他商品
- Android View 系统 3
- 用鼠标截取图像并保存(2)
- Acute Angle Cloud与Achain达成战略合作,共促区块链系统发展
- 香港监管机构发布比特币期货警示
- 源码安装docker方式
- 饿了么-vur2.0实现总结一(项目创建及文档结构)
- CentOS 7中添加菜单项
- linux Ubuntu安装samba服务器
- Android面试篇之Activity与Fragment、Fragment与Fragment之间的通信