ScrollView(RecyclerView等)为什么会自动滚动原理分析,还有阻止自动滑动的解决方案,2024Android面试心得

@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种方法: 第一种.




第二种.




第三种.

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) {
}
}
如果大家还有更好的解决方案,可以拿出来大家探讨,要是文章有不对的地方,欢迎拍砖。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

针对于上面的问题,我总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料。
(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

image

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。**

[外链图片转存中…(img-vJlqxLTk-1712550368005)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 9
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值