1.概述
Material Design从Android5.0开始引入的,是一种全新的设计语言(翻译为“原材料设计”),其实是谷歌提倡的一种设计风格、理念、原则。最近发现现在很多 app 包括最早的知乎没有使用了,今天再来回顾一下控件的简单使用,效果非常简单,先上张图。
效果展示
在这里插入图片描述
2.控件介绍
2.1 CoordinatorLayout:用来协调子控件,结合AppBarLayout,FloatingActionButton等使用
2.2 SwipeRefreshLayout:提供的下拉刷新的效果,里面只能包一个子控件可以包任意控件RecyclerView,ScrollView ,ListView。
2.3 ToolBar:加强版的ActionBar
2.4 TabLayout:导航书签,可以结合ViewPager使用
app:tabIndicatorColor 导航书签下方指示线的颜色
app:tabIndicatorHeight 指示线的高度
app:tabSelectedTextColor 导航书签选中时的字体颜色
app:tabMode="fixed" 显示模式
1:fixed 全部展示
2. scrollable 滑动展示
2.5 CollapsingToolBarLayout:折叠布局,结合ToolBar使用
android:minHeight 最小高度
app:layout_scrollFlags 模式
scroll 想滚动就必须设置这个。
enterAlways 实现quick return效果, 当向下移动时,立即显示View(比如Toolbar)。
exitUntilCollapsed 向上滚动时收缩View,但可以固定Toolbar一直在上面。
enterAlwaysCollapsed 当你的View已经设置minHeight属性又使用此标志时,你的View只能以最小高度进入,只有当滚动视图到达顶部时才扩大到完整高度。
app:contentScrim 当完全CollapsingToolbarLayout折叠(收缩)后的背景颜色。
app:expandedTitleGravity Toolbar的title显示位置
app:expandedTitleMargin 设置显示位置设置Margin值
app:layout_collapseParallaxMultiplier视差因子 0-1之间
app:layout_collapseMode 视差模式
pin:· 固定模式
parallax: 折叠效果
2.6 SnackBar:MD风格的Toast,底部弹出
2.7 TextInputLayout:自带错误提示的文本框
等等,不一一介绍了,今天只是简单的使用,想了解,可以去官网查看
3.效果实现
3.1 CoordinatorLayout 和 Behavior 介绍
CoordinatorLayout是什么? 看一下官方的介绍:Interaction behavior plugin for child views of (子视图的交互行为插件)
Behavior 实现了用户的一个或者多个交互行为,它们可能包括拖拽、滑动、快滑或者其他一些手势。Behavior 是一个顶层抽象类,其他的一些具体行为的 Behavior 都是继承自这个类。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
tools:context=".one_18.BehaviorActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@color/blue"
app:layout_scrollFlags="scroll|enterAlways|snap"/>
<!-- <androidx.appcompat.widget.Toolbar-->
<!-- android:id="@+id/tool_bar"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- app:title="知乎首页"-->
<!-- app:titleTextColor="#ffffff"-->
<!-- app:layout_scrollFlags="scroll|enterAlways|snap">-->
<!-- </androidx.appcompat.widget.Toolbar>-->
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginBottom="70dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
app:layout_behavior=".one_18.TranslationBehavior"
android:src="@mipmap/ic_launcher">
</com.google.android.material.floatingactionbutton.FloatingActionButton>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal"
android:background="@android:color/white"
app:layout_behavior="@string/bottom_sheet_behavior">
<ImageView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:src="@mipmap/ic_launcher"/>
<ImageView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:src="@mipmap/ic_launcher"/>
<ImageView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:src="@mipmap/ic_launcher"/>
<ImageView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:src="@mipmap/ic_launcher"/>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>
看一下Behaviror的方法作用
/**
* 表示是否给应用了Behavior 的View 指定一个依赖的布局,通常,当依赖的View 布局发生变化时
* 不管被被依赖View 的顺序怎样,被依赖的View也会重新布局
* @param parent
* @param child 绑定behavior 的View
* @param dependency 依赖的view
* @return 如果child 是依赖的指定的View 返回true,否则返回false
*/
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return super.layoutDependsOn(parent, child, dependency);
}
/**
* 当被依赖的View 状态(如:位置、大小)发生变化时,这个方法被调用
* @param parent
* @param child
* @param dependency
* @return
*/
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
return super.onDependentViewChanged(parent, child, dependency);
}
/**
* 当coordinatorLayout 的子View试图开始嵌套滑动的时候被调用。当返回值为true的时候表明
* coordinatorLayout 充当nested scroll parent 处理这次滑动,需要注意的是只有当返回值为true
* 的时候,Behavior 才能收到后面的一些nested scroll 事件回调(如:onNestedPreScroll、onNestedScroll等)
* 这个方法有个重要的参数nestedScrollAxes,表明处理的滑动的方向。
*
* @param coordinatorLayout 和Behavior 绑定的View的父CoordinatorLayout
* @param child 和Behavior 绑定的View
* @param directTargetChild
* @param target
* @param nestedScrollAxes 嵌套滑动 应用的滑动方向,看 {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
* {@link ViewCompat#SCROLL_AXIS_VERTICAL}
* @return
*/
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
/**
* 嵌套滚动发生之前被调用
* 在nested scroll child 消费掉自己的滚动距离之前,嵌套滚动每次被nested scroll child
* 更新都会调用onNestedPreScroll。注意有个重要的参数consumed,可以修改这个数组表示你消费
* 了多少距离。假设用户滑动了100px,child 做了90px 的位移,你需要把 consumed[1]的值改成90,
* 这样coordinatorLayout就能知道只处理剩下的10px的滚动。
* @param coordinatorLayout
* @param child
* @param target
* @param dx 用户水平方向的滚动距离
* @param dy 用户竖直方向的滚动距离
* @param consumed
*/
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
}
/**
* 进行嵌套滚动时被调用
* @param coordinatorLayout
* @param child
* @param target
* @param dxConsumed target 已经消费的x方向的距离
* @param dyConsumed target 已经消费的y方向的距离
* @param dxUnconsumed x 方向剩下的滚动距离
* @param dyUnconsumed y 方向剩下的滚动距离
*/
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}
/**
* 嵌套滚动结束时被调用,这是一个清除滚动状态等的好时机。
* @param coordinatorLayout
* @param child
* @param target
*/
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
super.onStopNestedScroll(coordinatorLayout, child, target);
}
/**
* onStartNestedScroll返回true才会触发这个方法,接受滚动处理后回调,可以在这个
* 方法里做一些准备工作,如一些状态的重置等。
* @param coordinatorLayout
* @param child
* @param directTargetChild
* @param target
* @param nestedScrollAxes
*/
@Override
public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
/**
* 用户松开手指并且会发生惯性动作之前调用,参数提供了速度信息,可以根据这些速度信息
* 决定最终状态,比如滚动Header,是让Header处于展开状态还是折叠状态。返回true 表
* 示消费了fling.
*
* @param coordinatorLayout
* @param child
* @param target
* @param velocityX x 方向的速度
* @param velocityY y 方向的速度
* @return
*/
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
/**
* 摆放子 View 的时候调用,可以重写这个方法对子View 进行重新布局
*/
@Override
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
return super.onLayoutChild(parent, child, layoutDirection);
}
3.2 自定义 Behavior 来实现 FloatingActionBar 的改变
public class TranslationBehavior<T extends FloatingActionButton> extends CoordinatorLayout.Behavior<T> {
private static final String TAG = "TranslationBehavior";
/**
* FloatingActionButton当前是否是显示状态
*/
private boolean isOut = false;
public TranslationBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 注意:当coordinatorLayout 的子View试图开始嵌套滑动的时候被调用。当返回值为true的时候表明
* coordinatorLayout 充当nested scroll parent 处理这次滑动,需要注意的是只有当返回值为true
* 的时候,Behavior 才能收到后面的一些nested scroll 事件回调(如:onNestedPreScroll、onNestedScroll等)
* 这个方法有个重要的参数nestedScrollAxes,表明处理的滑动的方向。
* @param coordinatorLayout 和Behavior 绑定的View的父CoordinatorLayout
* @param child 和Behavior 绑定的View
* @param directTargetChild
* @param target
* @param axes 嵌套滑动,滑动方向, {@link ViewCompat#SCROLL_AXIS_HORIZONTAL}
* {@link ViewCompat#SCROLL_AXIS_VERTICAL}
* @return
*/
@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull T child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
Log.e(TAG,"onStartNestedScroll");
return axes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
/**
* 进行嵌套滚动时被调用
* @param coordinatorLayout
* @param child
* @param target
* @param dxConsumed target 已经消费的x方向的距离
* @param dyConsumed target 已经消费的y方向的距离 往上滑是正 往下滑是负
* @param dxUnconsumed x 方向剩下的滚动距离
* @param dyUnconsumed y 方向剩下的滚动距离
* @param type
*/
@Override
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull T child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
Log.e(TAG,"onNestedScroll");
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
Log.e(TAG,"dyConsumed:"+dyConsumed+"--------dyUnconsumed:"+dyUnconsumed);
//1.将我们的child(FloatingActionButton)进行隐藏
if (dyConsumed > 0){
if (!isOut){
//2.移动距离计算 bottomMargin + 自己的高度
int translationY = ((CoordinatorLayout.LayoutParams)child.getLayoutParams()).bottomMargin + child.getMeasuredHeight();
child.animate().translationY(translationY).setDuration(500).start();
//3.注意:这样设置后网上滑的时候会发现FloatingActionButton会反应慢,我们可以设置一个标志位
isOut = true;
}
}else {
if (isOut){
child.animate().translationY(0).setDuration(500).start();
isOut = false;
}
}
}
}