NestedScrollView嵌套ListView可行性总结

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NestedScrollView嵌套ListView可行性总结相关的知识,希望对你有一定的参考价值。

由于公司项目遗留代码仍然使用PullToRefreshListView(后文简称PTRLV),且存在复用,更换RecyclerView成本太大,同时又想使用CoordinatorLayout来实现一些嵌套滑动效果,所以研究了NestedScrollView嵌套PTRLV的方案。

对于NestedScrollView嵌套普通的ListView,常见的问题有:

  1. 嵌套后ListView只显示一行。
  2. ListView无法滑动。

网上的解决方案主要是2种:

  1. 重写ListView的onMeasure方法。

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
     super.onMeasure(widthMeasureSpec, expandSpec);
 }

将ListView所有子View的高度以及分隔线的高度相加,重新设置给ListView。

private void setListViewHeightBasedOnChildren(ListView listView) {
     ListAdapter listAdapter = listView.getAdapter(); //获得Adapter
     if (listAdapter == null) { 
         return;
     }
     int totalHeight = 0;
     for (int i = 0; i < listAdapter.getCount(); i++) {
         View listItem = listAdapter.getView(i, null, listView);
         listItem.measure(0, 0);
         totalHeight += listItem.getMeasuredHeight();
     }
     ViewGroup.LayoutParams params = listView.getLayoutParams();
     params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
     listView.setLayoutParams(params);
 }
  1. 这种方法要求子View的根布局必须是LinearLayout。

这2种方法乍看之下也都能解决NestedScrollView嵌套PTRLV的问题,但其实局限性很大,项目中实践后发现场景稍有变化就仍然有问题。

主要有:

  1. 有些场景下展示的数据并不会在Activity的onCreate方法中就获取,例如搜索页需要用户输入关键字后才去获取数据,然后才将数据给到PTRLV展示,这时PTRLV只显示半行。
  2. 在PTRLV中改写添加的预加载功能失效。

第1个问题出现的原因在于,上述2种解决方案都是改变PTRLV中真正的ListView的大小,让它能够将全部的子View展现出来。在这个场景中,界面初始化时,ListView中是没有数据的,所以在measure时ListView本身大小为0,只测量出了PTRLV的那个滑动指示块的高度,所以PTRLV只有那么高。

那为什么获取到数据后再notify还是只有这么高呢?因为PTRLV在真正的ListView之外还有两层FrameLayout。虽然ListView因为重写了onMeasure方法已经展示全了,但这两层FrameLayout还是只有滑块那么高,所以PTRLV看起来还是没有改变。

解决方案为:
重写PTRLV内部的FrameLayout的onMeasure方法,跟ListView一样就行

public class NoScrollFrameLayout extends FrameLayout {

    public NoScrollFrameLayout(Context context) {
        super(context);
    }

    public NoScrollFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
    }
}

在PTRLV的父类PullToRefreshBase中改写addRefreshableView方法

private void addRefreshableView(Context context, T refreshableView) {
    mRefreshableViewWrapper = new NoScrollFrameLayout(context);
    mRefreshableViewWrapper.addView(refreshableView, ViewGroup.LayoutParams.MATCH_PARENT,
        ViewGroup.LayoutParams.MATCH_PARENT);

    addViewInternal(mRefreshableViewWrapper, new LayoutParams(LayoutParams.MATCH_PARENT,
        LayoutParams.MATCH_PARENT));
}

第2个问题出现的原因是因为滑动事件的冲突。这里可能会有疑问,为什么还说滑动事件有冲突,通过上述方案不是已经可以正常滑动了吗?那是因为那只是看上去像ListView的滑动,其实是NestedSrcollView在滑动,ListView根本没有动。

从第1个问题的原因中我们已经知道了,解决NestedScrollView中嵌套ListView显示不全问题的方法,其本质都是手动修改ListView的内容的高度,而且不是将高度match_parent,而是将ListView的子View全部加载进来,假如有50个数据,就一次性加载50个子View,所以ListView就相当于一个有50个子View的LinearLayout。(当然也就不会有复用,性能很差)

另一方面,NestedSrcollView在onInterceptTouchEvent中拦截了所有的Touch事件,ListView没有分发到任何事件,所以虽然看上去像ListView在滑动,实际上是ScrollView包裹着相当于LinearLayout的ListView在滑动,而由于我们项目的预加载逻辑写在ListView的onScrollStateChange方法中,没有分到任何Touch事件的情况下根本不会调用onScrollStateChange,因此预加载就失效了。

这个问题在不改动预加载逻辑实现位置的情况下是无解的。同时也可以得出:如果你在ListView的Touch事件中有自定义的逻辑,那么请不要用NestedSrcollView嵌套ListView,因为NestedSrcollView会拦截Touch事件,所有的滑动解决方案并没有真正解决滑动冲突,ListView是获取不到Touch事件的。

以上是关于NestedScrollView嵌套ListView可行性总结的主要内容,如果未能解决你的问题,请参考以下文章

PHP.DHN透过 NestedScrollView 源码解析嵌套滑动原理-附教程

PHP.DHN透过 NestedScrollView 源码解析嵌套滑动原理-附教程

Android番外篇 NestedScrollView嵌套RecyclerView

Android之解决NestedScrollView嵌套RecyclerView部分手机返回到这个页面Recyclerview顶部,而不是页面NestedScrollView顶部

Android之解决NestedScrollView嵌套RecyclerView部分手机返回到这个页面Recyclerview顶部,而不是页面NestedScrollView顶部

NestedScrollView嵌套ListView滑动冲突