一、简述CoordinatorLayout
原理请看这里 Android design包中CoordinatorLayout的设计原理
再看看facebook的效果
下面直接贴网上一些经典Demo
例子一:底部菜单抽屉效果
1 添加依赖
compile 'com.android.support:appcompat-v7:25.1.1'
2 xml
2 Activity
@BindView(R.id.coordinator)
CoordinatorLayout coordinator;
@BindView(R.id.nestedScrollView)
NestedScrollView scrollView;
@BindView(R.id.fabs)
FloatingActionButton fab;
@Override
protected int setLayoutResourceID() {
return R.layout.activity_hellow;
}
@Override
protected void setUpView() {
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//点击出现底部菜单
BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(scrollView);
bottomSheetBehavior.setState(bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED?BottomSheetBehavior.STATE_EXPANDED:BottomSheetBehavior.STATE_COLLAPSED);
}
});
}
实现的关键点:
1 根布局为 CoordinatorLayout;
2 为底部弹出框设置app:layout_behavior="@string/bottom_sheet_behavior";
3 在代码中得到BottomSheetBehavior(底部菜单),并动态改变它的状态;
4至于FloatingActionButton跟随弹出框移动,关键点在于为FloatingActionButton设置
5app:layout_behavior="@string/bottom_sheet_behavior"appbar_scrolling_view_behavior并不是一个字符串,而是一个类,而且这个类我们可以自定义。
例子二:AppBarLayout
1 xml
属性介绍
layout_scrollFlags
AppBarLayout中的子控件通过layout_scrollFlags这个属性或者setScrollFlags才能展现出他们的滚动行为。它的值有:scroll、snap、enterAlways、enterAlwaysCollapsed、exitUntilCollapsed。
scrol:所有想滚动的view都需要设置这个flag, 没有设置这个flag的view将被固定在屏幕顶部。这个我们已经见识过了
snap:这个直接翻译成“折断”。就是说被"关心"的View如果显示了一半,就全显示出来,否则则隐藏。
app:layout_scrollFlags="scroll|snap"
enterAlways:往上滑动的时候被"关心"的这个View隐藏,往下滑的时候显示
app:layout_scrollFlags="scroll|enterAlways"
enterAlwaysCollapsed:向上滑动的时候被"关心"的这个View隐藏,向下滑动时先展现一个最小高度,等到滑动到NestedScrollView最顶部的时候再完全展现出来。 注意这边一定要有一个最小高度,即minHeight属性,并且enterAlwaysCollapsed一定要搭配enterAlways和scroll才能正常展现
如果你的AppBarLayout中包含其他的View,那么含有layout_scrollFlags的标签的View请布局在前面。因为AppBarLayout实际上是一个LinearLayout。
例子三:折叠的Toolbar
CollapsingToolbarLayout提供了一个可以折叠的Toolbar,它继承FrameLayout。给它设置layout_scrollFlags,它可以控制包含在其中的子控件(如:ImageView、Toolbar)。
1 xml
相关属性
layout_collapseMode
有2种模式,
pin模式:即固定模式,在折叠的时候最后固定在顶端
parallax模式:即视差模式,在折叠的时候会有个视差折叠的效果
layout_collapseParallaxMultiplier
CollapsingToolbarLayout滑动时,子视图的视觉差。这个值的范围为0.0-1.0之间。为0的时候,你可以感觉到视图完全随NestedScrollView滚动;为1的时候,似乎又是完全不滚动
contentScrim
这是ToolBar被折叠到顶部固定后的背景。
expandedTitleMarginStart/expandedTitleMarginEnd
在ToolBar被折叠前文字部分的左右间距
setExpandedTitleColor/setCollapsedTitleTextColor
设置还没收缩时状态下字体颜色与收缩后Toolbar上字体的颜色
setTitle
使用CollapsingToolbarLayout时必须把title设置到CollapsingToolbarLayout上,设置到Toolbar上不会显示
例子四:同步移动
先看效果
DodoMoveView
**
* @创建 HaiJia
* @时间 2017/3/10 11:21
* @描述 可拖动的View
*/
public class DodoMoveView extends TextView{
private int lastX;
private int lastY;
public DodoMoveView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getRawX();
int y = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
CoordinatorLayout.MarginLayoutParams layoutParams =
(CoordinatorLayout.MarginLayoutParams) getLayoutParams();
int left = layoutParams.leftMargin + x - lastX;
int top = layoutParams.topMargin + y - lastY;
layoutParams.leftMargin = left;
layoutParams.topMargin = top;
setLayoutParams(layoutParams);
requestLayout();
break;
case MotionEvent.ACTION_UP:
break;
}
lastX = x;
lastY = y;
//如果返回值是true,代表事件在当前的viewGroup中会被处理,
//向下传递之路被截断(所有子控件将没有机会参与Touch事件),
// 同时把事件传递给当前的控件的onTouchEvent()处理。
return true;
}
}
然后简单修改Behavior
public class DodoBehavior0s extends CoordinatorLayout.Behavior
效果如下
三、其他各种效果Demo
1、BackBehavior
快速返回效果的Behavior,根据AppBarLayout的滚动来控制自定义View的滚动
2、项目可用的Demo
CoordinatorLayoutSample
CoordinatorBehaviorExample
附上关键源码
xml
layout_content_tom.xml
AvatarBehavior
public class AvatarBehavior extends CoordinatorLayout.Behavior {
// 缩放动画变化的支点
private static final float ANIM_CHANGE_POINT = 0.2f;
private Context mContext;
// 整个滚动的范围
private int mTotalScrollRange;
// AppBarLayout高度
private int mAppBarHeight;
// AppBarLayout宽度
private int mAppBarWidth;
// 控件原始大小
private int mOriginalSize;
// 控件最终大小
private int mFinalSize;
// 控件最终缩放的尺寸,设置坐标值需要算上该值
private float mScaleSize;
// 原始x坐标
private float mOriginalX;
// 最终x坐标
private float mFinalX;
// 起始y坐标
private float mOriginalY;
// 最终y坐标
private float mFinalY;
// ToolBar高度
private int mToolBarHeight;
// AppBar的起始Y坐标
private float mAppBarStartY;
// 滚动执行百分比[0~1]
private float mPercent;
// Y轴移动插值器
private DecelerateInterpolator mMoveYInterpolator;
// X轴移动插值器
private AccelerateInterpolator mMoveXInterpolator;
// 最终变换的视图,因为在5.0以上AppBarLayout在收缩到最终状态会覆盖变换后的视图,所以添加一个最终显示的图片
private CircleImageView mFinalView;
// 最终变换的视图离底部的大小
private int mFinalViewMarginBottom;
public AvatarBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mMoveYInterpolator = new DecelerateInterpolator();
mMoveXInterpolator = new AccelerateInterpolator();
if (attrs != null) {
TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.AvatarImageBehavior);
mFinalSize = (int) a.getDimension(R.styleable.AvatarImageBehavior_finalSize, 0);
mFinalX = a.getDimension(R.styleable.AvatarImageBehavior_finalX, 0);
mToolBarHeight = (int) a.getDimension(R.styleable.AvatarImageBehavior_toolBarHeight, 0);
a.recycle();
}
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, CircleImageView child, View dependency) {
return dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, CircleImageView child, View dependency) {
if (dependency instanceof AppBarLayout) {
_initVariables(child, dependency);
mPercent = (mAppBarStartY - dependency.getY()) * 1.0f / mTotalScrollRange;
float percentY = mMoveYInterpolator.getInterpolation(mPercent);
AnimHelper.setViewY(child, mOriginalY, mFinalY - mScaleSize, percentY);
if (mPercent > ANIM_CHANGE_POINT) {
float scalePercent = (mPercent - ANIM_CHANGE_POINT) / (1 - ANIM_CHANGE_POINT);
float percentX = mMoveXInterpolator.getInterpolation(scalePercent);
AnimHelper.scaleView(child, mOriginalSize, mFinalSize, scalePercent);
AnimHelper.setViewX(child, mOriginalX, mFinalX - mScaleSize, percentX);
} else {
AnimHelper.scaleView(child, mOriginalSize, mFinalSize, 0);
AnimHelper.setViewX(child, mOriginalX, mFinalX - mScaleSize, 0);
}
if (mFinalView != null) {
if (percentY == 1.0f) {
// 滚动到顶时才显示
mFinalView.setVisibility(View.VISIBLE);
} else {
mFinalView.setVisibility(View.GONE);
}
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && dependency instanceof CollapsingToolbarLayout) {
// 大于5.0才生成新的最终的头像,因为5.0以上AppBarLayout会覆盖变换后的头像
if (mFinalView == null && mFinalSize != 0 && mFinalX != 0 && mFinalViewMarginBottom != 0) {
mFinalView = new CircleImageView(mContext);
mFinalView.setVisibility(View.GONE);
// 添加为CollapsingToolbarLayout子视图
((CollapsingToolbarLayout) dependency).addView(mFinalView);
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mFinalView.getLayoutParams();
// 设置大小
params.width = mFinalSize;
params.height = mFinalSize;
// 设置位置,最后显示时相当于变换后的头像位置
params.gravity = Gravity.BOTTOM;
params.leftMargin = (int) mFinalX;
params.bottomMargin = mFinalViewMarginBottom;
mFinalView.setLayoutParams(params);
mFinalView.setImageDrawable(child.getDrawable());
mFinalView.setBorderColor(child.getBorderColor());
int borderWidth = (int) ((mFinalSize * 1.0f / mOriginalSize) * child.getBorderWidth());
mFinalView.setBorderWidth(borderWidth);
}
}
return true;
}
/**
* 初始化变量
* @param child
* @param dependency
*/
private void _initVariables(CircleImageView child, View dependency) {
if (mAppBarHeight == 0) {
mAppBarHeight = dependency.getHeight();
mAppBarStartY = dependency.getY();
}
if (mTotalScrollRange == 0) {
mTotalScrollRange = ((AppBarLayout) dependency).getTotalScrollRange();
}
if (mOriginalSize == 0) {
mOriginalSize = child.getWidth();
}
if (mFinalSize == 0) {
mFinalSize = mContext.getResources().getDimensionPixelSize(R.dimen.avatar_final_size);
}
if (mAppBarWidth == 0) {
mAppBarWidth = dependency.getWidth();
}
if (mOriginalX == 0) {
mOriginalX = child.getX();
}
if (mFinalX == 0) {
mFinalX = mContext.getResources().getDimensionPixelSize(R.dimen.avatar_final_x);
}
if (mOriginalY == 0) {
mOriginalY = child.getY();
}
if (mFinalY == 0) {
if (mToolBarHeight == 0) {
mToolBarHeight = mContext.getResources().getDimensionPixelSize(R.dimen.toolbar_height);
}
mFinalY = (mToolBarHeight - mFinalSize) / 2 + mAppBarStartY;
}
if (mScaleSize == 0) {
mScaleSize = (mOriginalSize - mFinalSize) * 1.0f / 2;
}
if (mFinalViewMarginBottom == 0) {
mFinalViewMarginBottom = (mToolBarHeight - mFinalSize) / 2;
}
}
}