引言
CoordinatorLayout+CollapsingToolbarLayout+Behavior 真是一个好东西,很多复杂的 UI 交互效果都可以通过 Behavior 来实现,用了 Behavior 之后腰也不疼了,再也不会对设计师说这个实现不了了,只要给我时间我就实现给你看!今天带来第一个自定义 Behavior:实现一个伸缩的标题栏。
效果图如下
实现思路
- 监听 CollapsingToolbarLayout 滚动的 Y 轴距离,和 CollapsingToolbarLayout 的总高度进行百分比计算得出当前滑动的百分比,再不断的计算顶部图标的宽高进行百分比缩减。整个按钮的 X 轴坐标跟随百分比减少。
- 整个 View 的宽度除以 4,得出每个 menu 所占的宽度,用 item 的的下标乘以 menu 的宽度得出每个 menu 的 X 轴。
- 当滑动的时候改变文字的透明度,大于 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>
先运行起来看看效果吧:
确定第一个 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>
运行起来看看效果吧:
可以看到,第 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;
}
}
查看效果图:
最后
未完待续、敬请期待!
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于