每次到每一年的年底,都会花几天时间把今年对每个知识点总结一下。算是对自己经验的累积,以弥补自己的不足。把知识点汇总一下,看看自身的不足和错误,以便2020年再接再厉
今天先总结一下关于Android View 总结
(顺手留下GitHub链接,需要获取相关面试等内容的可以自己去找)
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)
一丶 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的加载流程
View 随着 Activity 的创建而加载,startActivity 启动一个 Activity 时,在ActivityThread
的 handleLaunchActivity
方法中会执行 Activity 的 onCreate
方法,这个时候会调用 setContentView
加载布局创建出 DecorView
并将我们的 layout加载到 DecorView
中,当执行到 handleResumeActivity
时,Activity 的 onResume
方法被调用,然后 WindowManager
会将 DecorView
设置给 ViewRootImpl
,这样,DecorView
就被加载到Window中了,此时界面还没有显示出来,还需要经过 View的 measure,layout 和 draw 方法,才能完成 View 的工作流程。我们需要知道 View的绘制是由ViewRoot
来负责的,每一个DecorView
都有一个与之关联的ViewRoot
,这种关联关系是由WindowManager
维护的,将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 如何考虑机型适配?
- 合理使用 warp_content,match_parent
- 尽可能的是使用 RelativeLayout
- 针对不同的机型,使用不同的布局文件放在对应的目录下,android 会自动匹配。
- 尽量使用点 9 图片。
- 使用与密度无关的像素单位 dp,sp
- 引入 android 的百分比布局。
- 切图的时候切大分辨率的图,应用到布局当中。在小分辨率的手机上也会有很好的显示效果。
十三丶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
方法实现滑动的效果
(顺手留下GitHub链接,需要获取相关面试等内容的可以自己去找)
https://github.com/xiangjiana/Android-MS
相关推荐
本项目旨在教你如何在Android项目中创建一个自定义View,用于实现高斯模糊和毛玻璃效果,并且允许开发者自定义模糊程度。以下是关于这个主题的详细讲解。 一、高斯模糊原理 高斯模糊是一种图像处理技术,通过对图像...
在Android开发中,View是构建用户界面的基本元素。利用View,开发者可以自定义各种复杂的UI组件,其中之一就是画线。本篇文章将详细讲解如何在Android的View中实现画线功能,以此来创建一个简单的画线练习作品。 ...
在Android开发中,自定义View是一项重要的技能,它允许开发者根据特定需求创建独特且功能丰富的UI组件。本项目"Android 自定义View实现水平温度计"就是这样一个实例,它通过自定义View来展示一个水平方向的温度计,...
Android 自定义 View 实现转盘旋转的效果 Android 的自定义 View 为开发者定义和使用个性化的 View 提供了很好的支持,想要使用自己定义的 View,需要继承 View 类,并重写构造函数和 onDraw() 函数。onDraw 函数...
在Android开发中,有时我们需要对应用的某个特定View进行截图并保存或分享,例如用户希望分享当前活动的状态或者游戏得分。这个过程涉及到的关键知识点包括View的层级结构、Bitmap的生成与处理以及图片的保存和合成...
在Android开发中,自定义View是一项重要的技能,它允许开发者根据需求创建独特的用户界面元素,以实现更加丰富和个性化的交互体验。本实例主要讲解如何在Android中自定义View,并提供了一个可作为参考的demo。 首先...
在Android开发中,View是构建用户界面的基本元素,它的绘制流程是开发者理解并优化UI性能的关键。本篇文章将深入探讨Android中View的绘制流程,以及如何通过自定义ViewGroup进行更复杂的布局管理。 首先,我们要...
Android View 事件传递与消费,用于view事件传递的理解。
Android系统的事件分发主要由View的`dispatchTouchEvent()`、`onTouchEvent()`、`onInterceptTouchEvent()`三个方法协同完成。通过阅读源码,我们可以更深入地理解点击事件的处理流程,这对于优化性能和解决复杂场景...
在Android开发中,根View(Root View)是布局文件中最高层次的视图容器,它包含并管理着应用界面中的所有子视图。根View通常是LinearLayout、RelativeLayout、FrameLayout等布局组件,它决定了子视图的排列方式和...
Compose 和 Android 传统View 互相调用 示例 Demo Sample,以及在双层嵌套(原生View嵌套Compose,Compose中又嵌套原生View)的情况下,在最外层原生View中,怎么获取到Compose内部的原生View。 具体详见我的博客 : ...
本资源提供对于ViewGroup下所以子view的遍历,以及调用Views都过tag或者Widget的类查找返回一个List<View> 使用方法可见示例: 子View遍历 ViewGroup root = (ViewGroup) findViewById(android.R.id.content); ...
在Android开发中,View是界面元素的基础,它构成了用户与应用程序交互的核心部分。"android View下的继承关系"这个主题深入探讨了Android视图系统中View类及其子类的层次结构,帮助开发者理解如何构建和定制用户界面...
本文将深入探讨如何创建一个Android音频剪辑裁剪的自定义View,以便用户可以通过拖动来调整音频的剪切时长,并通过滑块实时显示对应的时间点。 首先,我们需要理解Android的音频处理基础。Android提供了`...
`android-pdfview`是一个专为Android平台设计的开源库,它允许开发者在Eclipse这样的集成开发环境中集成并实现PDF文件的展示。这个库是基于Java编程语言,使得在Eclipse中使用变得简单而高效。 在Eclipse中使用`...
android-pdfview是一个Android类库,提供一个快速读取显示Android PDF文件的组件——PDFView,该组件支持手势、动画和缩放。并且基于Google的VuDroid类库解码PDF文件。可以通过手势滑动PDF文件、对PDF文件进行放大...
在Android开发中,为View添加阴影效果是一种常见的需求,可以提升UI的视觉吸引力和用户体验。本文将详细讲解如何使用`elevation`属性以及`layer-list`来实现这一效果。 首先,`elevation`属性是自Android Lollipop...
总之,掌握自定义Android View不仅需要对Android系统的理解,还需要对图形绘制、事件处理、性能优化等多个方面有深入的了解。通过不断实践和学习,你将成为一名精通自定义View的Android开发大师,创造出更多引人入胜...
在Android应用开发中,处理触摸事件(touch event)是至关重要的,因为它允许用户与界面进行交互。本篇文章将深入探讨如何判断一个触摸事件点是否在指定的View范围内,以及涉及的相关事件响应和View属性操作。 首先...