Android 手写动画框架

作者: Samlss | 来源:发表于2018-08-06 12:23 被阅读262次

效果图

我们先来看看效果图:


screenshot1.gif screenshot2.gif
  • 根据ScrollView或者HorizontalScrollView中的滑动距离设置对应子view的动画效果
  • 自定义ScrollView、HorizontalScrollView,以及LinearLayoutView
  • 获取并记录在布局文件中定义的自定义动画属性值

定义属性值

定义属性值,在layout.xml即布局文件中可以给view(泛指,可以为任何view)指定动画属性值,例如是否进行alpha动画,或者指定出场方式。

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <declare-styleable name="XAnimator">
        <attr name="x_alpha" format="boolean"/>
        <attr name="x_scaleX" format="boolean"/>
        <attr name="x_scaleY" format="boolean"/>
        <attr name="x_startBgColor" format="color"/>
        <attr name="x_endBgColor" format="color"/>

        <attr name="from_direction">
            <flag name="top" value="1" />
            <flag name="bottom" value="2" />
            <flag name="left" value="3" />
            <flag name="right" value="4" />
        </attr>
    </declare-styleable>
</resources>
属性 说明
x_alpha 是否执行透明值变化动画(范围为0-1)
x_scaleX 是否执行x轴缩放动画(范围为0-1)
x_scaleY 是否执行y轴缩放动画(范围为0-1)
x_startBgColor 背景颜色渐变动画起始颜色值
x_endBgColor 背景颜色渐变动画结束颜色值
from_direction 出现方向,top, bottom, left, right,分别为从顶部,底部,左边,右边出现

定义属性类

用于记录在布局文件中指定的动画属性值XAnimator(即上面所定义的属性值)
在这里就不列出来了,可查看 XAnimatorAttr

布局代码

我们可以看到,使用就像ScrollView+LinearLayout一样,只不过在需要进行动画的子view(例如ImageView和LinearLayout)中指定了动画属性,在XAnimatorScrollView进行滑动的时候就可以根据指定的动画属性进行对应的设置。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.iigo.library.XAnimatorScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.iigo.library.XAnimatorLinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:src="@mipmap/duola_big" />

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:src="@mipmap/duola1"
                app:x_alpha="true"
                app:x_scaleX="true"
                app:x_scaleY="true"
                app:from_direction="left"/>

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:src="@mipmap/duola2"
                app:from_direction="right"
                app:x_alpha="true"
                app:x_scaleX="true"
                app:x_scaleY="true" />

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:src="@mipmap/duola3"
                app:from_direction="left"
                app:x_alpha="true" />

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:src="@mipmap/duola4"
                app:from_direction="right"
                app:x_alpha="true" />


            <ImageView
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:src="@mipmap/duola5"
                app:x_endBgColor="@android:color/holo_red_light"
                app:x_startBgColor="@android:color/holo_green_light" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:x_endBgColor="@android:color/holo_orange_light"
                app:x_startBgColor="@android:color/holo_green_light">

                <ImageView
                    android:layout_width="match_parent"
                    android:layout_height="200dp"
                    android:src="@mipmap/duola6" />
            </LinearLayout>

        </com.iigo.library.XAnimatorLinearLayout>
    </com.iigo.library.XAnimatorScrollView>
</RelativeLayout>

获取动画属性值

上面的布局代码中,我们在XAnimatorLinearLayout中使用了系统控件,例如ImageView和LinearLayout,那我们如何获取定义的动画属性值(即XAnimator)呢?

可以根据XmlResourceParser来进行获取

还不了解的可以看我文章 Android 关于XmlResourceParser

我们可以通过接口

XmlResourceParser parser = context.getResources().getLayout(layoutId);

解析layout资源文件来遍历获取动画属性

主要接口可参考 XAnimator

动画执行

在我们自定义的XAnimatorLinearLayout 中,增加onScrollChanged接口,在XAnimatorScrollView或XAnimatorHorizontalScrollView 调用onScrollChanged的时候,调用XAnimatorLinearLayout 中的onScrollChanged接口,再遍历子view,根据其设置的动画属性,设置对应scroll百分比的动画值。

public final class XAnimatorLinearLayout extends LinearLayout{
    private static ArgbEvaluator argbEvaluator = new ArgbEvaluator();

    public XAnimatorLinearLayout(Context context) {
        super(context);
    }

    public XAnimatorLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public XAnimatorLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public void onScrollChanged(int l, int t, int oldl, int oldt){
        boolean isVertical = getOrientation() == VERTICAL;

        for (int i = 0;i < getChildCount();i++) {
            View child = getChildAt(i);

            XAnimatorAttr xAnimatorAttr = (XAnimatorAttr) child.getTag(R.id.XAnimator);
            if (xAnimatorAttr == null){
                continue;
            }

            int referDistance = isVertical ? child.getTop() : child.getLeft();
            int referParentSize = isVertical ? ((ViewGroup)getParent()).getHeight()
                    : ((ViewGroup)getParent()).getWidth();
            int referChildSize  = isVertical ? child.getHeight() : child.getWidth();
            int absolute = referDistance - (isVertical ? t : l);
            int visibleSize = referParentSize - absolute;

            float ratio = limitValue(visibleSize / (float) referChildSize, 0, 1);

            if (absolute <= referParentSize) {
                performAnimator(child, xAnimatorAttr, ratio);
            } else {
                drawHideState(child, xAnimatorAttr);
            }
        }
    }

    /**
     * Now perform animator for the child view.
     *
     * @param child The child view.
     * @param xAnimatorAttr The {@link XAnimatorAttr} of the child view.
     * @param ratio The ratio of the scroll(0 <= ration <= 1).
     * */
    private void performAnimator(View child, XAnimatorAttr xAnimatorAttr, float ratio){
        if (child == null
                || xAnimatorAttr == null){
            return;
        }
        if (xAnimatorAttr.isAlpha()){
            child.setAlpha(ratio);
        }

        if (xAnimatorAttr.isScaleX()){
            child.setScaleX(ratio);
        }

        if (xAnimatorAttr.isScaleY()){
            child.setScaleY(ratio);
        }

        if (xAnimatorAttr.getStartBgColor() != -1
                && xAnimatorAttr.getEndBgColor() != -1){
            child.setBackgroundColor((Integer) argbEvaluator.evaluate(ratio,
                    xAnimatorAttr.getStartBgColor(),
                    xAnimatorAttr.getEndBgColor()));
        }

        switch (xAnimatorAttr.getFromDirection()){
            case XAnimatorAttr.FROM_DIRECTION_BOTTOM:
                child.setTranslationY(child.getHeight() * (1 - ratio));
                break;

            case XAnimatorAttr.FROM_DIRECTION_TOP:
                child.setTranslationY(-child.getHeight() * (1 - ratio));
                break;

            case XAnimatorAttr.FROM_DIRECTION_LEFT:
                child.setTranslationX(-child.getWidth() * (1 - ratio));
                break;

            case XAnimatorAttr.FROM_DIRECTION_RIGHT:
                child.setTranslationX(child.getWidth() * (1 - ratio));
                break;

            default: break;
        }
    }

    /**
     * When the child view is not yet displayed on the screen, will draw its hide state.
     *
     * @param child The child view.
     * @param xAnimatorAttr  The attr of the child view.
     * */
    private void drawHideState(View child, XAnimatorAttr xAnimatorAttr){
        if (child == null
                || xAnimatorAttr == null){
            return;
        }

        if(xAnimatorAttr.isAlpha()){
            child.setAlpha(0);
        }
        if(xAnimatorAttr.isScaleX()){
            child.setScaleX(0);
        }
        if(xAnimatorAttr.isScaleY()){
            child.setScaleY(0);
        }

        switch (xAnimatorAttr.getFromDirection()){
            case XAnimatorAttr.FROM_DIRECTION_BOTTOM:
                child.setTranslationY(child.getHeight());
                break;

            case XAnimatorAttr.FROM_DIRECTION_TOP:
                child.setTranslationY(-child.getHeight());
                break;

            case XAnimatorAttr.FROM_DIRECTION_LEFT:
                child.setTranslationX(-child.getWidth());
                break;

            case XAnimatorAttr.FROM_DIRECTION_RIGHT:
                child.setTranslationX(child.getWidth());
                break;

                default: break;
        }
    }

    /**
     * Limit the value between min and max.
     *
     * @param value The limit value.
     * @param min The min value.
     * @param max The max value.
     * */
    private float limitValue(float value, float min ,float max) {
        return Math.max(Math.min(value,max), min);
    }
}

Github

相关文章

网友评论

    本文标题:Android 手写动画框架

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