在 CollapsingToolbarLayout 的基础上实现的另一种折叠效果

本文介绍了一种特殊的CollapsingToolbarLayout折叠效果实现方法,通过监听AppBarLayout的偏移变化,调整RecyclerView的marginTop值,使卡片列表能够上下移动并覆盖AppBar。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先看一下效果图

1. 这是来自 Google 的设计文档的示例图

效果图

Z 轴透视图

2. 这是我自己的不才之作

预览图

前言

以前看 Google 的 Material Design 指导文档,看到了这么一个效果,印象挺深刻的。最近在家,又想起这么个效果,反正事也不多,所以果断着手实现它。

直接上代码

/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="?android:colorBackground"
    android:fitsSystemWindows="true"
    tools:context="com.jossing.collapsingtoolbarlayoutdemo.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/ctl"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fitsSystemWindows="true"
            app:contentScrim="@color/colorPrimary"
            app:scrimAnimationDuration="@android:integer/config_shortAnimTime"
            app:layout_scrollFlags="exitUntilCollapsed|scroll|snap">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="336dp"
                android:scaleType="centerCrop"
                android:fitsSystemWindows="true"
                app:layout_collapseMode="parallax"
                android:src="@drawable/material_design_vs_flat_design" />

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolBar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:fitsSystemWindows="false"
                app:layout_collapseMode="pin">

                <!-- Toolbar 中的自定义 View -->
                <RelativeLayout...>

            </android.support.v7.widget.Toolbar>
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <!-- 卡片列表 -->
    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="-112dp"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</android.support.design.widget.CoordinatorLayout>

以上布局的代码中,可以看到我给 ImageView 设定了固定的 height,为了让 RecyclerView 中的内容往上顶,以盖住展开的 AppBar,我还给 RecyclerView 设置了一个 -112dpmarginTop。至于为什么顶上去之后会盖住 AppBar ~,因为在 Android L 以及升级版本中,View 的绘制层次(暂且叫做绘制层次吧)是根据属性 elevation 来测量的,elevation 低的绘制在“下方”,高的绘制到“上”方。而 AppBar 折叠状态的 elevation4dp,展开状态和中间状态的 elevation0dp,而 CardView 默认状态下是 2dp,这就解释了为什么 AppBar 一会儿在卡片列表下方,一会儿又到了卡片列表上方了。这时再回头看看前面那张黑黑的 Z 轴透视图,对于刚开始学习的朋友,应该也很好理解了。

/MainActivity.java#onPostResume()
@Override
protected void onPostResume() {
    super.onPostResume();

    abl.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
        private float density = getResources().getDisplayMetrics().density; // 屏幕缩放比例
        private int marginTopOriginal;
        private int ratio; // AppBarLayout 的偏移范围与 marginTop 绝对值的比例
        private boolean isFirst = true;

        @Override
        public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
            // Log.e("VerticalOffset", ""+verticalOffset);
            // Log.e("VerticalOffset_dp", ""+verticalOffset / density);

            CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) rv.getLayoutParams();
            if (isFirst) {
                // RecyclerView 原来的 marginTop 的绝对值
                marginTopOriginal = lp.topMargin;
                // ImageView 的高度
                int contentHeight = ctl.getChildAt(0).getLayoutParams().height;
                // 减去 statusBar + Toolbar 的高度
                int verticalOffsetRange = Math.round(contentHeight - 80 * density);

                ratio = verticalOffsetRange / Math.abs(marginTopOriginal);

                isFirst = false;
            } else {
                // AppBarLayout 每折叠一点,marginTop 就按比例增加一点
                int newMarginTop = marginTopOriginal + Math.abs(verticalOffset / ratio);
                if (newMarginTop <= 0) {
                    // 避免将 marginTop 设置为大于 0 的值
                    lp.setMargins(0, newMarginTop, 0, 0);
                    rv.setLayoutParams(lp);
                }
            }
        }
    });
}

当然在 #onCreate() 中,少不了这么一行代码,

setSupportActionBar(toolbar);

其中 ablAppBarLayout 的对象,ctlCollapsingToolbarLayout 的对象,rvRecyclerView 的对象,toolbarToolbar 的对象。在 #onPostResume() 中写这些代码,是因为其中包含有获取 ImageView 高度的操作,这在 #onCreate() 中是不行的。代码中虽然已经写明注释,但也许并不够完善,所以如果读者还有不明白的地方,欢迎评论提问。

/style.xml
<style name="AppTheme.NoActionBar">
    <item name="windowActionBar">false</item>
    <item name="windowNoTitle">true</item>
    <item name="android:windowTranslucentStatus">true</item>
    <item name="android:windowDrawsSystemBarBackgrounds">true</item>
</style>

/style.xml 中还需要自定义一个 Theme,内容是去除 ActionBar、透明 StatusBar、允许绘制 StatusBar 的背景。然后在 /AndroidManifest.xml 配置,让 activity 使用这个 Theme

更新——2017/02/02

把代码中 ratio 变量改为 float 类型,比例更精准

private float ratio;

利用AppBarLayout 的一个 getTotalScrollRange() 方法,直接获取它的 offset 范围,就可以把 ratio 的计算方式更改为

ratio = abl.getTotalScrollRange() / 1f / Math.abs(marginTopOriginal);

然后 newMarginTop 计算时记得加一个 Math.round() 就可以了。

结语

以上的代码已经实现了那个比较特别的折叠效果了,这么看起来还是比较简单的对不对?只要给 AppBarLayout 增加 listener,然后在回调中写上几句代码就搞定了。其实 /手动笑哭,在实现它的过程中,我也是踩了不少坑,因为刚开始并不知道可以给 AppBarLayout 设置 listener,我尝试了各种奇怪的方式,比如给 RecyclerViewCollapsingToolbarLayout 设置 OnGlobalLayoutListener 来监听 Y、bottom 的变化,还给 RecyclerView 设置过 OnScrollListener,等等,都没有想要的效果。上面所说的两个 listener,在 AppBarLayout 收缩折叠时并不会触发,这里应该涉及到了嵌套滚动,不过我还没能很懂里面的原理呢~,说来也是奇怪为什么我可以想到给那些 View 设置监听,唯独想不到 AppBarLayout/再次手动笑哭 ?对了,这个效果我想不到一个好的名字,以至于我在网上一直没有搜索到其他的实现,也许大家有好主意?

最后,欢迎大家评论支持和指出我的不足,也许大家有更好的实现方式,欢迎与我分享,本人在此表示感谢~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值