Android属性动画3-----自定义动画框架

自定义动画框架,就是使用自定义属性,在XML文件中的某个控件添加自定义属性,那么这个控件就会执行相应的动画效果,例如:清晰度、渐变颜色、平移等等。

1、自定义属性

<resources>
	//动画的类型
    <declare-styleable name="animation_type">
    	//清晰度
        <attr name="discrollve_alpha" format="boolean"></attr>
        //X轴的缩放
        <attr name="discrollve_ScaleX" format="boolean"></attr>
        //Y轴的缩放
        <attr name="discrollve_ScaleY" format="boolean"></attr>
        //背景颜色变换的起始颜色
        <attr name="discrollve_fromBgColor" format="color"></attr>
        //背景颜色变换的最终颜色
        <attr name="discrollve_toBgColor" format="color"></attr>
        //动画转换的方向
        <attr name="animation_orientation"></attr>
    </declare-styleable>

    <attr name="animation_orientation">
    	//从上方
        <flag name="fromTop" value="0x01"></flag>
        
        <flag name="fromBottom" value="0x02"></flag>
        <flag name="fromLeft" value="0x04"></flag>
        <flag name="fromRight" value="0x08"></flag>
    </attr>
</resources>

对于自定义控件来说,可以通过自定义属性来完成相应的业务逻辑;但是对于系统控件来说,像TextView、Button、ImageView等等,它们是不能识别自定义属性的,要想自定义属性能够被系统控件识别,通常有2种做法。

(1)自定义FrameLayout,将系统控件包裹起来,这样就能识别自定义属性。这种方式适合于控件数量较少的情况下,如果有100个控件,那么就需要套100个帧布局那是不现实的。

(2)自定义一个布局,然后作为像ConstraintLayout那样,作为该布局的根布局,把全部的控件包裹起来,那么不管里边有多少控件,都能够识别自定义属性。

2、UI绘制

在之前动态换肤框架时,详细介绍过UI的绘制流程,当时只是为了hook系统实例化控件,并没有介绍整个XML布局加载的具体流程,下面就从LayoutInflater的inflate方法开始说。

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                  + Integer.toHexString(resource) + ")");
        }

        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
        //XML资源解析
        XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

inflate方法中,有一个XmlResourceParser ,这个类的主要工作就是解析XML布局。

 // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // Inflate all children under temp against its context.
                    //解析子布局
                    rInflateChildren(parser, temp, attrs, true);

                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }

系统在解析XML布局时,首先从root根节点开始解析,当解析完根节点之后,就需要解析子控件,调用rInflateChildren

 final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }
void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            final String name = parser.getName();

            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException(" cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException(" must be the root element");
            } else {
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }

当解析完根节点之后,就通过createViewFromTag解析子控件,通过addView,将子View添加到容器中,这个容器就是父容器parent根节点。

所以为了给每个系统控件套上一层帧布局,可以在addView方法中,实现。

@Override
    public void addView(View child) {
        //child子View
        AnimatorFrameLayout frameLayout = new AnimatorFrameLayout(getContext());
         //这个AnimatorFrameLayout就是ViewGroup
        frameLayout.addView(child);
        super.addView(frameLayout);
    }
ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs)

ViewGroup通过generateLayoutParams将所有的属性整合,添加到容器中,因此需要在这个方法中,来进行自定义属性的分析。

因为每个系统控件都包裹一个FrameLayout,那么这个FrameLayout就是ViewGroup,解析完ViewGroup之后,就会解析它其中包裹的一个子控件,这个子控件是系统控件,有些自定义属性需要解析,因为默认解析的就是系统控件属性。

  //child子View
            AnimatorFrameLayout frameLayout = new AnimatorFrameLayout(getContext());
            //这个AnimatorFrameLayout就是ViewGroup
            frameLayout.addView(child);
            //设置属性
            frameLayout.setmDiscrollveAlpha(layoutParams.mDiscrollveAlpha);
            frameLayout.setmDiscrollveFromBgColor(layoutParams.mDiscrollveFromBgColor);
            frameLayout.setmDiscrollveScaleX(layoutParams.mDiscrollveScaleX);
            frameLayout.setmDiscrollveScaleY(layoutParams.mDiscrollveScaleY);
            frameLayout.setmDisCrollveTranslation(layoutParams.mDisCrollveTranslation);
            frameLayout.setmDiscrollveToBgColor(layoutParams.mDiscrollveToBgColor);
            super.addView(frameLayout);

3、滑动控件

通过自定义滑动控件,来完成动画的实现。我们需要监听滑动的距离,根据滑动的百分比来控制动画。

Android属性动画3-----自定义动画框架_第1张图片

public class AnimatorScrollerView extends ScrollView {
    public AnimatorScrollerView(Context context) {
        this(context,null);
    }

    public AnimatorScrollerView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public AnimatorScrollerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    //滑动监听
    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);

        //获取AnimatorLinearLayout全部的子控件(AnimatorFrameLayout)
        for (int i = 0; i < mContent.getChildCount(); i++) {
            View child = getChildAt(i);

//            AnimatorFrameLayout frameLayout = (AnimatorFrameLayout) child;
            IScroller scroller = (IScroller) child;

            //该框架设计的核心就是,通过获取系统控件的自定义属性,获取到自定义属性之后,
            //控制动画

            //系数的计算 计算从下面冒出的距离 / 控件的宽度 = 系数

            //计算当前滑动的距离
            int childTop = getTop();
            int abslouteTop = childTop - t;

            int viewHeight = getHeight();
            if(abslouteTop <= viewHeight){
                int scrollHight = viewHeight - abslouteTop;


                float rate = scrollHight / (float)child.getHeight();
                scroller.onScroll(clamp(rate,1,0));
            }else{
                //划出去
                scroller.onResetScroll();
            }
        }
    }

    private float clamp(float rate,float max,float min) {

        return Math.max(Math.min(rate,max),min);
    }

    AnimatorLinearLayout mContent;
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        //在页面渲染完成之后,拿到AnimatorLayout
        mContent = (AnimatorLinearLayout) getChildAt(0);

    }
}

其他2个自定义View的源码:

AnimatorLinearLayout

public class AnimatorLinearLayout extends LinearLayout {
    public AnimatorLinearLayout(Context context) {
        this(context,null,0);
    }

    public AnimatorLinearLayout(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public AnimatorLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new AnimatorLayoutParams(getContext(),attrs);
    }

    @Override
    public void addView(View child, ViewGroup.LayoutParams params) {

        //获取LayoutParams
        AnimatorLayoutParams layoutParams = (AnimatorLayoutParams) params;

        if(isDiscrollve(params)){
            //如果没有自定义属性,就不需要包裹
            super.addView(child);
        }else{
            //child子View
            AnimatorFrameLayout frameLayout = new AnimatorFrameLayout(getContext());
            //这个AnimatorFrameLayout就是ViewGroup
            frameLayout.addView(child);
            //设置属性
            frameLayout.setmDiscrollveAlpha(layoutParams.mDiscrollveAlpha);
            frameLayout.setmDiscrollveFromBgColor(layoutParams.mDiscrollveFromBgColor);
            frameLayout.setmDiscrollveScaleX(layoutParams.mDiscrollveScaleX);
            frameLayout.setmDiscrollveScaleY(layoutParams.mDiscrollveScaleY);
            frameLayout.setmDisCrollveTranslation(layoutParams.mDisCrollveTranslation);
            frameLayout.setmDiscrollveToBgColor(layoutParams.mDiscrollveToBgColor);
            super.addView(frameLayout);
        }

    }

    private boolean isDiscrollve(ViewGroup.LayoutParams params) {
        AnimatorLayoutParams layoutParams = (AnimatorLayoutParams) params;
        return layoutParams.mDiscrollveAlpha ||
                layoutParams.mDiscrollveScaleX ||
                layoutParams.mDiscrollveScaleY ||
                layoutParams.mDisCrollveTranslation != -1 ||
                (layoutParams.mDiscrollveFromBgColor != -1 && layoutParams.mDiscrollveToBgColor != -1);

    }

    //动画属性
    public class AnimatorLayoutParams extends LinearLayout.LayoutParams{

        public boolean mDiscrollveAlpha;
        public boolean mDiscrollveScaleX;
        public boolean mDiscrollveScaleY;
        public int mDisCrollveTranslation;
        public int mDiscrollveFromBgColor;
        public int mDiscrollveToBgColor;

        public AnimatorLayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);

            TypedArray a = getContext().obtainStyledAttributes(attrs,R.styleable.animation_type);
            //没有传属性过来,给默认值FALSE
            mDiscrollveAlpha = a.getBoolean(R.styleable.animation_type_discrollve_alpha, false);
            mDiscrollveScaleX = a.getBoolean(R.styleable.animation_type_discrollve_ScaleX, false);
            mDiscrollveScaleY = a.getBoolean(R.styleable.animation_type_discrollve_ScaleY, false);
            mDisCrollveTranslation = a.getInt(R.styleable.animation_type_animation_orientation, -1);
            mDiscrollveFromBgColor = a.getColor(R.styleable.animation_type_discrollve_fromBgColor, -1);
            mDiscrollveToBgColor = a.getColor(R.styleable.animation_type_discrollve_toBgColor, -1);
            a.recycle();
        }
    }
}

AnimatorFrameLayout

public class AnimatorFrameLayout extends FrameLayout implements IScroller{
    public AnimatorFrameLayout(@NonNull Context context) {
        this(context,null,0);
    }

    public AnimatorFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public AnimatorFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    //保存自定义属性
    //定义很多的自定义属性
    private static final int TRANSLATION_FROM_TOP = 0x01;
    private static final int TRANSLATION_FROM_BOTTOM = 0x02;
    private static final int TRANSLATION_FROM_LEFT = 0x04;
    private static final int TRANSLATION_FROM_RIGHT = 0x08;

    //颜色估值器
    private static ArgbEvaluator sArgbEvaluator = new ArgbEvaluator();
    /**
     * 自定义属性的一些接收的变量
     */
    private int mDiscrollveFromBgColor;//背景颜色变化开始值
    private int mDiscrollveToBgColor;//背景颜色变化结束值
    private boolean mDiscrollveAlpha;//是否需要透明度动画
    private int mDisCrollveTranslation;//平移值
    private boolean mDiscrollveScaleX;//是否需要x轴方向缩放
    private boolean mDiscrollveScaleY;//是否需要y轴方向缩放
    private int mHeight;//本view的高度
    private int mWidth;//宽度


    public void setmDiscrollveFromBgColor(int mDiscrollveFromBgColor) {
        this.mDiscrollveFromBgColor = mDiscrollveFromBgColor;
    }

    public void setmDiscrollveToBgColor(int mDiscrollveToBgColor) {
        this.mDiscrollveToBgColor = mDiscrollveToBgColor;
    }

    public void setmDiscrollveAlpha(boolean mDiscrollveAlpha) {
        this.mDiscrollveAlpha = mDiscrollveAlpha;
    }

    public void setmDisCrollveTranslation(int mDisCrollveTranslation) {
        this.mDisCrollveTranslation = mDisCrollveTranslation;
    }

    public void setmDiscrollveScaleX(boolean mDiscrollveScaleX) {
        this.mDiscrollveScaleX = mDiscrollveScaleX;
    }

    public void setmDiscrollveScaleY(boolean mDiscrollveScaleY) {
        this.mDiscrollveScaleY = mDiscrollveScaleY;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
    }

    @Override
    public void onScroll(float rate) {
        //判断
        if(mDiscrollveAlpha){
            setAlpha(rate);
        }
        if(mDiscrollveScaleX){
            setScaleX(rate);
        }
        if(mDiscrollveScaleY){
            setScaleY(rate);
        }

        //平移动画,有四个方向,需要整合成一个属性
        if(isTranslationFrom(TRANSLATION_FROM_BOTTOM)){
            setTranslationY(mHeight * (1-rate));
        }
        if(isTranslationFrom(TRANSLATION_FROM_TOP)){
            setTranslationY(-mHeight * (1-rate));
        }
        if(isTranslationFrom(TRANSLATION_FROM_LEFT)){
            setTranslationX(-mWidth * (1-rate));
        }
        if(isTranslationFrom(TRANSLATION_FROM_RIGHT)){
            setTranslationX(mWidth * (1-rate));
        }
        if(mDiscrollveFromBgColor != -1 && mDiscrollveToBgColor != -1){
            setBackgroundColor((Integer) sArgbEvaluator.evaluate(rate,mDiscrollveFromBgColor,mDiscrollveToBgColor));
        }
    }

    @Override
    public void onResetScroll() {
        //判断
        if(mDiscrollveAlpha){
            setAlpha(0);
        }
        if(mDiscrollveScaleX){
            setScaleX(0);
        }
        if(mDiscrollveScaleY){
            setScaleY(0);
        }

        //平移动画,有四个方向,需要整合成一个属性
        if(isTranslationFrom(TRANSLATION_FROM_BOTTOM)){
            setTranslationY(mHeight);
        }
        if(isTranslationFrom(TRANSLATION_FROM_TOP)){
            setTranslationY(-mHeight);
        }
        if(isTranslationFrom(TRANSLATION_FROM_LEFT)){
            setTranslationX(-mWidth);
        }
        if(isTranslationFrom(TRANSLATION_FROM_RIGHT)){
            setTranslationX(mWidth);
        }
//        if(mDiscrollveFromBgColor != -1 && mDiscrollveToBgColor != -1){
//            setBackgroundColor((Integer) sArgbEvaluator.evaluate(rate,mDiscrollveFromBgColor,mDiscrollveToBgColor));
//        }
    }

    public boolean isTranslationFrom(int translationMask){
        if(mDisCrollveTranslation == -1){
            //没有偏移
            return false;
        }
        return (mDisCrollveTranslation & translationMask) == translationMask;
    }
}

你可能感兴趣的:(Android属性动画3-----自定义动画框架)