自定义属性动画框架

先来观察完整的动画效果

分析:每个child都可以设置自己的属性动画 根据滑动出自身的高度比例来进行开发者设置的属性动画(平移 渐变 缩放 透明度...)既然是动画框架封装 肯定是希望开发者用的更爽,代码简洁 意图清晰。 

首先设定好自定义的属性参数

<declare-styleable name="DiscrollView_LayoutParams">
        <attr name="discrollve_alpha" format="boolean"/>
        <attr name="discrollve_scaleX" format="boolean"/>
        <attr name="discrollve_scaleY" format="boolean"/>
        <attr name="discrollve_fromBgColor" format="color"/>
        <attr name="discrollve_toBgColor" format="color"/>
        <attr name="discrollve_translation">
            <flag name="fromTop" value="0x01" />
            <flag name="fromBottom" value="0x02" />
            <flag name="fromLeft" value="0x04" />
            <flag name="fromRight" value="0x08" />
        </attr>
    </declare-styleable>

解析为什么discrollve_translation 各个参数0x01 0x02 0x04 0x08
0x01 0000 0000 0000 0001 fromTop
0x02 0000 0000 0000 0010 fromBottom
0x04 0000 0000 0000 0100 fromLeft
0x08 0000 0000 0000 1000 fromRight
这样设置的目的 因为这个平移可以同时多个方向移动 比如我们希望他 左上方向移动 就可以这样设置
app:discrollve_translation = "fromTop|fromLeft"
既 它们的值 0000 0000 0000 0101 将来我们判断的时候 可以这样判断
比如判断是否设置了fromTop  就拿这个值 0101 & 0001fromTop = 0001 如果得出来的值等于fromTop就说明有设置这个值

我们希望开发者用这个属性动画框架的时候 只需要对对应的child设置相应属性参数 将来运行的时候就会执行相应的属性动画
理想开发者在布局文件设定即可 
 

<com.twy.mywearview.MyScrollView xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:discrollve="http://schemas.android.com/apk/res-auto">
    <com.twy.mywearview.MyLinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
       <ImageView
           android:layout_width="match_parent"
           android:layout_height="600dp"
           android:src="@mipmap/tb_bg"
           android:scaleType="centerCrop"/>
        <TextView
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:background="#007788"
            android:textColor="@android:color/black"
            android:textSize="25sp"
            android:padding="25dp"
            android:gravity="center"
            android:fontFamily="serif"
            android:text="带上您的行李箱,准备shopping!"
            discrollve:discrollve_alpha="true"
            tools:ignore="MissingPrefix" />
        <ImageView
            android:layout_width="200dp"
            android:layout_height="120dp"
            android:layout_gravity="top|right"
            discrollve:discrollve_translation="fromLeft|fromBottom"
            discrollve:discrollve_alpha="true"
            android:src="@mipmap/baggage"
            tools:ignore="MissingPrefix" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:textColor="@android:color/black"
            android:textSize="25sp"
            android:padding="25dp"
            android:gravity="center"
            android:fontFamily="serif"
            android:text="准备好相机,这里有你想象不到的惊喜!"
            discrollve:discrollve_fromBgColor="#ffff00"
            discrollve:discrollve_toBgColor="#88EE66"
            />

        <ImageView
            android:layout_width="220dp"
            android:layout_height="110dp"
            android:layout_gravity="right"
            android:src="@mipmap/camera"
            discrollve:discrollve_translation="fromRight" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="20dp"
            android:fontFamily="serif"
            android:gravity="center"
            android:background="#D97C1F"
            android:text="这次淘宝造物节真的来了,我们都在造,你造吗?\n
							7月22日-7月24日\n
							上海世博展览馆\n
							在现场,我们造什么?"
            android:textSize="23sp"
            discrollve:discrollve_alpha="true"
            discrollve:discrollve_translation="fromBottom" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_gravity="center"
            android:src="@mipmap/sweet"
            discrollve:discrollve_scaleX="true"
            discrollve:discrollve_scaleY="true"  />
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_gravity="center"
            android:src="@mipmap/shoes"
            discrollve:discrollve_alpha="true"
            discrollve:discrollve_scaleX="true"
            discrollve:discrollve_scaleY="true"
            discrollve:discrollve_translation="fromLeft|fromBottom" />
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_gravity="center"
            android:src="@mipmap/shoes"
            discrollve:discrollve_alpha="true"
            discrollve:discrollve_scaleY="true"
            discrollve:discrollve_translation="fromRight|fromTop" />
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_gravity="center"
            android:src="@mipmap/sweet"
            discrollve:discrollve_alpha="true"
            discrollve:discrollve_scaleY="true"
            discrollve:discrollve_translation="fromLeft" />
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_gravity="center"
            android:src="@mipmap/camera"
            discrollve:discrollve_scaleY="true"
            discrollve:discrollve_translation="fromLeft" />
    </com.twy.mywearview.MyLinearLayout>
</com.twy.mywearview.MyScrollView>

理想中 我们只需要这样写 运行起来 对应child就会自己根据用户滑动自身可见高度比例 执行自身设置的属性动画
问题1:这样的话 首先我得所对应得child对象本身 设置属性动画
问题2:获取到用户对所对应child对象设置属性的参数
问题3:监听用户上下滑动
针对上述问题 我们必须自定义一个LinearLayout和FramLayout容器 
自定义FramLayout容器 是用来包裹有设置属性动画的控件 目的是为了在该控件 设置对应的属性动画
自定义LinerLayout容器 有两个目的 获取到自定义属性参数 在addView实现偷天换日 首先将child的添加到我们自定义
FramLayout容器 再将该容器添加到LinerLayout容器 
问题3我们需要自定ScrollView以便于我们监听用户上下滑动

先来看看自定义的MyFrameLayout 此定义的控件主要是用来包裹有设置属性参数的控件 是用来执行属性动画 这样的话
我们让它实现一个接口 用来规范 执行动画 和恢复原来的样子
 

public interface DiscrollInterface {
    /**
     * 当滑动的时候调用该方法,用来控制里面的控件执行相应的动画
     * @param ratio 动画执行的百分比(child view画出来的距离百分比)
     */
    public void onDiscroll(float ratio);

    /**
     * 重置动画--让view所有的属性都恢复到原来的样子
     */
    public void onResetDiscroll();
}

将来要执行动画 就执行child.onDiscroll(ratio)

public class MyFrameLayout extends FrameLayout implements DiscrollInterface {

    //保存自定义属性
    //定义很多的自定义属性
    /**
     *  <attr name="discrollve_translation">
     <flag name="fromTop" value="0x01" />
     <flag name="fromBottom" value="0x02" />
     <flag name="fromLeft" value="0x04" />
     <flag name="fromRight" value="0x08" />
     </attr>
     0000000001
     0000000010
     0000000100
     0000001000
     top|left
     0000000001 top
     0000000100 left 或运算 |
     0000000101
     反过来就使用& 与运算
     */
    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 MyFrameLayout(Context context) {
        super(context);
    }

    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 onDiscroll(float ratio) {
        if(mDiscrollveAlpha){
            setAlpha(ratio);
        }
        if(mDiscrollveScaleX){
            setScaleX(ratio);
        }
        if(mDiscrollveScaleY){
            setScaleY(ratio);
        }
        if(isTranslationFrom(TRANSLATION_FROM_LEFT)){
            setTranslationX(-mWidth*(1-ratio));//mWidth--->0(0代表恢复到原来的位置)
        }
        if(isTranslationFrom(TRANSLATION_FROM_RIGHT)){
            setTranslationX(mWidth*(1-ratio));
        }
        if(isTranslationFrom(TRANSLATION_FROM_TOP)){
            setTranslationY(-mHeight*(1-ratio));////mHeight--->0(0代表恢复到原来的位置)
        }
        if(isTranslationFrom(TRANSLATION_FROM_BOTTOM)){
            setTranslationY(mHeight*(1-ratio));
        }

        //判断从什么颜色到什么颜色
        if(mDiscrollveFromBgColor!=-1&&mDiscrollveToBgColor!=-1){
            setBackgroundColor((int) sArgbEvaluator.evaluate(ratio, mDiscrollveFromBgColor, mDiscrollveToBgColor));
        }

    }

    @Override
    public void onResetDiscroll() {
        if(mDiscrollveAlpha){
            setAlpha(0);
        }
        if(mDiscrollveScaleX){
            setScaleX(0);
        }
        if(mDiscrollveScaleY){
            setScaleY(0);
        }
        //平移动画  int值:left,right,top,bottom    left|bottom
        if(isTranslationFrom(TRANSLATION_FROM_BOTTOM)){//是否包含bottom
            setTranslationY(mHeight);//height--->0(0代表恢复到原来的位置)
        }
        if(isTranslationFrom(TRANSLATION_FROM_TOP)){//是否包含bottom
            setTranslationY(-mHeight);//-height--->0(0代表恢复到原来的位置)
        }
        if(isTranslationFrom(TRANSLATION_FROM_LEFT)){
            setTranslationX(-mWidth);//mWidth--->0(0代表恢复到本来原来的位置)
        }
        if(isTranslationFrom(TRANSLATION_FROM_RIGHT)){
            setTranslationX(mWidth);//-mWidth--->0(0代表恢复到本来原来的位置)
        }
    }

    private boolean isTranslationFrom(int translationMask){
        if(mDisCrollveTranslation ==-1){
            return false;
        }
        //fromLeft|fromeBottom & fromBottom = fromBottom
        return (mDisCrollveTranslation & translationMask) == translationMask;
    }

}

自定义LinearLayout 主要两个目的 为每个子控件获取到开发者设定的属性参数  另外实现偷天换日 就是将设有属性的控件用 自定义的FramLayout的控件包裹起来 再将此控件添加到Linerlayout里面去
 

public class MyLinearLayout extends LinearLayout {
    public MyLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOrientation(VERTICAL);
    }

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

    @Override
    public void addView(View child, ViewGroup.LayoutParams params) {
        MyLayoutParams p = (MyLayoutParams) params;
        if(isDiscrollvable(p)){
            //偷天换日
            MyFrameLayout mf = new MyFrameLayout(getContext());
            mf.addView(child);
            mf.setmDiscrollveAlpha(p.mDiscrollveAlpha);
            mf.setmDiscrollveFromBgColor(p.mDiscrollveFromBgColor);
            mf.setmDiscrollveToBgColor(p.mDiscrollveToBgColor);
            mf.setmDiscrollveScaleX(p.mDiscrollveScaleX);
            mf.setmDiscrollveScaleY(p.mDiscrollveScaleY);
            mf.setmDisCrollveTranslation(p.mDisCrollveTranslation);
            super.addView(mf,params);
        }else {
            super.addView(child, params);
        }
    }

    private boolean isDiscrollvable(MyLayoutParams p){
        return p.mDiscrollveAlpha ||
                p.mDiscrollveScaleX ||
                p.mDiscrollveScaleY ||
                p.mDisCrollveTranslation != -1 ||
                (p.mDiscrollveFromBgColor !=-1 && p.mDiscrollveToBgColor != -1);
    }


    public class MyLayoutParams extends LinearLayout.LayoutParams{
        public int mDiscrollveFromBgColor;//背景颜色变化开始值
        public int mDiscrollveToBgColor;//背景颜色变化结束值
        public boolean mDiscrollveAlpha;//是否需要透明度动画
        public int mDisCrollveTranslation;//平移值
        public boolean mDiscrollveScaleX;//是否需要x轴方向缩放
        public boolean mDiscrollveScaleY;//是否需要y轴方向缩放
        public MyLayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            //解析attrs得到自定义的属性,保存
            TypedArray a = getContext().obtainStyledAttributes(attrs,R.styleable.DiscrollView_LayoutParams);
            mDiscrollveAlpha = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_alpha,false);
            mDiscrollveScaleX = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleX, false);
            mDiscrollveScaleY = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleY, false);
            mDisCrollveTranslation = a.getInt(R.styleable.DiscrollView_LayoutParams_discrollve_translation, -1);
            mDiscrollveFromBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_fromBgColor, -1);
            mDiscrollveToBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_toBgColor, -1);
            a.recycle();
        }
    }

}

最后自定义ScrollView 监听用户滑动 算出child浮现高度 和child高度比例 0~1 以便根据该值 去执行该控件的属性动画

public class MyScrollView extends ScrollView {
    private MyLinearLayout mContent;
    public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mContent = (MyLinearLayout) getChildAt(0);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        View first = mContent.getChildAt(0);
        first.getLayoutParams().height = getHeight();
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        int scrollViewHeight = getHeight();
        //监听滑动的程度---childView从下面冒出来多少距离/childView.getHeight();----0~1:动画执行的百分比ratio
        //动画执行的百分比ratio控制动画执行
        for(int i = 0;i<mContent.getChildCount();i++){
            View child = mContent.getChildAt(i);
            if(!(child instanceof DiscrollInterface)){
                continue;
            }
            int childHeight = child.getHeight();
            //接口回掉,传递执行的百分比给MyFrameLayout
            //低耦合高内聚
            DiscrollInterface discrollInterface = (DiscrollInterface) child;
            //child离parent顶部的高度
            int childTop = child.getTop();
            //滑出去的这一截高度:t
//            child离屏幕顶部的高度
            int absoluteTop = childTop -t;
            if(absoluteTop<=scrollViewHeight){
                //child浮现的高度 = ScrollView的高度 - child离屏幕顶部的高度
                int visibleGap = scrollViewHeight - absoluteTop;
                //float ratio = child浮现的高度/child的高度
                float ratio = visibleGap / (float) childHeight;
                //确保ratio是在0~1的范围
                discrollInterface.onDiscroll(clamp(ratio,1f,0f));
            }else {
                discrollInterface.onResetDiscroll();
            }
        }
    }

    //求三个数的中间大小的一个数。
    public static float clamp(float value, float max, float min){
        return Math.max(Math.min(value, max), min);
    }
}

ratio值的计算

以上就是该自定义属性框架的分析

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章