android 自定义Behavior

作者: 刘景昌 | 来源:发表于2019-05-16 11:03 被阅读103次

    CoordinatorLayout是support:design库里面的核心空间 可以帮助我们实现一些炫酷的交互动画在使用的过程中我们总是不可避免的遇到一个类Behavior有的时候我们并没有去深入的了解它只是简单是设置一下app:layout_behavior="@string/appbar_scrolling_view_behavior就完事了也不去了解里面具体的实现原理,其实它才是我们要实现一系列炫酷的交互的主要实现类。
    那么什么是behavior呢?我讲去实现一个自定义的behavior是大家更加方便的理解他
    我们要了解一下再Behavior中可以被重写的类

    layoutDependsOn():确定使用Behavior的View要依赖的View的类型

    onDependentViewChanged():当被依赖的View状态改变时回调

    onDependentViewRemoved():当被依赖的View移除时回调

    onMeasureChild():测量使用Behavior的View尺寸

    onLayoutChild():确定使用Behavior的View位置

    其中如果我们只做动画主要就是layoutDependsOn() 和 onDependentViewChanged()方法
    在这里先来看看我们UI要求的最终效果


    6675987674397022473_Trim.gif

    我们先分析下我们主要的实现过程:这里面一共包含三个动画
    (1)头像移动和缩放动画
    (2)文字的透明度动画
    (3)背景的透明动画
    为了方便我们以后的实现 我先定义的一个联动百分比滑动的基类 可以使子类更加方便的去实现
    基类代码

    
    public abstract  class PercentageViewBehavior1<V extends View> extends CoordinatorLayout.Behavior<V> {
    
        static final int UNSPECIFIED_INT = Integer.MAX_VALUE;
        static final float UNSPECIFIED_FLOAT = Float.MAX_VALUE;
    
    
        //需要联动的View的idq
        private int mDependViewId;
        //联动滑动的终点
        private int mDependTarget;
        //联动滑动的起点
        private int mDependStartY;
    
    
        /**
         * Is the values prepared to be use
         */
        private boolean isPrepared;
    
        PercentageViewBehavior1(Context context, AttributeSet attrs) {
            super(context, attrs);
    
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewBehavior);
            mDependViewId = a.getResourceId(R.styleable.ViewBehavior_behavior_dependsOn, 0);
            mDependTarget = a.getDimensionPixelOffset(R.styleable.ViewBehavior_behavior_dependTarget, UNSPECIFIED_INT);
            a.recycle();
        }
    
    
        /***
         * 初始化一些数值
         * @param parent
         * @param child
         * @param dependency
         */
        void prepare(CoordinatorLayout parent, V child, View dependency) {
            mDependStartY = (int) dependency.getY();
            isPrepared = true;
        }
    
        @Override
        public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
            // depend on the view that has the same id
            return dependency.getId() == mDependViewId;
        }
    
        @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
            // first time, prepare values before continue
            if (!isPrepared) {
                prepare(parent, child, dependency);
            }
            updateView(child, dependency);
            return false;
        }
    
        @Override
        public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
            boolean bool = super.onLayoutChild(parent, child, layoutDirection);
            if (isPrepared) {
                updateView(child, parent.getDependencies(child).get(0));
            }
            return bool;
        }
    
        /***
         * 根据 dependency 去修改View的状态
         * @param child
         * @param dependency
         */
        void updateView(V child, View dependency) {
            float percent = 0;
            float start = 0;
            float current = 0;
            float end = UNSPECIFIED_INT;
            start = mDependStartY;
            current = dependency.getY();
            end = mDependTarget;
            // need to define target value according to the depend type, if not then skip
            if (end != UNSPECIFIED_INT) {
                percent = Math.abs(current - start) / Math.abs(end - start);
            }
    
            updateViewWithPercent(child, percent > 1 ? 1 : percent);
    
        }
    
        /**
         * 根据百分比去实现子类的状态
         *
         * @param child
         * @param percent
         */
         abstract  void updateViewWithPercent(V child, float percent);
    
    
    }
    

    子类只需要去重写updateViewWithPercent(V child, float percent)方法去根据传入的百分比去做动画就好了

    后面我们先实现头像的移动和缩放动画
    首先分解一下动画
    缩放 整个过程中
    横向移动 前50%
    纵向移动 后50%

    public class SimpleViewBehavior extends PercentageViewBehavior<View> {
       //开始的信息
        private int mStartX;
        private int mStartY;
        private int mStartWidth;
        private int mStartHeight;
    
       //结束的信息
        private int targetX;
        private int targetY;
        private int targetWidth;
        private int targetHeight;
    
    
    
        public SimpleViewBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
            // setting values
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewBehavior);
            targetX = a.getDimensionPixelOffset(R.styleable.ViewBehavior_behavior_targetX, UNSPECIFIED_INT);
            targetY = a.getDimensionPixelOffset(R.styleable.ViewBehavior_behavior_targetY, UNSPECIFIED_INT);
            targetWidth = a.getDimensionPixelOffset(R.styleable.ViewBehavior_behavior_targetWidth, UNSPECIFIED_INT);
            targetHeight = a.getDimensionPixelOffset(R.styleable.ViewBehavior_behavior_targetHeight, UNSPECIFIED_INT);
            a.recycle();
        }
    
    
        @Override
        void prepare(CoordinatorLayout parent, View child, View dependency) {
            super.prepare(parent, child, dependency);
    
            mStartX = (int) child.getX();
            mStartY = (int) child.getY();
            mStartWidth = child.getWidth();
            mStartHeight = child.getHeight();
            // if parent fitsSystemWindows is true, add status bar height to target y if specified
            if (Build.VERSION.SDK_INT > 16 && parent.getFitsSystemWindows() && targetY != UNSPECIFIED_INT) {
                int result = 0;
                Resources resources = parent.getContext().getResources();
                int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
                if (resourceId > 0) {
                    result = resources.getDimensionPixelSize(resourceId);
                }
                targetY += result;
            }
        }
    
        @Override
        void updateViewWithPercent(View child, float percent) {
            float scaleWidth = mStartWidth + ((targetWidth - mStartWidth) * percent);
            float scaleHeight = mStartHeight + ((targetHeight - mStartHeight) * percent);
            if (targetWidth != UNSPECIFIED_INT || targetHeight != UNSPECIFIED_INT) {
                child.setScaleX(scaleWidth / mStartWidth);
                child.setScaleY(scaleHeight / mStartHeight);
            }
            if (percent < 0.5) {
                float newX = targetX == UNSPECIFIED_INT ? 0 : (targetX - mStartX) * percent * 2;
                float newWidth = mStartWidth + ((targetWidth - mStartWidth) * percent * 2);
                newX -= (mStartWidth - newWidth) / 2;
                child.setTranslationX(newX);
            } else {
                float newY = targetY == UNSPECIFIED_INT ? 0 : (float) ((targetY - mStartY) * (percent - 0.5) * 2);
                float newHeight = (float) (mStartHeight + ((targetHeight - mStartHeight) * (percent - 0.5) * 2));
                newY -= (mStartHeight - newHeight) / 2;
                child.setTranslationY(newY);
            }
            child.requestLayout();
        }
    }
    

    主要是在复写的updateViewWithPercent(View child, float percent)放大中进行操作
    整个过程的缩放操作

            float scaleWidth = mStartWidth + ((targetWidth - mStartWidth) * percent);
            float scaleHeight = mStartHeight + ((targetHeight - mStartHeight) * percent);
            if (targetWidth != UNSPECIFIED_INT || targetHeight != UNSPECIFIED_INT) {
                child.setScaleX(scaleWidth / mStartWidth);
                child.setScaleY(scaleHeight / mStartHeight);
            }
    

    前50%的移动操作:

                float newX = targetX == UNSPECIFIED_INT ? 0 : (targetX - mStartX) * percent * 2;
                float newWidth = mStartWidth + ((targetWidth - mStartWidth) * percent * 2);
                newX -= (mStartWidth - newWidth) / 2;
                child.setTranslationX(newX);
    

    后50%的移动操作:

                float newY = targetY == UNSPECIFIED_INT ? 0 : (float) ((targetY - mStartY) * (percent - 0.5) * 2);
                float newHeight = (float) (mStartHeight + ((targetHeight - mStartHeight) * (percent - 0.5) * 2));
                newY -= (mStartHeight - newHeight) / 2;
                child.setTranslationY(newY);
    

    这就完成了头像的的整个移动动画
    然后就是文字的隐藏动画 我们它在前50%就完全隐藏掉
    文字隐藏的代码

    public class AlphaViewBehavior extends PercentageViewBehavior<View> {
    
        private float mStartAlpha;
        private float targetAlpha;
    
        public AlphaViewBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
            // setting values
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewBehavior);
            targetAlpha = a.getFloat(R.styleable.ViewBehavior_behavior_targetAlpha, UNSPECIFIED_FLOAT);
            a.recycle();
        }
    
    
        @Override
        void prepare(CoordinatorLayout parent, View child, View dependency) {
            super.prepare(parent, child, dependency);
            mStartAlpha = child.getAlpha();
        }
    
        @Override
        void updateViewWithPercent(View child, float percent) {
            if (percent < 0.5) {
                if (targetAlpha != UNSPECIFIED_FLOAT) {
                    child.setAlpha(mStartAlpha + (targetAlpha - mStartAlpha) * percent * 2);
                }
            }
            child.requestLayout();
        }
    
    
    }
    

    背景图片也是透明度动画后面可以自己去加
    最终实现效果


    test2.gif

    简易的demo就这样了 要添加新的动画可以自己写一个新的自定义behavio试一下

    demo gitHub 地址:https://github.com/525642022/PercentageBehavior

    相关文章

      网友评论

        本文标题:android 自定义Behavior

        本文链接:https://www.haomeiwen.com/subject/uqrlaqtx.html