ScrollView(RecyclerView等)为什么会自动滚动原理分析,还有阻止自动滑动的解决方案
引言,有一天我在调试一个界面,xml布局里面包含Scroll View,里面嵌套了recyclerView的时候,界面一进去,就自动滚动到了recyclerView的那部分,百思不得其解,上网查了好多资料,大部分只是提到了解决的办法,但是对于为什么会这样,都没有一个很好的解释,本着对技术的负责的态度,花费了一点时间将前后理顺了下
1.首先在包含ScrollView的xml布局中,我们在一加载进来,ScrollView就自动滚动到获取焦点的子view的位置,那我们就需要看下我们activity的onCreate中执行了什么?
答:当我们在activity的onCreate方法中调用setContentView(int layRes)的时候,我们会调用LayoutInflater的inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)方法,这里会找到xml的rootView,然后对rootView进行rInflateChildren(parser, temp, attrs, true)加载xml的rootView下面的子View,如果是,其中会调用addView方法,我们看下addView方法:
public void addView(View child, int index, LayoutParams params) {
......
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
addView的方法内部是调用了ViewGroup的addViewInner(View child, int index, LayoutParams params,boolean preventRequestLayout)方法:
android.view.ViewGroup{
......
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
......
if (child.hasFocus()) {
requestChildFocus(child, child.findFocus());
}
......
}
}
}
这里我们看到,我们在添加一个hasFocus的子view的时候,是会调用requestChildFocus方法,在这里我们需要明白view的绘制原理,是view树的层级绘制,是绘制树的最顶端,也就是子view,然后父view的机制。明白这个的话,我们再继续看ViewGroup的requestChildFocus方法,
@Override
public void requestChildFocus(View child, View focused) {
if (DBG) {
System.out.println(this + " requestChildFocus()");
}
if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
return;
}
// Unfocus us, if necessary
super.unFocus(focused);
// We had a previous notion of who had focus. Clear it.
if (mFocused != child) {
if (mFocused != null) {
mFocused.unFocus(focused);
}
mFocused = child;
}
if (mParent != null) {
mParent.requestChildFocus(this, focused);
}
}
在上面会看到 mParent.requestChildFocus(this, focused);的调用,这是Android中典型的也是24种设计模式的一种(责任链模式),会一直调用,就这样,我们肯定会调用到ScrollView的requestChidlFocus方法,然后Android的ScrollView控件,重写了requestChildFocus方法:
@Override
public void requestChildFocus(View child, View focused) {
if (!mIsLayoutDirty) {
scrollToChild(focused);
} else {
mChildToScrollTo = focused;
}
super.requestChildFocus(child, focused);
}
因为在addViewInner之前调用了requestLayout()方法:
@Override
public void requestLayout() {
mIsLayoutDirty = true;
super.requestLayout();
}
所以我们在执行requestChildFocus的时候,会进入else的判断,mChildToScrollTo = focused。
2.接下来我们继续分析下mParent.requestChildFocus(this, focused)方法?
android.view.ViewGroup{
@Override
public void requestChildFocus(View child, View focused) {
if (DBG) {
System.out.println(this + " requestChildFocus()");
}
if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
return;
}
// Unfocus us, if necessary
super.unFocus(focused);
// We had a previous notion of who had focus. Clear it.
if (mFocused != child) {
if (mFocused != null) {
mFocused.unFocus(focused);
}
mFocused = child;
}
if (mParent != null) {
mParent.requestChildFocus(this, focused);
}
}
}
首先,我们会判断ViewGroup的descendantFocusability属性,如果是FOCUS_BLOCK_DESCENDANTS值的话,直接就返回了(这部分后面会解释,也是android:descendantFocusability="blocksDescendants"属性能解决自动滑动的原因),我们先来看看if (mParent != null)mParent.requestChildFocus(this, focused)}成立的情况,这里会一直调用,直到调用到ViewRootImpl的requestChildFocus方法
@Override
public void requestChildFocus(View child, View focused) {
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "Request child focus: focus now " + focused);
}
checkThread();
scheduleTraversals();
}
scheduleTraversals()会启动一个runnable,执行performTraversals方法进行view树的重绘制。
3.那么ScrollView为什么会滑到获取焦点的子view的位置了?
答:通过上面的分析,我们可以看到当Scrollview中包含有焦点的view的时候,最终会执行view树的重绘制,所以会调用view的onLayout方法,我们看下ScrollView的onLayout方法
android.view.ScrollView{
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
......
if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
scrollToChild(mChildToScrollTo);
}
mChildToScrollTo = null;
......
}
}
从第一步我们可以看到,我们在requestChildFocus方法中,是对mChildToScrollTo进行赋值了,所以这个时候,我们会进入到if判断的执行,调用scrollToChild(mChildToScrollTo)方法:
private void scrollToChild(View child) {
child.getDrawingRect(mTempRect);
offsetDescendantRectToMyCoords(child, mTempRect);
int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
if (scrollDelta != 0) {
scrollBy(0, scrollDelta);
}
}
很明显,当前的方法就是将ScrollView移动到获取制定的view当中,在这里我们可以明白了,为什么ScrollView会自动滑到获取焦点的子view的位置了。
4.为什么在ScrollView的子viewGroup中增加android:descendantFocusability=”blocksDescendants”属性能阻止ScrollView的自动滑动呢?
答:如第一步所说的,view的绘制原理:是view树的层级绘制,是绘制树的最顶端,也就是子view,然后父view绘制的机制,所以我们在ScrollView的直接子view设置android:descendantFocusability=”blocksDescendants”属性的时候,这个时候直接return了,就不会再继续执行父view也就是ScrollView的requestChildFocus(View child, View focused)方法了,导致下面的自动滑动就不会触发了。
@Override
public void requestChildFocus(View child, View focused) {
......
if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
return;
}
......
if (mParent != null) {
mParent.requestChildFocus(this, focused);
}
}
5.相信在这里有不少人有疑问了:如果是按照博主你的解释,是不是在ScrollView上面加android:descendantFocusability=”blocksDescendants”属性也能阻止自动滑动呢?
答:按照前面的分析的话,似乎是可以的,但是翻看ScrollView的源码,我们可以看到
private void initScrollView() {
mScroller = new OverScroller(getContext());
setFocusable(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
final ViewConfiguration configuration = ViewConfiguration.get(mContext);
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mOverscrollDistance = configuration.getScaledOverscrollDistance();
mOverflingDistance = configuration.getScaledOverflingDistance();
}
当你开心的设置android:descendantFocusability=”blocksDescendants”属性以为解决问题了,但是殊不知人家ScrollView的代码里面将这个descendantFocusability属性又设置成了FOCUS_AFTER_DESCENDANTS,所以你在xml中增加是没有任何作用的。
6.从上面我们分析了,ScrollView一加载就会滑动到获取焦点的子view的位置了,也明白了增加android:descendantFocusability="blocksDescendants"属性能阻止ScrollView会自动滚动到获取焦点的子view的原因,但是为什么在获取焦点的子view外面套一层view,然后增加focusableInTouchMode=true属性也可以解决这样的滑动呢?
答:我们注意到,调用addViewInner方法的时候,会先判断view.hasFocus(),其中view.hasFocus()的判断有两个规则:1.是当前的view在刚显示的时候被展示出来了,hasFocus()才可能为true;2.同一级的view有多个focus的view的话,那么只是第一个view获取焦点。
如果在布局中view标签增加focusableInTouchMode=true属性的话,意味这当我们在加载的时候,标签view的hasfocus就为true了,然而当在获取其中的子view的hasFocus方法的值的时候,他们就为false了。(这就意味着scrollview虽然会滑动,但是滑动到添加focusableInTouchMode=true属性的view的位置,如果view的位置就是填充了scrollview的话,相当于是没有滑动的,这也就是为什么在外布局增加focusableInTouchMode=true属性能阻止ScrollView会自动滚动到获取焦点的子view的原因)所以在外部套一层focusableInTouchMode=true并不是严格意义上的说法,因为虽然我们套了一层view,如果该view不是铺满的scrollview的话,很可能还是会出现自动滑动的。所以我们在套focusableInTouchMode=true属性的情况,最好是在ScrollView的直接子view 上添加就可以了。
总结
通过上面的分析,其实我们可以得到多种解决ScrollView会自动滚动到获取焦点的子view的方法,比如自定义重写Scrollview的requestChildFocus方法,直接返回return,就能中断Scrollview的自动滑动,本质上都是中断了ScrollView重写的方法requestChildFocus的进行,或者是让Scrollview中铺满ScrollView的子view获取到焦点,这样虽然滑动,但是滑动的距离只是为0罢了,相当于没有滑动罢了。**
同理我们也可以明白,如果是RecyclerView嵌套了RecyclerView,导致自动滑动的话,那么RecyclerView中也应该重写了requestChildFocus,进行自动滑动的准备。也希望大家通过阅读源码自己验证。
整理下3种方法:
第一种.
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:id="@+id/ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusableInTouchMode="true"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
第二种.
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:id="@+id/ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
第三种.
public class StopAutoScrollView extends ScrollView {
public StopAutoScrollView(Context context) {
super(context);
}
public StopAutoScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public StopAutoScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void requestChildFocus(View child, View focused) {
}
}
掘金首发如果觉得有用,请点个赞或者关注下
ScrollView(RecyclerView等)为什么会自动滚动原理分析,还有阻止自动滑动的解决方案的更多相关文章
- springboot创建,自动装配原理分析,run方法启动
使用IDEA快速创建一个springboot项目 创建Spring Initializr,然后一直下一步下一步直至完成 选择web,表示创建web项目 运行原理分析 我们先来看看pom.xml文件 核 ...
- spring boot实战(第十三篇)自动配置原理分析
前言 spring Boot中引入了自动配置,让开发者利用起来更加的简便.快捷,本篇讲利用RabbitMQ的自动配置为例讲分析下Spring Boot中的自动配置原理. 在上一篇末尾讲述了Spring ...
- Spring Boot自动配置原理、实战
Spring Boot自动配置原理 Spring Boot的自动配置注解是@EnableAutoConfiguration, 从上面的@Import的类可以找到下面自动加载自动配置的映射. org.s ...
- PDF如何自动滚动阅读
PDF如何自动滚动阅读 视图---页面显示---自动滚动 快捷键 Ctrl +shift+H
- 4、Spring Boot 2.x 自动配置原理
1.4 Spring Boot 自动配置原理 简介 spring boot自动配置功能可以根据不同情况来决定spring配置应该用哪个,不应该用哪个,举个例子: Spring的JdbcTemplate ...
- springboot自动装配原理回顾、配置文件分析
配置文件 spring boot官方文档 官方外部配置文件说明参考文档 自动配置原理分析 1. SpringBoot启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfigurat ...
- RecyclerView常见问题解决方案,RecyclerView嵌套自动滚动,RecyclerView 高度设置wrap_content 无作用等问题
1,ScrollView或者RecyclerView1 嵌套RecyclerView2 进入页面自动跳转到recyclerView2上面页面会自动滚动貌似是RecyclerView 自动获得了焦点两 ...
- 解决点击状态栏时ScrollView自动滚动到初始位置失效办法
http://www.cocoachina.com/ios/20150807/12949.html 取消点击状态栏scrollView会自动滚动到初始位置的功能 _scrollView.scrolls ...
- 界面为ScrollView时打开界面会自动滚动到底部之解决方法
开发中遇到了这样的一个问题,界面最外层是ScrollView,然后里面有嵌套了一个ListView还有其他可以获取焦点的View,然后每次打开界面都会自动滚动到最底部,经过一番折腾,发现了一个简单的方 ...
随机推荐
- MicroPython-GPRS教程之TPYBoardv702GPRS功能测试
一.什么是TPYBoardV702 TPYBoardV702是目前市面上唯一支持通信通信功能的MicroPython开发板:支持Python3.0及以上版本直接运行.支持GPS+北斗双模通信.GPRS ...
- 使用dropwizard(4)-加入测试-jacoco代码覆盖率
前言 dropwizard提供了一个简单的测试框架.这里简单集成并加入jacoco测试. Demo source https://github.com/Ryan-Miao/l4dropwizard 本 ...
- Transact-SQL参考--学习笔记
基本的就不累赘了. 运算符 除法: dividend / divisor 如果用一个整数的 divisor 去除整数的 dividend,其结果是一个整数,小数部分被截断,如果要有小数可以将divid ...
- Qt 5中信号和槽的新语法
QT 是一个跨平台的 C++ GUI 应用构架,它提供了丰富的窗口部件集,具有面向对象.易于扩展.真正的组件编程等特点,更为引人注目的是目前 Linux 上最为流行的 KDE 桌面环境就是建立在 QT ...
- Chris Richardson微服务翻译:微服务之事件驱动的数据管理
Chris Richardson 微服务系列翻译全7篇链接: 微服务介绍 构建微服务之使用API网关 构建微服务之微服务架构的进程通讯 微服务架构中的服务发现 微服务之事件驱动的数据管理(本文) 微服 ...
- .net的retrofit--WebApiClient库
# 库简介 WebApiClient是开源在github上的一个httpClient客户端库,内部基于HttpClient开发,是一个只需要定义c#接口(interface),并打上相关特性,即可异步 ...
- CentOS7服务去Nginx使用-安装
1.官网下载目前稳定版本nginx 这里我使用1.12.1版本进行安装. 上传到linux的/mnt/pack/nginx这个目录,主要是存放linux安装相关的文件. 可以使用window下载好上传 ...
- .Net IOC框架入门之一 Unity
一.概述 IOC:英文全称:Inversion of Control,中文名称:控制反转,它还有个名字叫依赖注入(Dependency Injection). 作用:将各层的对象以松耦合的方式组织在一 ...
- 一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](五)
前言 Hi,大家好,我是Rector 时间飞逝,一个星期又过去了,今天还是星期五,Rector在图享网继续跟大家分享系列文本:一步一步创建ASP.NET MVC5程序[Repository+Autof ...
- 【JavaScript的引入方式】
javascript: 是基于对象和事件驱动的客户端脚本[组成] Bom:浏览对象模型(与浏览器交互的方法和接口) Dom:文档对象模型(处理网页内容的方法和接口) ecma:核心(描述了js的语 ...