Android view的一些认识
转载:9102年末,我对Android view的13条认识;
(顺手留下GitHub链接,需要获取相关面试等内容的可以自己去找)
https://github.com/xiangjiana/Android-MS
1.LinearLayout布局,会不会走onDraw()方法?
明framelayout是viewgroup的子类,而viewgroup是view的子类,在自定义view的时候重写ondraw方法可以画出各式各样的view为什么在framelayou里ondraw方法就不执行呢?
经过一番查阅资料,发现原因简单来说是这样的,google工程师们是这么分类的,viewgroup是属于容器,view属于内容,于是容器里面的ondraw方法默认设置为不执行。
解决方法如下:
1,在构造函数里面,给其设置一个颜色,如#00000000。
自定义了一个集成LinearLayout的控件,但是如果xml不设置background属性,onDraw不会被调用~
2,在构造函数里面,调用setWillNotDraw(false)。
ViewGroup默认情况下,出于性能考虑,会被设置成WILL_NOT_DROW,这样,ondraw就不会被执行了。
如果我们想重写一个viewgroup的ondraw方法,有两种方法:
1,构造函数中,给viewgroup设置一个颜色。
2,构造函数中,调用setWillNotDraw(false),去掉其WILL_NOT_DRAW flag。
在viewgroup初始化的时候,它调用了一个私有方法:initViewGroup,它里面会有一句setFlags(WILLL_NOT_DRAW,DRAW_MASK);相当于调用了setWillNotDraw(true),所以说,对于ViewGroup,他就认为是透明的了,如果我们想要重写onDraw,就要调用setWillNotDraw(false)。
一丶 View 的绘制流程?
View 的工作流程主要是指 measure、layout、draw 这三大流程,即测量、布局和绘制,
其中 measure 确定 View 的测量宽/ / 高, layout 确定 View 的最终宽/ / 高和 四个顶点的位置,而 draw 则将 View绘制到屏幕上
View 的绘制过程遵循如下几步:
- 绘制背景 background.draw(canvas)
- 绘制自己(onDraw)
- 绘制 children(dispatchDraw)
绘制装饰(onDrawScollBars)
二丶View的事件分发制
点击事件产生后,首先传递给 Activity 的 dispatchTouchEvent
方法,
通过PhoneWindow
传递给 DecorView
, 然后再传递给根 ViewGroup
, 进入ViewGroup
的dispatchTouchEvent()
,
执行 onInterceptTouchEvent
方法判断是否拦截,再不拦截的情况下,此时会遍历 ViewGroup
的子元素,进入子 View 的dispatchToucnEvent
方法。
如果子 view 设置了 onTouchListener
, 就执行 onTouch
方法,并根据 onTouch
的返回值为 true 还是 false 来决定是否执行 onTouchEvent
方法,
如果是 false 则继续执行 onTouchEvent
。 在onTouchEvent
的 Action Up 事件中判断,如果设置了 onClickListener
, 就执行 onClick
方法。
View 事件传递分发机制?
- View 事件分发本质就是对
MotionEvent
事件分发的过程。即当一个 MotionEvent 发生后,系统将这个点击事件传递到一个具体的 View 上 - 点击事件的传递顺序: Activity( Window)→ViewGroup→ View
- 事件分发过程由三个方法共同完成
dispatchTouchEvent
:用来进行事件的分发。如果事件能够传递给当前 View,那么此方法一定会被调用,返回结果受当前 View 的onTouchEvent
和下级View 的dispatchTouchEvent
方法的影响,表示是否消耗当前事件onInterceptTouchEvent
:在上述方法内部调用,对事件进行拦截。该方法只在ViewGroup
中有,View(不包含ViewGroup
)是没有的。一旦拦截,则执行ViewGroup
的onTouchEvent
,在ViewGroup
中处理事件,而不接着分发给 View。且只调用一次,返回结果表示是否拦截当前事件onTouchEvent
: 在dispatchTouchEvent
方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件
三丶 View的加载流程
1.1 View 随着 Activity 的创建而加载,startActivity 启动一个 Activity 时,在
ActivityThread
的 handleLaunchActivity()
会执行 Activity 的 onCreate
方法,这个时候会调用 setContentView
加载布局,
创建出 DecorView
并将我们的 layout加载到 DecorView
中。
1.2 当执行到 handleResumeActivity
时,Activity 的 onResume
方法被调用, 然后 WindowManager
会将 DecorView
设置给 ViewRootImpl
,
这样,DecorView
就被加载到Window中了,此时界面还没有显示出来, 还需要经过 View的 measure,layout 和 draw 方法,才能完成 View 的工作流程。
View的绘制 是 由ViewRoot
来负责的,每一个DecorView
都有一个与之关联的ViewRoot
, 这种关联关系是由WindowManager
维护的,
1.3 将DecorView
和 ViewRoot
关联之后,
ViewRootImpl
的 requestLayout
会被调用 以完成初步布局,通过scheduleTraversals
方法向主线程发送消息请求遍历,
最终调用ViewRootImpl
的 performTraversals
方法, 这个方法会执行 View 的 measure layout 和 draw 流程。
四丶 自定义view 需要注意的几点
1. 让 view 支持 wrap_content 属性,在 onMeasure
方法中针对 AT_MOST 模式做专门处理,否则 wrap_content
会和 match_parent
效果一样(继承 ViewGroup
也同样要在 onMeasure
中做这个判断处理)
if (widthMeasureSpec == MeasureSpec.AT_MOST && heightMeasureSpec == MeasureSpec.AT_MOST) {
setMeasuredDimension( 200 , 200 );
// wrap_content
情况下要设置一个默认值,200 只是举个例子,最终的值需要计算得到刚好包裹内容的宽高值
} else if (widthMeasureSpec == MeasureSpec.AT_MOST) {
setMeasuredDimension( 200 ,heightMeasureSpec );
} else if (heightMeasureSpec == MeasureSpec.AT_MOST) {
setMeasuredDimension(heightMeasureSpec , 200 );
}
2.让 view 支持 padding( onDraw 的时候, 宽高减去 padding 值, margin 由父布局控制,不需要 view 考虑),
自定义 ViewGroup
需要考虑自身的 padding 和子view的 margin 造成的影响。
3.在 view 中尽量不要使用 handler,使用 view 本身的 post 方法
4.在 onDetachedFromWindow 中及时停止线程或动画
5.view 带有滑动嵌套情形时,处理好滑动冲突
五丶View 的 measure(), layout() 和 draw()?
在上边的分析中我们知道,View 绘制流程的入口在 ViewRootImpl
的performTraversals
方法,在方法中首先调用 performMeasure
方法,
传入一个childWidthMeasureSpec
和 childHeightMeasureSpec
参数,这两个参数代表的是DecorView
的 MeasureSpec
值,
这个 MeasureSpec
值由窗口的尺寸和 DecorView
的 LayoutParams
决定,最终调用 View 的 measure 方法进入测量流程
measure :
View 的 measure 过程由 ViewGroup
传递而来,在调用 View.measure 方法之前, 会首先根据 View 自身的 LayoutParams
和 父布局的MeasureSpec,
确定子 view 的MeasureSpec
,然后将 view 宽高对应的 measureSpec
传递到 measure 方法中,那么子 view 的 MeasureSpec
获取规则是怎样的?
分几种情况进行说明
1.父布局是 EXACTLY 模式:
a. 子 view 宽或高是个确定值,那么子 view 的 size 就是这个确定值,mode是 EXACTLY(是不是说子 view 宽高可以超过父 view?见下一个)
b. 子 view 宽或高设置为 match_parent, 那么子 view 的 size 就是占满父容器剩余空间,模式就是 EXACTLY
c. 子 view 宽或高设置为 wrap_content, 那么子 view 的 size 就是占满父容器剩余空间,不能超过父容器大小,模式就是 AT_MOST
2.父布局是 AT_MOST 模式:
a. 子 view 宽或高是个确定值,那么子 view 的 size 就是这个确定值,mode 是EXACTLY
b. 子 view 宽或高设置为 match_parent,那么子 view 的 size 就是占满父容器剩余空间,不能超过父容器大小,模式就是 AT_MOST
c. 子 view 宽或高设置为 wrap_content, 那么子 view 的 size 就是占满父容器剩余空间,不能超过父容器大小,模式就是 AT_MOST
3.父布局是 UNSPECIFIED 模式:
a. 子 view 宽或高是个确定值,那么子 view 的 size 就是这个确定值,mode 是EXACTLY
b. 子 view 宽或高设置为 match_parent,那么子 view 的 size 就是 0,模式就是UNSPECIFIED
c. 子 view 宽或高设置为 wrap_content,那么子 view 的 size 就是 0,模式就是UNSPECIFIED
获取到宽高的 MeasureSpec
后,传入 view 的 measure 方法中来确定 view 的宽高,这个时候还要分情况
1.当
MeasureSpec
的 mode 是 UNSPECIFIED, 此时 view 的宽或者高要看 view 有没有设置背景,如果没有设置背景,就返回设置的
minWidth
或minHeight
, 这两个值如果没有,设置默认就是 0,如果 view 设置了背景,就取
minWidth
或minHeight
和 背景 这个drawable
固有宽或者高 ,中的最大值返回;
2.当MeasureSpec
的 mode 是 AT_MOST 和 EXACTLY,此时 view 的宽高都返回从MeasureSpec
中获取到的 size 值,这个值的确定见上边的分析。因此如果要通过继承 view 实现自定义 view,一定要重写
onMeasure
方法对wrap_conten
属性做处理,否则,他的
match_parent
和wrap_content
属性效果就是一样的
layout:
layout()是用来确定 view 本身的位置,onLayout()
确定所有子元素的位置。
当 ViewGroup
的位置确定之后, 它在 onLayout
中会遍历所有的子元素并调用其 layout 方法,在子元素的 layout 方法中 onLayout
方法又会被调用。
layout 方法的流程是,首先通过 setFrame
方法确定 view 四个顶点的位置,然后view 在父容器中的位置也就确定了,
接着会调用onLayout
方法,确定子元素的位置,onLayout
是个空方法,需要继承者去实现。
getMeasuredHeight
和getHeight
方法有什么区别?
getMeasuredHeight
(测量高度)形成于 view 的 measure 过程,getHeight
(最终高度)形成于 layout 过程,
在有些情况下,view 需要 measure 多次才能确定测量宽高,在前几次的测量过程中,得出的测量宽高有可能和最终宽高不一致,
但是最终来说,还是会相同。 有一种情况会导致两者值不一样,如下,此代码会导致 view 的最终宽高比测量宽高大100PX。
public void layout(int l,int t,int r, int b) {
super.layout(l,t,r+100,b+100); {
}
View 的绘制过程遵循如下几步:
a.绘制背景 background.draw(canvas)
b.绘制自己(onDraw)
c.绘制 children(dispatchDraw)
d.绘制装饰(onDrawScrollBars)
View 绘制过程的传递是通过 dispatchDraw
来实现的,它会遍历所有的子元素的draw 方法,如此 draw 事件就一层一层的传递下去了
ps: view 有一个特殊的方法 setWillNotDraw
,如果一个 view 不需要绘制内容,即不需要重写 onDraw
方法绘制,可以开启这个标记,系统会进行相应的优化。
默认情况下,View 没有开启这个标记,默认认为需要实现 onDraw
方法绘制。
当我们继承 ViewGroup
实现自定义控件,并且明确知道不需要具备绘制功能时,可以开启这个标记,如果我们重写了 onDraw
, 那么要显示的关闭这个标记。
子 view 宽高可以超过父 view ?能
1. android:clipChildren = "false" 这个属性要设置在父 view 上。代表其中的子View 可以超出屏幕。
2. 子 view 要有具体的大小,一定要比父 view 大 才能超出。比如 父 view 高度100px 子 view 设置高度 150px。子 view 比父 view 大,这样超出的属性才有意义。(高度可以在代码中动态赋值,但不能用 wrap_content / match_partent)。
3. 对父布局还有要求,要求使用 linearLayout(反正我用 RelativeLayout 是不行)。你如果必须用其他布局,可以在需要超出的 view 上面套一个linearLayout 外面再套其他的布局。
4. 最外面的布局如果设置的 padding 不能超出
六丶MotionEvent 是什么? 包含几种事件? 什么条件下会产生?
MotionEvent 是手指接触屏幕后所产生的一系列事件。典型的事件类型有如下:
ACTION_DOWN:手指刚接触屏幕
ACTION_MOVE:手指在屏幕上移动
ACTION_UP:手指从屏幕上松开的一瞬间
ACTION_CANCELL:手指保持按下操作,并从当前控件转移到外层控件时触发
正常情况下,一次手指触摸屏幕的行为会触发一系列点击
事件,考虑如下几种情况:
点击屏幕后松开,事件序列:DOWN→UP
点击屏幕滑动一会再松开,事件序列为DOWN→MOVE→.....→MOVE→UP
七丶如何解决View 的事件冲突
参考回答:
常见开发中,事件冲突的有 ScrollView
与 RecyclerView
滑动冲突、 RecyclerView
内嵌同时滑动同一方向
滑动冲突的处理规则:
- 对于由于外部滑动和内部滑动,方向不一致导致的滑动冲突,可以根据滑动的方向 判断谁来拦截事件。
- 对于由于外部滑动方向和内部滑动,方向一致导致的滑动冲突,可以根据业务需求,规定何时让外部View 拦截事件,何时由内部 View 拦截事件。
- 对于上面两种情况的嵌套,相对复杂,可同样根据需求在业务上找到突破点。
滑动冲突的实现方法:
- 外部拦截法: 指点击事件都先经过父容器的拦截处理,如果父容器需要此事件,就拦截,否则就不拦截。
具体方法: 需要重写父容器的onInterceptTouchEvent
方法,在内部做出相应的拦截。 - 内部拦截法: 指父容器不拦截任何事件,而将所有的事件都传递给子容器,如果子容器需要此事件就直接消耗,否则就交由父容器进行处理。
具体方法:需要配合requestDisallowInterceptTouchEvent
方法
八丶Scroller 是怎么实现View的弹性滑动
参考回答:
- 在
MotionEvent.ACTION_UP
事件触发时调用startScroll()
方法,该方法并没有进行实际的滑动操作,而是记录滑动相关量(滑动距离、滑动时间) - 接着调用
invalidate/postInvalidate()
方法,请求 View重绘,导致 View.draw 方法被执行 - 当 View 重绘后会在 draw 方法中调用
computeScroll
方法,而computeScroll
又会去向Scroller
获取当前的scrollX
和scrollY
; - 然后通过
scrollTo
方法实现滑动;接着又调用postInvalidate
方法来进行第二次重绘,和之前流程一样,如此反复导致 View 不断进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动,直到整个滑动过成结束
九丶 invalidate()和 postInvalidate() 的区别 ?
invalidate()
与 postInvalidate()
都用于刷新 View,
主要区别是 invalidate()
在主线程中调用,若在子线程中使用需要配合 handler; 而 postInvalidate()
可在子线程中直接调用。
十丶 SurfaceView 和 View 的区别?
- View 需要在 UI 线程对画面进行刷新,而
SurfaceView
可在子线程进行页面的刷新 - View 适用于主动更新的情况, 而
SurfaceView
适用于被动更新,如频繁刷新,这是因为如果使用 View 频繁刷新会阻塞主线程,导致界面卡顿。 SurfaceView
在底层已实现--双缓冲机制,而 View 没有,因此SurfaceView
更适用于需要频繁刷新、刷新时数据处理量很大的页面(如视频播放界面)
十一丶 scrollTo()和 scollBy() 的区别?
scollBy
内部调用了scrollTo
,它是基于当前位置的相对滑动; 而scrollTo
是绝对滑动;- 因此如果使用相同输入参数多次调用
scrollTo
方法,由于 View 的初始位置是不变的,所以只会出现一次 View 滚动的效果 - 两者都只能对 View内容的滑动,而非使View本身滑动。 可以使用
Scroller
有过度滑动的效果
十三丶View 的滑动方式
a. layout(left,top,right,bottom)
: 通过修改 View 四个方向的属性值。来修改 View 的坐标,从而滑动 View
b. offsetLeftAndRight() offsetTopAndBottom()
:指定偏移量滑动 view
c. LayoutParams
,改变布局参数:layoutParams
中保存了 view 的布局参数,可以通过修改布局参数的方式滑动 view
d. 通过动画来移动 view:注意安卓的平移动画不能改变 view 的位置参数,属性动画可以
e. scrollTo/scrollBy
:注意移动的是 view 的内容,scrollBy(50,50)
你会看到屏幕上的内容向屏幕的左上角移动了,
这是参考对象不同导致的,你可以看作是它移动的是手机屏幕,手机屏幕向右下角移动,那么屏幕上的内容就像左上角移动了
f. scroller: scroller
需要配置 computeScroll
方法实现 view 的滑动,
scroller
本身并不会滑动 view,它的作用可以看作一个插值器,它会计算当前时间点 view 应该滑动到的距离,然后 view 不断的重绘,不断的调用 computeScroll
方法,这个方法是个空方法,所以我们重写这个方法,在这个方法中不断的从 scroller
中获取当前 view 的位置,调用 scrollTo
方法实现滑动的效果 。
十三丶自定义View 如何考虑机型适配?
- 合理使用 warp_content,match_parent
- 尽可能的是使用 RelativeLayout
- 针对不同的机型,使用不同的布局文件,放在对应的目录下,android 会自动匹配。
- 尽量使用点 9 图片。
- 使用与密度无关的像素单位: dp,sp
- 引入 android 的百分比布局。
- 切图的时候切大分辨率的图,应用到布局当中。 在小分辨率的手机上也会有很好的显示效果。
Android view的一些认识的更多相关文章
- 虾扯蛋:Android View动画 Animation不完全解析
本文结合一些周知的概念和源码片段,对View动画的工作原理进行挖掘和分析.以下不是对源码一丝不苟的分析过程,只是以搞清楚Animation的执行过程.如何被周期性调用为目标粗略分析下相关方法的执行细节 ...
- Android View.setId(int id) 用法
Android View.setId(int id) 用法 当要在代码中动态的添加View并且为其设置id时,如果直接用一个int值时,Studio会警告. 经过查询,动态设置id的方法有两种; 1. ...
- android view 中各函数的执行顺数
这个就好像是 activity 的生命周期一样,如果我们要使用自定义的 view,那么就很有必要了解一下 view 的那些能够被重写的函数的执行顺序.废话不多讲,以常用的5个函数为例子,见下文: pa ...
- Android项目部署时,发生AndroidRuntime:android.view.InflateException: Binary XML file line #168: Error inflating class错误
这个错误也是让我纠结了一天,当时写的项目在安卓虚拟机上运行都很正常,于是当我部署到安卓手机上时,点击登陆按钮跳转到用户主界面的时候直接结束运行返回登陆界面. 当时,我仔细检查了一下自己的代码,并 ...
- Android View 事件分发机制 源码解析 (上)
一直想写事件分发机制的文章,不管咋样,也得自己研究下事件分发的源码,写出心得~ 首先我们先写个简单的例子来测试View的事件转发的流程~ 1.案例 为了更好的研究View的事件转发,我们自定以一个My ...
- 简单研究Android View绘制三 布局过程
2015-07-28 17:29:19 这一篇主要看看布局过程 一.布局过程肯定要不可避免的涉及到layout()和onLayout()方法,这两个方法都是定义在View.java中,源码如下: /* ...
- java.lang.ClassCastException: android.view.AbsSavedState$1 cannot be cast to android.widget.ProgressBar$SavedState
java.lang.ClassCastException: android.view.AbsSavedState$1 cannot be cast to android.widget.Progress ...
- Android View各种尺寸位置相关的方法探究
Android View各种尺寸位置相关的方法探究 本来想做一个View间的碰撞检测之类的. 动手做了才发现不是想象的那么简单. 首先,写好了碰撞检测的工具类如下: package com.mengd ...
- Activity has leaked window that was originally added -界面退出时未关闭对话框异常 android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running? -
退出Activity时弹出登录框,点击确定finish当前Activity,结果报了这个错,随后查找资料知道 原因: 是因为退出Activity时没有关闭弹出框,出现了这个错误 解决方法: 只需要在a ...
- [Android] View.setTag(key,Object) (java.lang.IllegalArgumentException: The key must be an application-specific resource id.)
转自: http://blog.csdn.net/brokge/article/details/8536906 setTag是android的view类中很有用的一个方法,可以用它来给空间附加一些信息 ...
随机推荐
- selenium 模拟键盘事件 复制粘贴、右键、回车等
#coding=utf-8 ''' selenium ''' from selenium import webdriver as wd import time bc=wd.Chrome(executa ...
- ZZNU-OJ-2118 -(台球桌面碰来碰去,求总距离)——模拟到爆炸【超时】的不能AC的代码
ZZNU-2118 : 早安晚安,不如我先入土为安 题目描述 spring比较喜欢玩台球,因为看着台球在桌子上碰来碰去很有意思(台球撞壁反弹,入射角等于反射角),每次完美的台球入洞,都能体现他数学天才 ...
- ggplot2入门与进阶(下)
出处:http://www.cellyse.com/how_to_use_gggplot2_part2/ 更多实战 例一 Michaelis-Menten动力学方程 这个例子中采用出自文献中的一组有关 ...
- 三种方法给Vmware虚拟机占用空间清理瘦身
随着VMware虚拟机使用时间的增长,其所占用的空间也越来越大,本文来说说怎么给VMware虚拟机占用的空间进行瘦身. 方法一:VMware自带的清理磁盘这个方法是VMware自带,具有普适性,对快照 ...
- .npmrc 实用小技巧
小技巧 因为每次执行 npm adduser 的时候都需要输入用户名.密码和email 很麻烦,我们都可以配置在.npmrc 文件中,在命令行中执行如下脚本 echo -n 'myuser:mypas ...
- rabbitmq 公平分发和消息接收确认(转载)
原文地址:http://www.jianshu.com/p/f63820fe2638 当生产者投递消息到broker,rabbitmq把消息分发到消费者. 如果设置了autoAck=true 消费者会 ...
- [2019牛客多校第三场][G. Removing Stones]
题目链接:https://ac.nowcoder.com/acm/contest/883/G 题目大意:有\(n\)堆石头,每堆有\(a_i\)个,每次可以选其中两堆非零的石堆,各取走一个石子,当所有 ...
- Invalid HTTP_HOST header: 'xxx.xxx:8000'. You may need to add 'xxx.xx' to ALLOWED_HOSTS
用python3 manage.py runserver 0.0.0.0:8000命令运行django程序后,通过浏览器访问服务器网址的8000端口,出现访问错误,报错为 Invalid HTTP_H ...
- nios ii 13 主程序的函数可以用Open Declaration 查看,但是编译的时候却说 undefined reference to 。。。这是为什么?
在做12864 ip 核试验时,写了三个文件第一个是时序文件QC12864.v第二个是QC12864.H这个文件主要包括声明和宏定义,第三个文件是QC12864.c这个文件包含函数的定义.详细的请看 ...
- 【线性代数】6-5:正定矩阵(Positive Definite Matrices)
title: [线性代数]6-5:正定矩阵(Positive Definite Matrices) categories: Mathematic Linear Algebra keywords: Po ...