Android
(一)RecyclerView 与 ListView 的主要区别:
- 使用
- 布局效果
- 局部刷新
- 嵌套滚动机制
- 布局效果对比
- 常用功能和API对比
- RecyclerView 和 ListView 在 Android L 引入嵌套滚动机制之后的对比
(1)简单使用
ListView:
- 继承重写BaseAdapter类
- 自定义 ViewHolder 和 convertView 一起完成复用优化工作(在自定义的ADP、adapter中完成)
RecyclerView:
- 继承重写RecyclerView.Adapter与RecyclerView.ViewHolder
- 在ViewHolder中传入View参数,获取布局实例findViewById()
- onCreateViewHolder()--------加载布局,创建ViewHolder实例
- onBindViewHolder()--------子项数据赋值
- getItemCount()-------子项数
- 设置LayoutManager,以及layout的布局效果
- LayoutManager-------LinearLayoutManager,GridLayoutManager,staggeredGridLayoutManager
(2)布局效果
**ListView:**布局单一,只有纵向效果
**RecyclerView:**布局效果丰富(3种);LayoutManager的API中自定义Layout:
(3)实现嵌套滚动机制
-
在事件分发机制中,Touch事件在进行分发的时候,由父View向子View传递,一旦子View消费这个事件的话,那么接下来的事件分发的时候,父View将不接受,由子View进行处理;但是与Android的事件分发机制不同,嵌套滚动机制(Nested Scrolling)可以弥补这个不足,能让子View与父View同时处理这个Touch事件,主要实现在于NestedScrollingChild与NestedScrollingParent这两个接口;而在RecyclerView中,实现的是NestedScrollingChild,所以能实现嵌套滚动机制;
-
ListView就没有实现嵌套滚动机制;
(4)局部刷新
**RecyclerView:**notifyItemChanged(),局部刷新
ListView:
-
notifyDataSetChanged() ,全局刷新(每个item的数据都会重新加载一遍),非常消耗资源;
-
如何实现局部刷新?
-
在adapter里定义一个方法notifyDataSetChangedAt(View view,int position),然后在该方法处理逻辑(自定义局部刷新方法)
public void notifyDataSetChangedAt(View view,int position) { if(view != null){ int data = datas.get(position); data = data+1; datas.set(position,data); TextView textView = (TextView) view.findViewById(R.id.textview); textView.setText(String.valueOf(data)); } } ———————————————— //在外部调用,实现调用 mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { /*int data = datas.get(position); data = data+1; datas.set(position,data); adapter.notifyDataSetChanged();*/----->刷新全部数据 adapter.notifyDataSetChangedAt(view,position);----》刷新局部数据 } });
-
【扩展】:View的重新绘制过程(尝试引导话题)
onMeasure()–>onLayout()–>onDraw()
(二)View的工作流程
measure:
- View
- ViewGroup
layout:
- layout()
- onLayout()
draw:
draw()中调用的4个方法
PhoneWindow是窗口类,继承自抽象类Window,也是唯一子类。WindowManager是Window管理接口,继承自ViewManager,他的唯一实现类是WindowManagerImpl。WindowManagerImpl并没有真正实现windowManager接口逻辑,而是把逻辑转给了WindowManagerGlobal,WindowManagerGlobal是全局单例。Window和View的联系通过ViewRootImpl桥梁,同时ViewRootImpl还负责管理view树、绘制view树、和WMS通信。WMS即WindowManagerService,是Window的真正管理类。
![]()
measure------->layout-------->draw
测量-------布局-------绘制
-
确定View的测量宽高
-
确定View的最终宽高和四个顶点的位置
-
将View绘制到屏幕上
-
measure:完成测量过程。由**(final型,不能重写)**measure()方法来完成
- View的measure()方法中会调用View的onMeasure()方法
- 如果是ViewGroup则会遍历调用所有的子元素的measure方法-----measureChildren()
- 对于ViewGroup来说,除了完成自己的measure过程,还会遍历去调用所有子元素的measure方法,各个子元素在递归去执行这个过程。和View不同的是,ViewGroup是一个抽象类,因此它没有重写View的onMeasure方法,但是他提供了一个叫measureChildren的方法。
-
Layout:作用:ViewGroup用来确定子元素的位置
- layout()方法确定View本身的位置
- 通过setFrame()方法设定View的四个顶点的位置
- 调用onLayout()方法:父容器确定子元素的位置
- onLayout()方法确定所有子元素的位置。(onLayout()是在layout()中被调用个)
- 在onLayout()方法中调用子元素的layout()方法
- 层层传递,则可完成整个View树的layout过程
- layout()方法确定View本身的位置
-
draw:将View绘制到屏幕上。
这4个方法全包括在draw()方法里面调用
- 绘制背景 drawBackground(canvas)
- 绘制自己( onDraw(canvas) )
- 绘制children(dispatchDraw(canvas) )
- dispatchDraw会层层遍历所有子元素的draw方法
- 绘制装饰(onDrawScrollerBars(canvas) )
View绘制的过程的传递通过dispatchDraw()来实现的,它会调用所有子元素的draw方法
那么发起绘制的入口在哪里呢?
在介绍发起绘制的入口之前,我们需要先了解Window,ViewRootImpl,DecorView之间的联系。
一个 Activity 包含一个Window,Window是一个抽象基类,是 Activity 和整个 View 系统交互的接口,只有一个子类实现类PhoneWindow,提供了一系列窗口的方法,比如设置背景,标题等。一个PhoneWindow 对应一个 DecorView 跟 一个 ViewRootImpl,DecorView 是ViewTree 里面的顶层布局,是继承于FrameLayout,包含两个子View,一个id=statusBarBackground 的 View 和 LineaLayout,LineaLayout 里面包含 title 跟 content,title就是平时用的TitleBar或者ActionBar,contenty也是 FrameLayout,activity通过 setContent()加载布局的时候加载到这个View上。ViewRootImpl 就是建立 DecorView 和 Window 之间的联系。
这三个阶段的核心入口是在 ViewRootImpl 类的 performTraversals() 方法中。绘制的入口是由ViewRootImpl的performTraversals方法来发起Measure,Layout,Draw等流程的。
private void performTraversals() {
......
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
......
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
mView.draw(canvas);
......
}
1. Measure
MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。
- 对于View默认是测量很简单,大部分情况就是拿计算出来的MeasureSpec的size 当做最终测量的大小
一个MeasureSpec封装了从父容器传递给子容器的布局要求,这个MeasureSpec 封装的是父容器传递给子容器的布局要求,而不是父容器对子容器的布局要求,“传递” 两个字很重要,更精确的说法应该这个MeasureSpec是由父View的MeasureSpec和子View的LayoutParams通过简单的计算得出一个针对子View的测量要求,这个测量要求就是MeasureSpec。
- 子View的LayoutParams其实就是我们在xml写的时候设置的layout_width和layout_height 转化而来的
父View的measure的过程会先测量子View,等子View测量结果出来后,再来测量自己,上面的measureChildWithMargins就是用来测量某个子View的
子View的LayoutParams:
- 具体大小(childDimension)
- match_parent
- wrap_content
MeasureSpec一共有三种模式 :
-
UPSPECIFIED : 父容器对于子容器没有任何限制,子容器想要多大就多大(说明父View大小是确定的)
-
EXACTLY: 父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间。
-
AT_MOST:子容器可以是声明大小内的任意大小(说明父View大小是不确定的)
(三)事件分发机制
- 三种方法的解析
- 具体流程分析(点击事件为例):图
- View处理事件优先级:3种的对比
- dispatchTouchEvent()-------分发事件
- onInterceptTouchEvent()--------判断是否拦截某个事件,
如果当前View拦截了某个事件,那么在同一个事件序列中,此方法不会被再次调用,返回结果表示是否拦截当前事件。(只存在于ViewGroup中,为什么?)------->解释原因:结合传递顺序- 如果拦截事件,则调用onTouchEvent()来处理点击事件。
- 不拦截-----> child.dispatchTouchEvent()
- 在 ViewGroup 的dispatchTouchEvent()内部调用
- onTouchEvent()-------处理点击事件,在dispatchTouchEvent()中调用,返回结果表示是否消耗当前事件。
//这个是伪代码表示,并不是实际的代码!!!!!
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if(onInterceptTouchEvent(ev)){ //拦截事件
consume = onTouchEvent(ev);
} else { //不拦截事件
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
点击事件的传递过程:
Activity ----> ViewGroup----> View
顶级View接收到事件后,就会按照事件分发机制区分发事件。
对比上面的例子,Activity对应的就是项目经理,viewGroup就是你的老大,而你就相当于view了;
**Activity和View是没有onInterceptTouchEvent()**事件拦截这个方法的?
-
Activity是处于分发机制的最顶端,如果一开始就把事件拦截了,那么会导致整个屏幕都无法响应用户的操作
-
而view处于事件分发的最末端,它不需要拦截,事件分发到View的时候,view能处理就处理,不处理就返回给他的父控件;
流程分析,以点击事件为例:
当用户点击了屏幕,首先Activity先监测到,事件先传递到Activity中,Activity通过它的dispatchTouchEvent将事件分发到phoneWindow,phonewindow则会调用superdispatchTouchEvent方法(抽象方法)的内部是调用了其内部类DecorView,而DecorView又会调用dispatchTouchEvent去进行事件分发,如果不拦截事件,那么就会继续下传到rootview**(ViewGroup),rootview中的操作是一样的,同样在dispatchTouchEvent内部调用onInterceptTouchEvent去判断是否拦截**(讨论),不拦截就会把事件分发给下一个viewgroupA,拦截就直接在onTouchEvent返回true,viewgroupA中做的判断也是一样,最后事件传递到view1,view1是最底层控件,不会有onInterceptTouchEvent,它的选择就只有处理或不处理(讨论),处理就在onTouchEvent进行处理并返回true,不处理的话事件也不会被销毁,view1这时会把事件回传,经过上述流程后回传给activity,如果Activity还不处理,那么这个事件才会被销毁。

**【补充】**回答后面加粗的2,3,4
View处理事件优先级
OnTouchListener 【onTouch()】 > onTouchEvent()方法 > OnClickListener【onClick()】
OnTouchListener中有onTouch()方法
OnClickListener中有onClick()方法
先说onTouchEvent与setOnTouchListener中onTouch的区别
其逻辑可参看下面伪代码
public boolean dispatchTouchEvent(MotionEvent event){
if(mOnTouchListener != null && mOnTouchListener.onTouch(this,event)){
return true;
}
return onTouchEvent(event);
}
结合上面代码的含义与事件分发机制得出以下结论
(1)dispatchTouchEvent(MotionEvent event)返回true,表示本次事件被消耗,然后会有新事件传入。若返回false则不会有新事件传入。
(2)mOnTouchListener.onTouch方法返回的是true,onTouchEvent将不被执行。只有前者返回false,后者才会执行.
(3)只要onTouchEvent方法中的DOWN与UP事件都执行了,就会执行setOnClickListener中的onClick或者回调View.onClick,只是后者优先执行。
(4)总体优先级 setTouchListener > onTouchEvent > onClick > setClickListener
注意:DOWN或UP就是一个事件,不是DOWM+MOVE+UP才是一个事件,加起来是我们称作一系列事件
重写onTouchEvent时千万别删了super.onTouchEvent(event)——本人手贱,为此付出过惨痛代价。
-
可见是首先执行OnTouchListener,之后为onTouchEvent,最后才执行onClickListener内的方法
-
至于为什么OnTouchListener和onTouchEvent执行了两次,onClick()执行力一次
- 是因为在DOWN和UP时两个方法都被调用
- 至于onClickListener则只在UP的时候调用
(四)Android的Handler机制
看字节一面复盘

本文深入探讨了Android开发中的关键概念,包括内存管理、进程优先级、启动模式、生命周期、事件分发、滑动冲突解决、数据持久化以及多线程间的通信。详细讲解了如何避免内存溢出(OOM)、理解Activity的启动模式、使用Handler和Binder进行进程间通信,以及如何处理滑动冲突,特别强调了单例模式在内存管理中的应用和生命周期的理解。此外,还介绍了ListView与RecyclerView的区别、滑动冲突的解决方案,以及如何在ViewPager中实现页面切换。通过对Android系统级别的理解,开发者能够更好地优化应用性能和用户体验。
最低0.47元/天 解锁文章
5530

被折叠的 条评论
为什么被折叠?



