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; } }