Material Design 系列 - 自定义 Behavior 实现伸缩标题栏

本贴最后更新于 2438 天前,其中的信息可能已经渤澥桑田

引言

CoordinatorLayout+CollapsingToolbarLayout+Behavior 真是一个好东西,很多复杂的 UI 交互效果都可以通过 Behavior 来实现,用了 Behavior 之后腰也不疼了,再也不会对设计师说这个实现不了了,只要给我时间我就实现给你看!今天带来第一个自定义 Behavior:实现一个伸缩的标题栏。

效果图如下

Behavior 效果图

实现思路

  1. 监听 CollapsingToolbarLayout 滚动的 Y 轴距离,和 CollapsingToolbarLayout 的总高度进行百分比计算得出当前滑动的百分比,再不断的计算顶部图标的宽高进行百分比缩减。整个按钮的 X 轴坐标跟随百分比减少。
  2. 整个 View 的宽度除以 4,得出每个 menu 所占的宽度,用 item 的的下标乘以 menu 的宽度得出每个 menu 的 X 轴。
  3. 当滑动的时候改变文字的透明度,大于 0.4 则隐藏文字。

开始编码

引入相关依赖

dependencies{ implementation 'com.android.support:design:26.0.2' }

创建相关 View

创建 xml,CollapsingToolbarLayout 定义高度和滚动模式,内部放一个 View 作为滑动的坐标参考。而 menu 是一个垂直的 LinearLayout,上面一个 ImageView,下面一个 TextView,下面是相关内容:

<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" tools:context=".AlipayBehaviorActivity"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:elevation="2dp"> <android.support.design.widget.CollapsingToolbarLayout android:layout_width="match_parent" android:layout_height="135dp" android:background="@color/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <!--滚动模式--> <!--用来做背景坐标参考--> <FrameLayout android:id="@+id/flScroll" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_collapseMode="parallax" app:layout_collapseParallaxMultiplier="0.9" /> <android.support.v7.widget.Toolbar android:layout_width="match_parent" android:layout_height="48dp" android:background="@color/colorPrimary" app:layout_anchor="@id/flScroll" app:layout_collapseMode="pin" app:title="" /> <!--Toolbar的layout_collapseMode设置为pin,代表toolbar一直固定在顶部--> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <!--menu布局--> <LinearLayout style="@style/ServerShortcutMenuLineStyle" android:gravity="center"> <ImageView style="@style/ServerShortcutMenuImageStyle" android:src="@mipmap/icon_server_app_door" /> <TextView style="@style/ServerShortcutMenuTextStyle" android:text="门禁" /> </LinearLayout> </android.support.design.widget.CoordinatorLayout>

为了完成后方便复制,我将样式抽取出来了,样式文件如下:

<style name="ServerShortcutMenuLineStyle"> <!--快捷菜单行样式--> <item name="android:layout_width">wrap_content</item> <item name="android:layout_height">70dp</item> <item name="android:background">?selectableItemBackground</item> <item name="android:clickable">true</item> <item name="android:focusable">true</item> <item name="android:gravity">center_horizontal|bottom</item> <item name="android:orientation">vertical</item> <item name="android:elevation">5dp</item> </style> <style name="ServerShortcutMenuTextStyle"> <item name="android:layout_width">wrap_content</item> <item name="android:layout_height">wrap_content</item> <item name="android:layout_marginTop">4dp</item> <item name="android:textColor">#fff</item> <item name="android:textSize">14sp</item> </style> <style name="ServerShortcutMenuImageStyle"> <item name="android:layout_width">30dp</item> <item name="android:layout_height">30dp</item> </style>

先运行起来看看效果吧:

View1

确定第一个 menu 的位置

创建 AlipayBehavior 继承至 CoordinatorLayout.Behavior,按照思路,先确定第 0 个 item 的 X 和 Y 轴。核心代码如下:

@Override public boolean onDependentViewChanged(CoordinatorLayout parent, LinearLayout child, View dependency) { //计算出每个View的宽度 mViewWidth = dependency.getWidth() / 4/*四个View*/; //高度 mViewHeight = child.getHeight(); //重新规划 宽度 ViewGroup.LayoutParams layoutParams = child.getLayoutParams(); if (layoutParams != null) { layoutParams.width = mViewWidth; } child.setLayoutParams(layoutParams); //设置X轴坐标 child.setX(0); // 设置Y轴坐标 child.setY(mViewHeight/2); return true; }

在 xml 中进行引用:

<LinearLayout style="@style/ServerShortcutMenuLineStyle" android:gravity="center" app:layout_anchor="@id/flScroll" app:layout_behavior="android.of.road.com.behavior.AlipayBehavior"> <ImageView style="@style/ServerShortcutMenuImageStyle" android:src="@mipmap/icon_server_app_door" /> <TextView style="@style/ServerShortcutMenuTextStyle" android:text="门禁" /> </LinearLayout>

运行起来看看效果吧:

View2

可以看到,第 0 个 View 的距离已经确定下来,下一步就需要开始跟随滑动而更改 menu 的位置了。

监听滑动,更改 menu 的 X 轴位置

  • 滑动的百分比为 dependency 的 Y 轴位置除以 dependency 的高度。
  • 为了能让 menu 滑动到最小后又能滑动到最大位置,需要用两个变量 mViewMaxX 和 mViewMaxY 存储最大值。
  • 跟随监听更改 menu 的(mViewMaxX 和 mViewMaxY)乘以百分比的 X 轴和 Y 轴坐标。

代码实现如下:

@Override public boolean onDependentViewChanged(CoordinatorLayout parent, LinearLayout child, View dependency) { //计算出每个View的宽度 mViewWidth = dependency.getWidth() / 4/*四个View*/; //高度 mViewHeight = child.getHeight(); //计算居中X轴,第一个 随便给的默认值 mViewMaxX = 50; //计算Y轴 坐标系参考View的高度二分之一减去menu的高度除以2,刚好居中 mViewMaxY = dependency.getHeight() / 2 - mViewHeight / 2; //重新规划 宽度 ViewGroup.LayoutParams layoutParams = child.getLayoutParams(); if (layoutParams != null) { layoutParams.width = mViewWidth; } child.setLayoutParams(layoutParams); //计算百分比 当前的百分比其实是没有减去状态栏的 float mPercent = dependency.getY() / (dependency.getHeight()); if (mPercent >= 1f) mPercent = 1; //更改 内部文字的透明底 View mTextTitleView = child.getChildAt(1); if (mTextTitleView != null) { mTextTitleView.setAlpha(1 - (mPercent > 0.4 ? 1 : mPercent)); } // 更改内部imageView的大小 View mImageTitleView = child.getChildAt(0); if (mImageTitleView != null) { mImageTitleView.setScaleX(1 - (0.4f * mPercent)); mImageTitleView.setScaleY(1 - (0.4f * mPercent)); } //设置X轴坐标 child.setX(mViewMaxX - mViewMaxX * mPercent); // 设置Y轴坐标 child.setY(mViewMaxY - (mViewMaxY * 1.4f) * mPercent); return true; }

效果:

初步完成监听

分配到每个 menu 上

现在已经完成了,现在需要的是为每个 menu 进行配置 Behavior,实现思路如下:

  • 这里的实现思路是为每个 menu 加一个 tag,而 tag 就是 menu 的下标
  • Behavior 中获取 menu 的下标,根据下标来确定 x 和 y 轴的位置

xml 代码如下:

<android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:elevation="2dp"> <android.support.design.widget.CollapsingToolbarLayout android:layout_width="match_parent" android:layout_height="135dp" android:background="@color/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <!--用来做背景坐标参考--> <FrameLayout android:id="@+id/flScroll" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_collapseMode="parallax" app:layout_collapseParallaxMultiplier="0.9" /> <android.support.v7.widget.Toolbar android:layout_width="match_parent" android:layout_height="48dp" android:background="@color/colorPrimary" app:layout_anchor="@id/flScroll" app:layout_collapseMode="pin" app:title="" /> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <include layout="@layout/layout_apay_content" /> <LinearLayout style="@style/ServerShortcutMenuLineStyle" android:gravity="center" android:tag="0" app:layout_anchor="@id/flScroll" app:layout_behavior="android.of.road.com.behavior.AlipayBehavior"> <ImageView style="@style/ServerShortcutMenuImageStyle" android:src="@mipmap/icon_server_app_door" /> <TextView style="@style/ServerShortcutMenuTextStyle" android:text="门禁" /> </LinearLayout> <LinearLayout style="@style/ServerShortcutMenuLineStyle" android:gravity="center" android:tag="1" app:layout_anchor="@id/flScroll" app:layout_behavior="android.of.road.com.behavior.AlipayBehavior"> <ImageView style="@style/ServerShortcutMenuImageStyle" android:src="@mipmap/icon_server_app_scanf" /> <TextView style="@style/ServerShortcutMenuTextStyle" android:text="扫一扫" /> </LinearLayout> <LinearLayout style="@style/ServerShortcutMenuLineStyle" android:gravity="center" android:tag="2" app:layout_anchor="@id/flScroll" app:layout_behavior="android.of.road.com.behavior.AlipayBehavior"> <ImageView style="@style/ServerShortcutMenuImageStyle" android:src="@mipmap/icon_server_app_card" /> <TextView style="@style/ServerShortcutMenuTextStyle" android:text="停车月卡" /> </LinearLayout> <LinearLayout style="@style/ServerShortcutMenuLineStyle" android:gravity="center" android:tag="3" app:layout_anchor="@id/flScroll" app:layout_behavior="android.of.road.com.behavior.AlipayBehavior"> <ImageView style="@style/ServerShortcutMenuImageStyle" android:src="@mipmap/icon_server_app_wallet" /> <TextView style="@style/ServerShortcutMenuTextStyle" android:text="钱包" /> </LinearLayout> </android.support.design.widget.CoordinatorLayout>

完整 Java 代码如下:

public class AlipayBehavior extends CoordinatorLayout.Behavior<LinearLayout> { /** * 下标 */ private int mPosition = -1; /** * X轴坐标 */ private float mViewMaxX = 0; /** * View的宽度 */ private int mViewWidth; /** * View的高度 */ private int mViewHeight; /** * Y轴的最大高度 */ private int mViewMaxY = 0; public AlipayBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean layoutDependsOn(CoordinatorLayout parent, LinearLayout child, View dependency) { return dependency instanceof Toolbar; } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, LinearLayout child, View dependency) { if (mPosition == -1) {//未初始化 mPosition = Integer.parseInt((String) child.getTag()); //计算出每个View的宽度 mViewWidth = dependency.getWidth() / 4/*四个View*/; //高度 mViewHeight = child.getHeight(); //重新规划 宽度 ViewGroup.LayoutParams layoutParams = child.getLayoutParams(); if (layoutParams != null) { layoutParams.width = mViewWidth; } child.setLayoutParams(layoutParams); // 总宽度 除以四。得出每一个子View的宽度,居中为 //计算居中X轴 mViewMaxX = mViewWidth * mPosition; //计算Y轴 mViewMaxY = (int) (child.getY() + DensityUtils.dp2px(parent.getContext(), 50f)); } //计算百分比 当前的百分比其实是没有减去状态栏的 float mPercent = dependency.getY() / (dependency.getHeight()/* - ScreenUtils.getStatusHeight(parent.getContext())*/); if (mPercent >= 1f) mPercent = 1; // 动态更改 View的高度 ViewGroup.LayoutParams layoutParams = child.getLayoutParams(); if (layoutParams != null) { layoutParams.height = (int) (mViewHeight - (mViewHeight /** 0.8f*/) * mPercent); layoutParams.width = (int) (mViewWidth - (mViewWidth * mPercent)); child.setLayoutParams(layoutParams); } //更改 内部文字的透明底 View mTextTitleView = child.getChildAt(1); if (mTextTitleView != null) { mTextTitleView.setAlpha(1 - (mPercent > 0.4 ? 1 : mPercent)); } // 更改内部imageView的大小 View mImageTitleView = child.getChildAt(0); if (mImageTitleView != null) { mImageTitleView.setScaleX(1 - (0.4f * mPercent)); mImageTitleView.setScaleY(1 - (0.4f * mPercent)); } //设置X轴坐标//没有计算状态栏的情况之下,滑动并不是完整的 child.setX(mViewMaxX - (mViewMaxX - 50/*左边的距离*/) * mPercent); // 设置Y轴坐标 child.setY(mViewMaxY - (mViewMaxY * 1.4f) * mPercent); return true; } }

查看效果图:

完成监听

最后

未完待续、敬请期待!

FullScreenDeveloper

源码地址

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1063 引用 • 3455 回帖 • 161 关注
  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    336 引用 • 324 回帖
  • mpercent
    1 引用

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...