多重结构嵌套实现可伸缩头部界面
最近做项目的时候遇到了一个问题,就是做顶部是固定的而下面的内容是RecyclerView构成的列表的界面,在自己使用的时候发现因为头部太大导致下面显示的范围会很小,用户体验比较差。于是就想到了现在很多app都有向下滑动的时候头部会缩小成一个只包含最主要功能的工具栏,于是就想着来找找如何实现。
效果展示

参考链接
@aqi00 CSDN 感谢大佬的方法!!
准备工作
在build.gradle中导入库
implementation 'com.google.android.material:material:1.2.0'
布局文件XML编写
基本原理
XML文件主要利用CoordinatorLayout嵌套AppBarLayout再嵌套CollapsingToolbarLayout再嵌套Toolbar的布局,听起来十分复杂,但是了解了每一个布局是什么作用理解起来就不是特别困难。
- CoordinatorLayout:这个布局在这次demo的作用我理解的是将界面头部和下面包含的RecyclerView的滑动操作绑定起来,可以使得滑动后对头部做出相关操作。其中RecyclerView也可以替换为NestedScrollView
- AppBarLayout:这个布局就是头部所在的布局,通过和RecyclerView一起嵌套在CoordinatorLayout中实现跟随内容视图下拉而展开,跟随内容视图上拉而收缩的效果
- CollapsingToolbarLayout:这个布局用来包含头部所使用到的布局,以及定义跟随滑动产生的效果
- Toolbar:定义了收缩前以及收缩后顶部工具栏的布局
实现代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.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">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/detail_top_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:contentScrim="#F5F5F5">
<include
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.7"
layout="@layout/detail_expand_information"/>
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="50dp"
app:layout_collapseMode="pin"
app:contentInsetLeft="0dp"
app:contentInsetStart="0dp">
<include
android:id="@+id/detail_close_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
layout="@layout/detail_close_bar" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id_detail_post_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="25dp"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
其中include所包含的布局要自己实现在其他的xml文件中。并且**ToolBar**布局中的android:layout_height参数是工具栏的高度。
值得注意的是在**CollapsingToolbarLayout布局中的app:contentScrim="#F5F5F5"语句是定义了工具栏位置的背景色,这个背景色必须设置且不能为透明,否则被收缩起来的头部会被透过来。这里的#F5F5F5**是安卓默认的背景颜色。
还有一点就是,因为我的demo在头部展开的情况下不需要工具栏,所以**ToolBar布局中只有一个收缩后的布局include,如果需要展开情况下也有工具栏的话则需在include一个布局文件,且不用设置android:visibility参数,且若在展开情况下也显示工具栏的话,需要手动在CollapsingToolbarLayout布局中设置android:layout_marginTop参数,且参数值为ToolBar的高度,以避免布局的重叠。**
Java逻辑代码实现
即使我们是通过include将我们的布局整合到一个布局文件中,但是我们依然可以用老办法来绑定布局组件,例如:
icon = findViewById(R.id.detail_icon);
icon_close = findViewById(R.id.detail_close_icon);
id = findViewById(R.id.detail_id);
id_close = findViewById(R.id.detail_close_id);
introduce = findViewById(R.id.detail_introduce);
fans = findViewById(R.id.detail_fans);
blog = findViewById(R.id.detail_blog);
more = findViewById(R.id.detail_more_pic);
follow = findViewById(R.id.detail_follow_text);
follow_close = findViewById(R.id.detail_close_follow_text);
大部分的功能通过布局文件中RecyclerView布局的app:layout_behavior参数可以自动实现,我们这里只要实现ToolBar在不同状态下的切换即可,示例代码如下:
private AppBarLayout detail_card;
private View detail_close_bar;
detail_card = findViewById(R.id.detail_top_bar);
detail_close_bar = findViewById(R.id.detail_close_bar);
detail_card.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int i) {
int offset = Math.abs(i);
int total = appBarLayout.getTotalScrollRange();
if (offset <= total / 2) {
detail_close_bar.setVisibility(View.GONE);
} else {
detail_close_bar.setVisibility(View.VISIBLE);
}
}
});
至此就可以实现图上的功能了
美化
这一部分没有弄明白,似乎是为了双ToolBar时切换达到一个渐变的效果,在我的demo中实现后没有明显的差别,不知道是因为我是单ToolBar的原因,还是因为代码实现有问题。这里直接引用 @aqi00 大佬的代码:
public class AlipayActivity extends AppCompatActivity implements OnOffsetChangedListener {
private final static String TAG = "AlipayActivity";
private AppBarLayout abl_bar;
private View tl_expand, tl_collapse;
private View v_expand_mask, v_collapse_mask, v_pay_mask;
private int mMaskColor;
private RecyclerView rv_content;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_alipay);
mMaskColor = getResources().getColor(R.color.blue_dark);
rv_content = (RecyclerView) findViewById(R.id.rv_content);
rv_content.setLayoutManager(new GridLayoutManager(this, 4));
rv_content.setAdapter(new LifeAdapter(this, LifeItem.getDefault()));
abl_bar = (AppBarLayout) findViewById(R.id.abl_bar);
tl_expand = (View) findViewById(R.id.tl_expand);
tl_collapse = (View) findViewById(R.id.tl_collapse);
v_expand_mask = (View) findViewById(R.id.v_expand_mask);
v_collapse_mask = (View) findViewById(R.id.v_collapse_mask);
v_pay_mask = (View) findViewById(R.id.v_pay_mask);
abl_bar.addOnOffsetChangedListener(this);
}
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
Log.d(TAG, "verticalOffset="+verticalOffset);
int offset = Math.abs(verticalOffset);
int total = appBarLayout.getTotalScrollRange();
int alphaIn = offset;
int alphaOut = (200-offset)<0?0:200-offset;
int maskColorIn = Color.argb(alphaIn, Color.red(mMaskColor),
Color.green(mMaskColor), Color.blue(mMaskColor));
int maskColorInDouble = Color.argb(alphaIn*2, Color.red(mMaskColor),
Color.green(mMaskColor), Color.blue(mMaskColor));
int maskColorOut = Color.argb(alphaOut*2, Color.red(mMaskColor),
Color.green(mMaskColor), Color.blue(mMaskColor));
if (offset <= total / 2) {
tl_expand.setVisibility(View.VISIBLE);
tl_collapse.setVisibility(View.GONE);
v_expand_mask.setBackgroundColor(maskColorInDouble);
} else {
tl_expand.setVisibility(View.GONE);
tl_collapse.setVisibility(View.VISIBLE);
v_collapse_mask.setBackgroundColor(maskColorOut);
}
v_pay_mask.setBackgroundColor(maskColorIn);
}
}
杂谈
这四个布局其实还没有搞清楚具体的作用,以及使用方法,仅仅只是研究了如何实现本文提到的功能。并且也只是跟着大佬的教程一步一步的实现了而已。如果以后有时间再会去研究每一个布局的详细功能

