NestedScrollView嵌套RecyclerView出现的坑

天气渐寒,然学习不可懈怠,记录一下使用NestedScrollView嵌套RecyclerView的两个问题,以后遇到可以来这里温故.

  应该说在MD中,RecyclerView代替了ListView,而NestedScrollView代替了ScrollView,他们两个都可以用来跟ToolBar交互,实现上拉下滑中ToolBar的变化。在NestedScrollView的名字中其实就可以看出他的作用了,Nested是嵌套的意思,而ToolBar基本需要嵌套使用.

  • 问题一,使用NestedScrollView嵌套RecyclerView时,滑动lRecyclerView列表会出现强烈的卡顿感.

体验极其不流畅,这不是我们希望的.于是,百度了一下轻松找到解决办法.

mRecyclerView.setNestedScrollingEnabled(false);

加上这句之后,整个世界都平静了,非常流畅啊!

这里面做了啥?

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild {
...
}

点击RecyclerView源码,发现其实现了ScrollingView和NestedScrollingChild两个接口.

ScrollingView点进源码查看.

复制代码
public interface ScrollingView {
    /**
     * <p>Compute the horizontal range that the horizontal scrollbar
     * represents.</p>
     *
     * @return the total horizontal range represented by the horizontal
     *         scrollbar
     *
     * @see #computeHorizontalScrollExtent()
     * @see #computeHorizontalScrollOffset()
     * @see android.widget.ScrollBarDrawable
     */
    int computeHorizontalScrollRange();

    /**
     * <p>Compute the horizontal offset of the horizontal scrollbar's thumb
     * within the horizontal range. This value is used to compute the position
     * of the thumb within the scrollbar's track.</p>
     * @return the horizontal offset of the scrollbar's thumb
     *
     * @see #computeHorizontalScrollRange()
     * @see #computeHorizontalScrollExtent()
     * @see android.widget.ScrollBarDrawable
     */
    int computeHorizontalScrollOffset();

    /**
     * <p>Compute the horizontal extent of the horizontal scrollbar's thumb
     * within the horizontal range. This value is used to compute the length
     * of the thumb within the scrollbar's track.</p>
     *
     * @return the horizontal extent of the scrollbar's thumb
     *
     * @see #computeHorizontalScrollRange()
     * @see #computeHorizontalScrollOffset()
     * @see android.widget.ScrollBarDrawable
     */
    int computeHorizontalScrollExtent();

    /**
     * <p>Compute the vertical range that the vertical scrollbar represents.</p>
     *
     *
     * @return the total vertical range represented by the vertical scrollbar
     *
     * <p>The default range is the drawing height of this view.</p>
     *
     * @see #computeVerticalScrollExtent()
     * @see #computeVerticalScrollOffset()
     * @see android.widget.ScrollBarDrawable
     */
    int computeVerticalScrollRange();

    /**
     * <p>Compute the vertical offset of the vertical scrollbar's thumb
     * within the horizontal range. This value is used to compute the position
     * of the thumb within the scrollbar's track.</p>
     *
     * @return the vertical offset of the scrollbar's thumb
     *
     * @see #computeVerticalScrollRange()
     * @see #computeVerticalScrollExtent()
     * @see android.widget.ScrollBarDrawable
     */
    int computeVerticalScrollOffset();

    /**
     * <p>Compute the vertical extent of the vertical scrollbar's thumb
     * within the vertical range. This value is used to compute the length
     * of the thumb within the scrollbar's track.</p>
     *
     *
     * @return the vertical extent of the scrollbar's thumb
     *
     * @see #computeVerticalScrollRange()
     * @see #computeVerticalScrollOffset()
     * @see android.widget.ScrollBarDrawable
     */
    int computeVerticalScrollExtent();
}
复制代码

可以看出该接口主要关联横向和纵向滑动距离的计算.那NestedScrollingChild呢?这是个什么?

点进其源码,经过一顿翻译和分析,发现其定义的方法并不多:

复制代码
public interface NestedScrollingChild {  
    /** 
     * 设置嵌套滑动是否能用
     * 
     *  @param enabled true to enable nested scrolling, false to disable
     */  
    public void setNestedScrollingEnabled(boolean enabled);  
  
    /** 
     * 判断嵌套滑动是否可用 
     * 
     * @return true if nested scrolling is enabled
     */  
    public boolean isNestedScrollingEnabled();  
  
    /** 
     * 开始嵌套滑动
     * 
     * @param axes 表示方向轴,有横向和竖向
     */  
    public boolean startNestedScroll(int axes);  
  
    /** 
     * 停止嵌套滑动 
     */  
    public void stopNestedScroll();  
  
    /** 
     * 判断是否有父View 支持嵌套滑动 
     * @return whether this view has a nested scrolling parent
     */  
    public boolean hasNestedScrollingParent();  
  
    /** 
     * 在子View的onInterceptTouchEvent或者onTouch中,调用该方法通知父View滑动的距离
     *
     * @param dx  x轴上滑动的距离
     * @param dy  y轴上滑动的距离
     * @param consumed 父view消费掉的scroll长度
     * @param offsetInWindow   子View的窗体偏移量
     * @return 支持的嵌套的父View 是否处理了 滑动事件 
     */  
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);  

    /** 
     * 子view处理scroll后调用
     *
     * @param dxConsumed x轴上被消费的距离(横向) 
     * @param dyConsumed y轴上被消费的距离(竖向)
     * @param dxUnconsumed x轴上未被消费的距离 
     * @param dyUnconsumed y轴上未被消费的距离 
     * @param offsetInWindow 子View的窗体偏移量
     * @return  true if the event was dispatched, false if it could not be dispatched.
     */  
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,  
          int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);  
  

  
    /** 
     * 滑行时调用 
     *
     * @param velocityX x 轴上的滑动速率
     * @param velocityY y 轴上的滑动速率
     * @param consumed 是否被消费 
     * @return  true if the nested scrolling parent consumed or otherwise reacted to the fling
     */  
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);  
  
    /** 
     * 进行滑行前调用
     *
     * @param velocityX x 轴上的滑动速率
     * @param velocityY y 轴上的滑动速率 
     * @return true if a nested scrolling parent consumed the fling
     */  
    public boolean dispatchNestedPreFling(float velocityX, float velocityY);  
}
复制代码

再回头看下RecyclerView中

复制代码
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild {

public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    if (attrs != null) {
        TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0);
        mClipToPadding = a.getBoolean(0, true);
        a.recycle();
    } else {
        mClipToPadding = true;
    }
    setScrollContainer(true);
  //默认获取焦点 setFocusableInTouchMode(
true); final ViewConfiguration vc = ViewConfiguration.get(context); mTouchSlop = vc.getScaledTouchSlop(); mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER); mItemAnimator.setListener(mItemAnimatorListener); initAdapterManager(); initChildrenHelper(); // If not explicitly specified this view is important for accessibility. if (ViewCompat.getImportantForAccessibility(this) == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); } mAccessibilityManager = (AccessibilityManager) getContext() .getSystemService(Context.ACCESSIBILITY_SERVICE); setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this)); // Create the layoutManager if specified. boolean nestedScrollingEnabled = true; if (attrs != null) { int defStyleRes = 0; TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView, defStyle, defStyleRes); String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager); int descendantFocusability = a.getInt( R.styleable.RecyclerView_android_descendantFocusability, -1); if (descendantFocusability == -1) { setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); } a.recycle(); createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes); if (Build.VERSION.SDK_INT >= 21) { a = context.obtainStyledAttributes(attrs, NESTED_SCROLLING_ATTRS, defStyle, defStyleRes); nestedScrollingEnabled = a.getBoolean(0, true); a.recycle(); } } else { setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); }   //默认支持嵌套滚动 // Re-set whether nested scrolling is enabled so that it is set on all API levels setNestedScrollingEnabled(nestedScrollingEnabled); }   @Override public void setNestedScrollingEnabled(boolean enabled) { getScrollingChildHelper().setNestedScrollingEnabled(enabled); } @Override public boolean isNestedScrollingEnabled() { return getScrollingChildHelper().isNestedScrollingEnabled(); } @Override public boolean startNestedScroll(int axes) { return getScrollingChildHelper().startNestedScroll(axes); } @Override public void stopNestedScroll() { getScrollingChildHelper().stopNestedScroll(); } @Override public boolean hasNestedScrollingParent() { return getScrollingChildHelper().hasNestedScrollingParent(); } @Override public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); } @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); } @Override public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed); } @Override public boolean dispatchNestedPreFling(float velocityX, float velocityY) { return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY); }
复制代码

这是全部都交给getScrollingChildHelper()这个方法的返回对象处理了啊,看看这个方法是怎么实现的。

private NestedScrollingChildHelper getScrollingChildHelper() {
        if (mScrollingChildHelper == null) {
            mScrollingChildHelper = new NestedScrollingChildHelper(this);
        }
        return mScrollingChildHelper;
    }

这样的话.NestedScrollingChild 接口的方法都交给NestedScrollingChildHelper这个代理对象处理了.

那么到这个类的源码中定位到我们关注方法的实现;

复制代码
public class NestedScrollingChildHelper {
   private final View mView;
private ViewParent mNestedScrollingParent;
private boolean mIsNestedScrollingEnabled;
private int[] mTempNestedScrollConsumed;

/**
* Construct a new helper for a given view.
*/
public NestedScrollingChildHelper(View view) {
mView = view;
}

/**
* Enable nested scrolling.
*
* <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
* method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same
* signature to implement the standard policy.</p>
*
* @param enabled true to enable nested scrolling dispatch from this view, false otherwise
*/
public void setNestedScrollingEnabled(boolean enabled) {
if (mIsNestedScrollingEnabled) {
ViewCompat.stopNestedScroll(mView);
}
mIsNestedScrollingEnabled = enabled;
}
  /**
  * Check if nested scrolling is enabled for this view.
  *
  * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
  * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same
  * signature to implement the standard policy.</p>
  *
  * @return true if nested scrolling is enabled for this view
  */
  public boolean isNestedScrollingEnabled() {
return mIsNestedScrollingEnabled;
  }
.
.
.
}
复制代码

可以看出RecyclerView默认是setNestedScrollingEnabled(true),是支持嵌套滚动的,也就是说当它嵌套在NestedScrollView中时,默认会随着NestedScrollView滚动而滚动,放弃了自己的滚动.

所以给我们的感觉就是滞留、卡顿.主动将该值置false可以有效解决该问题.

  • 问题二,使用NestedScrollView嵌套RecyclerView时,每次打开界面都是定位在RecyclerView在屏幕顶端,列表上面的布局都被顶上去了.

这个问题花了我不少时间去查原因,最终定位到是RecyclerView抢占了焦点,自动滚动导致的.

查看RecyclerView的源码发现,它会在构造方法中调用setFocusableInTouchMode(true),所以抢到焦点后一定会定位到第一行的位置突出RecyclerView的显示

解决方法就是NestScrollView节点添加

android:focusableInTouchMode="true"

然后在NestScrollView的子节点view添加:

android:descendantFocusability="blocksDescendants"

或者 直接mRecyclerVIew.setFocusableInTouchMode(false)

  • 总结

不明白的地方还是要多看源码 , 看源码有几个好处.第一点,有些问题只能从源码里找出究竟 , 一点一点剖析才能看出问题的根本, 弄透一个接口的作用 , 那么有类似需求的时候,可以自定义控件实现该接口;再一个,就是在看的过程中, 我们会熟悉源码的风格,命令的方式和逻辑的先后 .总之 , 看一个好轮子的源码 , 是为了写轮子做准备.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 嵌套ScrollViewRecyclerView时,可以使用NestedScrollView来解决滑动冲突的问题。NestedScrollView是Android 5.引入的一个新控件,它可以嵌套其他可滑动的控件,如RecyclerView、ListView等。在嵌套时,需要在RecyclerView的外层包裹一个NestedScrollView,并在NestedScrollView中设置app:layout_behavior="@string/appbar_scrolling_view_behavior"属性,以便与AppBarLayout联动。同时,需要在RecyclerView中设置android:nestedScrollingEnabled="false"属性,以禁止RecyclerView自身的滑动事件。这样就可以实现NestedScrollView嵌套RecyclerView的效果了。 ### 回答2: NestedScrollView是一个可以嵌套滚动的视图容器,可以方便地实现嵌套滚动的效果。而RecyclerView是一个能够高效地维护大量数据的控件,它可以展示大数据集合,并且拥有灵活的布局以及可定制性。因此,嵌套ScrollViewRecyclerView的需求是非常普遍的。 在使用NestedScrollView嵌套RecyclerView的时候,需要注意以下几个问题: 1.布局的设置 嵌套布局时需要设置正确的布局层级,例如可以将NestedScrollView作为父布局,让它的孩子是RecyclerView。 2.增加嵌套滑动事件 NestedScrollView默认不具备嵌套滑动事件,因此需要在RecyclerView中增加相应的滑动事件来实现嵌套滑动。 3.优化RecyclerView性能 在RecyclerView中可能会存在大量的数据,因此需要做一些性能优化。例如可以使用ViewHolder来重用子视图,使用LayoutManager来管理子视图的布局等等。 4.合理设置RecyclerView高度 由于RecyclerView的高度需要根据具体情况来设置,因此需要根据每个item的高度来设置RecyclerView的高度,否则可能会出现一些问题。 综上所述,NestedScrollView嵌套RecyclerView需要考虑的问题比较多,需要开发者综合考虑各种情况来尽可能达到良好的用户体验和流畅的界面效果。 ### 回答3: NestedScrollView嵌套RecyclerView是常见的布局方式,可以应用在需要列表上下滑动,同时支持整个界面上下滑动的情况下。 NestedScrollView是Android 5.0之后提供的用于嵌套滑动的控件,它继承自ScrollView,但比ScrollView功能更强大,可以实现更复杂的嵌套滑动效果。而RecyclerView是Android系统提供的一个列表控件,比ListView具有更好的性能和灵活性。 在嵌套RecyclerView时,我们需要先将RecyclerView的滑动事件交给外层的NestedScrollView来处理,这可以通过给RecyclerView设置一个OnTouchListener来实现。 同时,在NestedScrollView中,我们可以设置子view这个属性app:layout_behavior="@string/appbar_scrolling_view_behaviour",也就是将NestedScrollView的滑动事件交给外层的AppBarLayout来处理。 在使用上,我们需要了解两个重要的方法:onNestedPreScroll和onNestedPreFling。这两个方法分别用于处理子view传递过来的滑动事件,我们需要在这两个方法中做出相应处理,从而实现NestedScrollView内部RecyclerView与外部AppBarLayout的联动效果。 另外,为了保证RecyclerView能够完全显示出来,我们还需要在RecyclerView的外层布局中设置android:layout_height="wrap_content"属性。 综上所述,NestedScrollView嵌套RecyclerView需要注意以下几点:将RecyclerView的滑动事件交给外层的NestedScrollView处理;设置子view的app:layout_behavior;重写onNestedPreScroll和onNestedPreFling方法;设置RecyclerView的外层布局高度为wrap_content。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值