NestedScrollView 嵌套 ListView 实现滑动折叠效果

引言

最近,在做公司一个design折叠效果的时候遇到个问题,就是我们本身app的方法数太多了,dex分包技术还没搞定。不得不尽量缩减一些不必要的包、类。当我们引入RecyclerView的时候,恰好是压死骆驼的最后一根稻草,故不得不采用其他方案来代替RecyclerView 和 CollapsingToolbarLayout实现的折叠效果。本文试着采用 NestedScrollView 嵌套 ListView的方法来实现折叠效果。具体结果如图所示:

布局文件

如下所示:

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="270dp"
        android:fitsSystemWindows="true">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#F03843"
            app:contentScrim="#F03843"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="0.7"/>
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:minHeight="?attr/actionBarSize"
                app:contentInsetEnd="0dp"
                app:contentInsetStart="0dp"
                app:layout_collapseMode="pin">

                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">

                    <ImageButton
                        android:id="@+id/toolbar_import_navigation"
                        android:layout_width="?attr/actionBarSize"
                        android:layout_height="?attr/actionBarSize"
                        android:layout_alignParentLeft="true"
                        android:layout_alignParentStart="true"
                        android:layout_centerVertical="true"
                        android:src="@drawable/icon_toolbar_navigation"/>
                    <TextView
                        android:id="@+id/toolbar_title"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_centerInParent="true"
                        android:gravity="center"
                        android:lineSpacingMultiplier="1.5"
                        android:text="@string/select_category"
                        android:textColor="@color/dark_gray"
                        android:textSize="@dimen/text_40px"/>
                    <TextView
                        android:id="@+id/toolbar_import"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_alignParentEnd="true"
                        android:layout_alignParentRight="true"
                        android:layout_centerVertical="true"
                        android:layout_marginRight="16dp"
                        android:lineSpacingMultiplier="1.5"
                        android:text="@string/vocabulary_import"
                        android:textColor="@color/dark_gray"
                        android:textSize="@dimen/text_32px"/>
                </RelativeLayout>
            </android.support.v7.widget.Toolbar>
            <android.support.design.widget.TabLayout
                android:id="@+id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:tabGravity="fill" />
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true"
        android:layout_marginTop="2dp"
        android:id="@+id/vocabulary_nested_scroll"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/parent_list_view"
            android:orientation="vertical">
            <NoScrollListView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:id="@+id/list" />
        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

上述布局需要注意的是:

  • 必须采用CoordinatorLayout作为外层包裹,至于原因是由于使用Behavior需要。
  • AppBarLayout做一个兼容。
  • android:fitsSystemWindows,经本人代码测试,表示CollapsingToolbarLayout的上边界是否扩展到statusbar,这里如果想使用透明的statusbar,这里更新下应该该布局的根布局的该属性为true,不过这里兼容性还是存在问题的,特别是android 4.4版本的折叠布局的透明statusbar,不知道改为有没更好的方式。
  • app:contentScrim 表示CollapsingToolbarLayout折叠以后,toolbar的颜色。
  • app:layout_scrollFlags="scroll|exitUntilCollapsed" 表示CoordinatorLayout的依赖元素滚动的时候,进行折叠。
    scroll - 想滚动就必须设置这个。 enterAlways - 实现quick return效果, 当向下移动时,立即显示View(比如Toolbar). exitUntilCollapsed - 向上滚动时收缩View,但可以固定Toolbar一直在上面。 enterAlwaysCollapsed - 当你的View已经设置minHeight属性又使用此标志时,你的View只能以最小高度进入,只有当滚动视图到达顶部时才扩大到完整高度。(参见: http://android.jobbole.com/82193/
  • layout_collapseMode,pin表示不动,parallax视差效果
  • 将 app:layout_behavior="@string/appbar_scrolling_view_behavior"指定给NestedScrollView,即当该控件滑动的时候,其他CollapsingToolbarLayout内的子view做相应的改变如(视差)或pin(不变)。

解决NestedScrollView嵌套listView问题。

   貌似网上一抓一大把,本文采用其中之一方案,复写listView。
public class NoScrollListView extends ListView {
    public NoScrollListView(Context context) {
        super(context);
    }

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

    public NoScrollListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

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

解决NestedScrollView嵌套的listView滚动时无响应bug。

private void adjustParentView() { //兼容NestedscrollView
    int actionBarHeight = 0;
    TypedValue tv = new TypedValue();
    if (getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
        actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,getResources().getDisplayMetrics());
    }
    Rect outRect = new Rect();  //状态栏高度
    getWindow().getDecorView().getWindowVisibleDisplayFrame(outRect);
    mParentView.setMinimumHeight(DeviceUtils.screenHeight() - actionBarHeight - outRect.top);
}

注:直接使用 RecyclerView更省事省力。

题外话:直接使用RecyclerView折叠动画不平滑问题

在stackoverflow上找到如下解决方案,大致是由于google官方留的Behavior坑。

public final class FlingBehavior extends AppBarLayout.Behavior {
    private static final int TOP_CHILD_FLING_THRESHOLD = 3;
    private boolean isPositive;

    public FlingBehavior() {
    }

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

    @Override
    public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
        if (velocityY > 0 && !isPositive || velocityY < 0 && isPositive) {
            velocityY = velocityY * -1;
        }
        if (target instanceof RecyclerView && velocityY < 0) {
            final RecyclerView recyclerView = (RecyclerView) target;
            final View firstChild = recyclerView.getChildAt(0);
            final int childAdapterPosition = recyclerView.getChildAdapterPosition(firstChild);
            consumed = childAdapterPosition > TOP_CHILD_FLING_THRESHOLD;
        }
        return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
    }

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        isPositive = dy > 0;
    }
}

最后在AppBarLayout如下添加代码:

 <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="210dp"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:layout_behavior="com.youdao.vocabulary.widget.FlingBehavior">

题外话2

java.lang.IllegalStateException: View can not be anchored to the the parent CoordinatorLayout
当我们给一个view设置app:layout_anchor时,在最新的23.2.0包会出现问题,解决办法是用android:layout_gravity="bottom|end" 替换。

题外话3

23.2.0中在CoordinatorLayout中使用Toolbar ,toolbar无法与顶部对齐,即顶部存在垂直间距。解决方法在AppBarLayout添加 android:fitsSystemWindows="true"

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 175,049评论 25 709
  • 最新项目中用到了些Material效果,在此对自己的学习做个小结。 首先养成良好的学习习惯-----看源码: Co...
    风少侠阅读 10,264评论 5 37
  • 昨夜平行世界的我 和此间的你 一样遥远 没有一分钟拥有的 一千另一个自己 和那一千另一个你 一样干涸 我丢下了你 ...
    靳非阅读 2,405评论 0 0
  • 由暴走人员被追尾致死伤引发的思考 时间永远定格在2017年7月8日早上一瞬间,对于临沂市来说是个难忘的一天...
    我的另一半是你阅读 1,631评论 0 1
  • 在文章开始之前,大家可以先思考一个问题:你选择从事你目前职业的原因是什么?是迫于无奈?是因为它热门高薪?是感兴趣?...
    Neil彭彭阅读 8,475评论 5 29