博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ScrollView(RecyclerView等)为什么会自动滚动原理分析,还有阻止自动滑动的解决方...
阅读量:6440 次
发布时间:2019-06-23

本文共 8311 字,大约阅读时间需要 27 分钟。

hot3.png

引言,有一天我在调试一个界面,xml布局里面包含Scroll View,里面嵌套了recyclerView的时候,界面一进去,就自动滚动到了recyclerView的那部分,百思不得其解,上网查了好多资料,大部分只是提到了解决的办法,但是对于为什么会这样,都没有一个很好的解释,本着对技术的负责的态度,花费了一点时间将前后理顺了下

1.首先在包含ScrollView的xml布局中,我们在一加载进来,ScrollView就自动滚动到获取焦点的子view的位置,那我们就需要看下我们activity的onCreate中执行了什么?

答:当我们在activity的onCreate方法中调用setContentView(int layRes)的时候,我们会调用LayoutInflater的inflate(XmlPullParser parser, 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方法:

@Overridepublic void requestChildFocus(View child, View focused) {    if (!mIsLayoutDirty) {        scrollToChild(focused);    } else {        mChildToScrollTo = focused;    }    super.requestChildFocus(child, focused);}

因为在addViewInner之前调用了requestLayout()方法:

@Overridepublic void requestLayout() {    mIsLayoutDirty = true;    super.requestLayout();}

所以我们在执行requestChildFocus的时候,会进入else的判断,mChildToScrollTo = focused。

2.接下来我们继续分析下mParent.requestChildFocus(this, focused)方法?

android.view.ViewGroup{@Overridepublic 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方法

@Overridepublic 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{@Overrideprotected 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种方法:

第一种.

第二种.

第三种.

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) {    }}

如果觉得有用,请点个赞呗

最后给大家分享一份非常系统和全面的Android进阶技术大纲及进阶资料,及面试题集

想学习更多Android知识,请加入Android技术开发交流 7520 16839

进群与大牛们一起讨论,还可获取Android高级架构资料、源码、笔记、视频

包括 高级UI、Gradle、RxJava、小程序、Hybrid、移动架构、React Native、性能优化等全面的Android高级实践技术讲解性能优化架构思维导图,和BATJ面试题及答案!

群里免费分享给有需要的朋友,希望能够帮助一些在这个行业发展迷茫的,或者想系统深入提升以及困于瓶颈的

朋友,在网上博客论坛等地方少花些时间找资料,把有限的时间,真正花在学习上,所以我在这免费分享一些架构资料及给大家。希望在这些资料中都有你需要的内容。

e3c6fd8d2525ce17431b52d575d665025e7.jpg

1240

转载于:https://my.oschina.net/u/3956562/blog/3016270

你可能感兴趣的文章
BZOJ4133 : Answer的排队
查看>>
基于Centos搭建 Mono 开发环境
查看>>
算法题:福尔摩斯的约会
查看>>
Oralce sql (+) 补充
查看>>
hdu 2665 划分树
查看>>
laravel中的plicy授权方法:
查看>>
基于R进行相关性分析--转载
查看>>
常用 cdn
查看>>
tomcat8 管理页面403 Access Denied的解决方法
查看>>
怎样避免应用冷启动
查看>>
把vux中的@font-face为base64格式的字体信息解码成可用的字体文件
查看>>
vue sync
查看>>
CentOS6下OpenLDAP+PhpLdapAdmin基本安装及主从/主主高可用模式部署记录
查看>>
Wix 安装部署教程(十一) ---QuickWix
查看>>
Spring @Value注解问题
查看>>
P1886 滑动窗口
查看>>
实施vertex compression所遇到的各种问题和解决办法
查看>>
ubuntu 12.04 rails server 时候报错 execjs
查看>>
linux下文件压缩与解压操作
查看>>
使用树莓派实现微信远程监控
查看>>