package com.lzy.demo.ui;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Transformation;
import com.lzy.demo.R;
@SuppressWarnings("unused")
public class SimpleViewBehavior extends CoordinatorLayout.Behavior<View> {
private static final int UNSPECIFIED_INT = Integer.MAX_VALUE;
private static final float UNSPECIFIED_FLOAT = Float.MAX_VALUE;
private static final int DEPEND_TYPE_HEIGHT = 0;
private static final int DEPEND_TYPE_WIDTH = 1;
private static final int DEPEND_TYPE_X = 2;
private static final int DEPEND_TYPE_Y = 3;
private int mDependViewId = 0; //默认没有依赖对象
private int mDependType = DEPEND_TYPE_Y; //默认按照y方向变化
private int mDependTargetX; //X方向的允许最大距离(影响动画percent)
private int mDependTargetY; //Y方向的允许最大距离(影响动画percent)
private int mDependTargetWidth; //依赖控件起始最大宽度(影响动画percent)
private int mDependTargetHeight; //依赖控件起始最大高度(影响动画percent)
private int targetX;
private int targetY;
private int targetWidth;
private int targetHeight;
private int targetBackgroundColor;
private float targetAlpha;
private float targetRotateX;
private float targetRotateY;
private int mAnimationId = 0; //自定义动画id(xml文件定义动画)
private int mDependStartX;
private int mDependStartY;
private int mDependStartWidth;
private int mDependStartHeight;
private int mStartX;
private int mStartY;
private int mStartWidth;
private int mStartHeight;
private int mStartBackgroundColor;
private float mStartAlpha;
private float mStartRotateX;
private float mStartRotateY;
private Animation mAnimation;
private boolean isPrepared;
public SimpleViewBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SimpleViewBehavior);
mDependViewId = a.getResourceId(R.styleable.SimpleViewBehavior_svb_dependOn, mDependViewId);
mDependType = a.getInt(R.styleable.SimpleViewBehavior_svb_dependType, mDependType);
mDependTargetX = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_dependTargetX, UNSPECIFIED_INT);
mDependTargetY = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_dependTargetY, UNSPECIFIED_INT);
mDependTargetWidth = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_dependTargetWidth, UNSPECIFIED_INT);
mDependTargetHeight = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_dependTargetHeight, UNSPECIFIED_INT);
targetX = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_targetX, UNSPECIFIED_INT);
targetY = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_targetY, UNSPECIFIED_INT);
targetWidth = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_targetWidth, UNSPECIFIED_INT);
targetHeight = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_targetHeight, UNSPECIFIED_INT);
targetBackgroundColor = a.getColor(R.styleable.SimpleViewBehavior_svb_targetBackgroundColor, UNSPECIFIED_INT);
targetAlpha = a.getFloat(R.styleable.SimpleViewBehavior_svb_targetAlpha, UNSPECIFIED_FLOAT);
targetRotateX = a.getFloat(R.styleable.SimpleViewBehavior_svb_targetRotateX, UNSPECIFIED_FLOAT);
targetRotateY = a.getFloat(R.styleable.SimpleViewBehavior_svb_targetRotateY, UNSPECIFIED_FLOAT);
mAnimationId = a.getResourceId(R.styleable.SimpleViewBehavior_svb_animation, mAnimationId);
a.recycle();
}
/** 初始化数据 */
private void prepare(CoordinatorLayout parent, View child, View dependency) {
mDependStartX = (int) dependency.getX();
mDependStartY = (int) dependency.getY();
mDependStartWidth = dependency.getWidth();
mDependStartHeight = dependency.getHeight();
mStartX = (int) child.getX();
mStartY = (int) child.getY();
mStartWidth = child.getWidth();
mStartHeight = child.getHeight();
mStartAlpha = child.getAlpha();
mStartRotateX = child.getRotationX();
mStartRotateY = child.getRotationY();
//特殊处理y方向变化
if (mDependTargetY == UNSPECIFIED_INT && dependency instanceof AppBarLayout) {
mDependTargetY = ((AppBarLayout) dependency).getTotalScrollRange();
}
// 背景颜色渐变
if (child.getBackground() instanceof ColorDrawable) mStartBackgroundColor = ((ColorDrawable) child.getBackground()).getColor();
// 自定义动画
if (mAnimationId != 0) {
mAnimation = AnimationUtils.loadAnimation(child.getContext(), mAnimationId);
mAnimation.initialize(child.getWidth(), child.getHeight(), parent.getWidth(), parent.getHeight());
}
// 兼容5.0以上的沉浸模式
if (Build.VERSION.SDK_INT > 16 && parent.getFitsSystemWindows() && targetY != UNSPECIFIED_INT) {
targetY += getStatusBarHeight(parent.getContext());
}
isPrepared = true;
}
/**
* child 是指应用behavior的View ,dependency 担任触发behavior的角色,并与child进行互动。
* layoutDependsOn方法在每次layout发生变化时都会调用,我们需要在dependency控件发生变化时返回True,
* 在我们的例子中是用户在屏幕上滑动时(因为AppBarLayout发生了移动),然后我们需要让child做出相应的反应。
*/
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency.getId() == mDependViewId;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
// 该方法会在滑动的时候一直回调,但只需要初始化一次
if (!isPrepared) prepare(parent, child, dependency);
updateView(child, dependency);
return false;
}
/**
* 这个是CoordinatorLayout在进行measure的过程中,利用Behavior对象对子view进行大小测量的一个方法。
* 在这个方法内,我们可以通过parent.getDependencies(child);这个方法,获取到这个child依赖的view,然后通过获取这个child依赖的view的大小来决定自身的大小。
*/
@Override
public boolean onMeasureChild(CoordinatorLayout parent, View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
return super.onMeasureChild(parent, child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
}
/**
* 这个方法是用来子view用来布局自身使用,如果依赖其他view,那么系统会首先调用
* public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency)
* 这个方法,可以在这个回调中记录dependency的一些位置信息,在onLayoutChild中利用保存下来的信息进行计算,然后得到自身的具体位置。
*/
@Override
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
boolean bool = super.onLayoutChild(parent, child, layoutDirection);
if (isPrepared) updateView(child, parent.getDependencies(child).get(0));
return bool;
}
public void updateView(View child, View dependency) {
float percent = 0;
float start = 0;
float current = 0;
float end = UNSPECIFIED_INT;
switch (mDependType) {
case DEPEND_TYPE_WIDTH:
start = mDependStartWidth;
current = dependency.getWidth();
end = mDependTargetWidth;
break;
case DEPEND_TYPE_HEIGHT:
start = mDependStartHeight;
current = dependency.getHeight();
end = mDependTargetHeight;
break;
case DEPEND_TYPE_X:
start = mDependStartX;
current = dependency.getX();
end = mDependTargetX;
break;
case DEPEND_TYPE_Y:
start = mDependStartY;
current = dependency.getY();
end = mDependTargetY;
break;
}
if (end != UNSPECIFIED_INT) {
percent = Math.abs(current - start) / Math.abs(end - start);
}
updateViewWithPercent(child, percent > 1 ? 1 : percent);
}
/** 更新View */
public void updateViewWithPercent(View child, float percent) {
if (mAnimation == null) {
//如果没有自定义动画,那么使用属性动画
float newX = targetX == UNSPECIFIED_INT ? 0 : (targetX - mStartX) * percent;
float newY = targetY == UNSPECIFIED_INT ? 0 : (targetY - mStartY) * percent;
//缩放动画
if (targetWidth != UNSPECIFIED_INT || targetHeight != UNSPECIFIED_INT) {
child.setScaleX(scaleEvaluator(mStartWidth, targetWidth, percent));
child.setScaleY(scaleEvaluator(mStartHeight, targetHeight, percent));
float newWidth = floatEvaluator(mStartWidth, targetWidth, percent);
float newHeight = floatEvaluator(mStartWidth, targetWidth, percent);
newX -= (mStartWidth - newWidth) / 2;
newY -= (mStartHeight - newHeight) / 2;
}
//平移动画
child.setTranslationX(newX);
child.setTranslationY(newY);
//透明度变化
if (targetAlpha != UNSPECIFIED_FLOAT) child.setAlpha(floatEvaluator(mStartAlpha, targetAlpha, percent));
//背景渐变
if (targetBackgroundColor != UNSPECIFIED_INT && mStartBackgroundColor != 0) {
child.setBackgroundColor(argbEvaluator(mStartBackgroundColor, targetBackgroundColor, percent));
}
//旋转动画
if (targetRotateX != UNSPECIFIED_FLOAT) child.setRotationX(floatEvaluator(mStartRotateX, targetRotateX, percent));
if (targetRotateY != UNSPECIFIED_FLOAT) child.setRotationY(floatEvaluator(mStartRotateY, targetRotateY, percent));
} else {
mAnimation.setStartTime(0);
mAnimation.restrictDuration(100);
Transformation transformation = new Transformation();
mAnimation.getTransformation((long) (percent * 100), transformation);
BehaviorAnimation animation = new BehaviorAnimation(transformation);
child.startAnimation(animation);
}
child.requestLayout();
}
private static class BehaviorAnimation extends Animation {
private Transformation mTransformation;
public BehaviorAnimation(Transformation transformation) {
mTransformation = transformation;
setDuration(0);
setFillAfter(true);
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
t.compose(mTransformation);
super.applyTransformation(interpolatedTime, t);
}
}
public static float floatEvaluator(float originalSize, float finalSize, float percent) {
return (finalSize - originalSize) * percent + originalSize;
}
public static float scaleEvaluator(float originalSize, float finalSize, float percent) {
float calcSize = (finalSize - originalSize) * percent + originalSize;
return calcSize / originalSize;
}
public static int argbEvaluator(int startColor, int endColor, float percent) {
int startA = (startColor >> 24) & 0xff;
int startR = (startColor >> 16) & 0xff;
int startG = (startColor >> 8) & 0xff;
int startB = startColor & 0xff;
int endA = (endColor >> 24) & 0xff;
int endR = (endColor >> 16) & 0xff;
int endG = (endColor >> 8) & 0xff;
int endB = endColor & 0xff;
return ((startA + (int) (percent * (endA - startA))) << 24) |
((startR + (int) (percent * (endR - startR))) << 16) |
((startG + (int) (percent * (endG - startG))) << 8) |
((startB + (int) (percent * (endB - startB))));
}
/** 获取状态栏的高度 */
private static int getStatusBarHeight(Context context) {
int result = 0;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
return result;
}
}