专栏首页Android 开发学习NestedScrollView 嵌套 ListView 实现滑动折叠效果

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"

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • ConstraintLayout 使用简介一 背景二 demo三 进一步升级打怪四 更多

    用户1127566
  • IjkPlayer 源码阅读一 初始化

    用户1127566
  • Gradle plugin自定义

    用户1127566
  • PopUpWindow的使用

    PopupWindow这个类用来实现一个弹出框,可以使用任意布局的View作为其内容,这个弹出框是悬浮在当前activity之上的。

    提莫队长
  • Android开发(1) - 登录界面的布局演示

    android开发真的是让人觉得很有乐趣的事情呢。比起windows mobile好玩多了。虽然哪个所见即所得的视图设计环境糟糕的可以。不过这样用类似描述语言(...

    zhangyunfeiVir
  • Android 9.0 SystemUI 主要视图 SystemBars

    SystemBars 是 SystemUI 主要视图,接下来看看它是如何呈现?基于 AOSP 9.0 分析。

    吴小龙同學
  • Android实现横向滑动卡片效果

    最近项目上需要实现这样效果的一个页面,本来想找个现成的两下搞定,但是问了半天度娘也没招,索性自己琢磨琢磨(这里边也少不了同事的帮助),先把最终的效果图贴上:

    砸漏
  • Android编程双重单选对话框布局实现与事件监听方法示例

    本文实例讲述了Android编程双重单选对话框布局实现与事件监听方法。分享给大家供大家参考,具体如下:

    砸漏
  • 学习使用Material Design控件(四)Android实现标题栏自动缩放、放大效果

    CoordinatorLayout 组织它的子views之间协作的一个Layout,它可以给子View切换提供动画效果。 AppBarLayout 可以让...

    砸漏
  • Android Studio 3.5格式化布局代码时错位、错乱bug的解决

    设置 code style XML 右上角 Set from然后选择Predefined Style… Android即可

    砸漏

扫码关注云+社区

领取腾讯云代金券