自定义动画框架

在开发中,为了实现某些固定可配置的动画,即在布局中实现进行配置(或者在代码中配置)就可以完成的某些动画框架。
其实,说白了也就是自定义特殊控件,在控件中处理一些常用可规范的动画效果,比如:随着滑动而伴随的加速减速显现、缩放、透明度等效果。
做一个自己封装好的效果需要考虑:便于使用和重用;便于配置;

实现的思路:
(1)自定义view
(2)设置可选择的配置参数
(3)处理动画

实现的方式:
1.偷天换日,给每一个需要配置自定义属性的子控件外面包裹一层自定义容器。
(1)DiscrollView:重写ScrollView,处理滑动监听,并将动画需要的数据通过接口的方式传递给view

public class DiscrollView extends ScrollView {

    private DiscrollViewContent mContent;

    public DiscrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    @Override
    protected void onFinishInflate() {
        // TODO Auto-generated method stub
        super.onFinishInflate();
        View content = getChildAt(0);
        mContent = (DiscrollViewContent)content;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        // TODO Auto-generated method stub
        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) {
        // TODO Auto-generated method stub
        super.onScrollChanged(l, t, oldl, oldt);

        int scrollViewHeight = getHeight();
        //监听滑动----接口---->控制DiscrollViewContent的属性
        for(int i=0;i<mContent.getChildCount();i++){//遍历MyLinearLayout里面所有子控件(MyViewGroup)
            View child = mContent.getChildAt(i);
            if(!(child instanceof DiscrollvableInterface)){
                continue;
            }

            //ratio:0~1
            DiscrollvableInterface discrollvableInterface =  (DiscrollvableInterface) child;
            //1.child离scrollview顶部的高度 a
            int discrollvableTop = child.getTop();
            int discrollvableHeight = child.getHeight();

            //2.得到scrollview滑出去的高度  b  就是int t,
            //3.得到child离屏幕顶部的高度  c
            int discrollvableAbsoluteTop = discrollvableTop - t;
            //什么时候执行动画?当child滑进屏幕的时候
            if(discrollvableAbsoluteTop<=scrollViewHeight){
                int visibleGap = scrollViewHeight - discrollvableAbsoluteTop;
                //确保ratio是在0~1,超过了1 也设置为1
                discrollvableInterface.onDiscrollve(clamp(visibleGap/(float)discrollvableHeight, 1f,0f));
            }else{//否则,就恢复到原来的位置
                discrollvableInterface.onResetDiscrollve();
            }
        }
    }

    public static float clamp(float value, float max, float min){
        return Math.max(Math.min(value, max), min);
    }

}

(2)DiscrollViewContent,重写LinearLayout,主要是将子view进行一层包装,这里使用FrameLayout。核心就在此,只要这个FrameLayout执行动画,那么里面被包裹的view也就依附于包裹容器呈现出动画效果,偷梁换柱的实现这个效果。
同时,它将所有配置数据封装起来,设置给了包裹容器,方便包包裹容器做处理。

public class DiscrollViewContent extends LinearLayout {

    public DiscrollViewContent(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOrientation(VERTICAL);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        // 得到xml里面穿过来的参数
        return new MyLayoutParams(getContext(),attrs);
    }

    @Override
    public void addView(View child, int index,
            android.view.ViewGroup.LayoutParams params) {
        //从child里面拿到我自定义的属性,传到discrollvableView里面
        MyLayoutParams p = (MyLayoutParams) params;
        if(!isDiscrollvable(p)){//判断该view是否穿了自定义属性值,不是就不需要执行动画,不包一层FrameLayout
            super.addView(child, index, params);
        }else{
            //在addView里面插一脚,往child外面包裹一层FrameLayout
            DiscrollvableView discrollvableView = new DiscrollvableView(getContext());
            discrollvableView.setmDiscrollveAlpha(p.mDiscrollveAlpha);
            discrollvableView.setmDisCrollveTranslation(p.mDisCrollveTranslation);
            discrollvableView.setmDiscrollveScaleX(p.mDiscrollveScaleX);
            discrollvableView.setmDiscrollveScaleY(p.mDiscrollveScaleY);
            discrollvableView.setmDiscrollveFromBgColor(p.mDiscrollveFromBgColor);
            discrollvableView.setmDiscrollveToBgColor(p.mDiscrollveToBgColor);

            discrollvableView.addView(child);
            super.addView(discrollvableView, index, params);
        }
    }

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

    public static 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 context, AttributeSet attrs) {
            super(context, attrs);
            // 从child里面拿到我自定义的属性
            TypedArray a = context.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();
        }

    }


}

(3)DiscrollvableView,继承了FrameLayout,它是包裹容器,用来处理动画效果。动画效果的执行用接口实现。

public class DiscrollvableView extends FrameLayout implements DiscrollvableInterface{

    /**
     *  <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


        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 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) {
        // TODO Auto-generated method stub
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
        onResetDiscrollve();
    }


    public DiscrollvableView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    public DiscrollvableView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    @Override
    public void onDiscrollve(float ratio) {
        // ratio:0~1
        //控制自身的动画属性
        if(mDiscrollveAlpha){
            setAlpha(ratio);
        }
        if(mDiscrollveScaleX){
            setScaleX(ratio);
        }
        if(mDiscrollveScaleY){
            setScaleY(ratio);
        }

//      int mDisCrollveTranslation 有很多种枚举的情况

        //判断到底是哪一种值:fromTop,fromBottom,fromLeft,fromRight
        //fromBottom
        if(isDiscrollTranslationFrom(TRANSLATION_FROM_BOTTOM)){
            setTranslationY(mHeight*(1-ratio));//mHeight-->0(代表原来的位置)
        }
        if(isDiscrollTranslationFrom(TRANSLATION_FROM_TOP)){
            setTranslationY(-mHeight*(1-ratio));//-mHeight-->0(代表原来的位置)
        }
        if(isDiscrollTranslationFrom(TRANSLATION_FROM_LEFT)){
            setTranslationX(-mWidth*(1-ratio));//-width-->0(代表原来的位置)
        }
        if(isDiscrollTranslationFrom(TRANSLATION_FROM_RIGHT)){
            setTranslationX(mWidth*(1-ratio));//width-->0(代表原来的位置)
        }

        //颜色渐变动画    
        if(mDiscrollveFromBgColor!=-1&&mDiscrollveToBgColor!=-1){
            //ratio=0.5 color=中间颜色
            setBackgroundColor((Integer) sArgbEvaluator.evaluate(ratio, mDiscrollveFromBgColor, mDiscrollveToBgColor));
        }

    }

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

    @Override
    public void onResetDiscrollve() {
        //控制自身的动画属性
        if(mDiscrollveAlpha){
            setAlpha(0);
        }
        if(mDiscrollveScaleX){
            setScaleX(0);
        }
        if(mDiscrollveScaleY){
            setScaleY(0);
        }

//      int mDisCrollveTranslation 有很多种枚举的情况

        //判断到底是哪一种值:fromTop,fromBottom,fromLeft,fromRight
        //fromBottom
        if(isDiscrollTranslationFrom(TRANSLATION_FROM_BOTTOM)){
            setTranslationY(mHeight);//mHeight-->0(代表原来的位置)
        }
        if(isDiscrollTranslationFrom(TRANSLATION_FROM_TOP)){
            setTranslationY(-mHeight);//-mHeight-->0(代表原来的位置)
        }
        if(isDiscrollTranslationFrom(TRANSLATION_FROM_LEFT)){
            setTranslationX(-mWidth);//-width-->0(代表原来的位置)
        }
        if(isDiscrollTranslationFrom(TRANSLATION_FROM_RIGHT)){
            setTranslationX(mWidth);//width-->0(代表原来的位置)
        }   
    }
}

(4)DiscrollvableInterface,动画执行的回调接口

public interface DiscrollvableInterface {

    /**
     * 当滑动的时候调用该方法,用来控制里面的控件执行相应的动画
     * @param ratio
     */
    public void onDiscrollve(float ratio);

    /**
     * 重置view的属性----恢复view的原来属性
     */
    public void onResetDiscrollve();


}

(5)Activity的布局

<com.seasons.live.discrollview.DiscrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:discrollve="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.seasons.live.discrollview.DiscrollViewContent
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="600dp"
            android:background="@android:color/white"
            android:textColor="@android:color/black"
            android:textSize="25sp"
            android:padding="25dp"
            tools:visibility="gone"
            android:gravity="center"
            android:fontFamily="serif"
            android:text="冯绍峰对着倪妮发誓说:‘’如果有一天我离开了你,我就把名字倒着念‘’。倪妮说:‘我也是’’!——尼玛,看着我也是醉了!" />

        <View
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:background="#007788"
            discrollve:discrollve_alpha="true"
             />

        <ImageView
            android:layout_width="200dp"
            android:layout_height="120dp"
            discrollve:discrollve_alpha="true"
            discrollve:discrollve_translation="fromLeft|fromBottom"
            android:src="@drawable/baggage" />

        <View
            android:layout_width="match_parent"
            android:layout_height="200dp"
            discrollve:discrollve_fromBgColor="#ffff00"
            discrollve:discrollve_toBgColor="#88EE66" />

        <ImageView
            android:layout_width="220dp"
            android:layout_height="110dp"
            android:layout_gravity="right"
            android:src="@drawable/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:text="眼见范冰冰与李晨在一起了,孩子会取名李冰冰;李冰冰唯有嫁给范伟,生个孩子叫范冰冰,方能扳回一城。"
            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="@drawable/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="@drawable/camera"
            discrollve:discrollve_translation="fromLeft|fromBottom"
              />

    </com.seasons.live.discrollview.DiscrollViewContent>

</com.seasons.live.discrollview.DiscrollView>

2.偷天换日,可以自定义LayoutInflater,自己来控制view的加载。
(1)核心:在加载view的时候使用重写新的ParallaxLayoutInflater。

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        Bundle bundle = getArguments();
        int layoutId = bundle.getInt("layoutId");
//      View view = inflater.inflate(layoutId, container);
        ParallaxLayoutInflater layoutInflater = new ParallaxLayoutInflater(inflater, getActivity(),this);
        return layoutInflater.inflate(layoutId, null);
    }

其实就是在渲染view这个步骤的前面进行了一层处理,获取子view中所有自定义属性,并将这些属性封装成一个tag数据,并给view添加一个tag,方便在动画的时候根据view的属性来做动画处理。至于View的加载过程,可查看LayoutInflater源码。

public class ParallaxLayoutInflater extends LayoutInflater {

    private ParallaxFragment fragment;

    protected ParallaxLayoutInflater(LayoutInflater original, Context newContext,ParallaxFragment fragment) {
        super(original, newContext);
        this.fragment = fragment;
        setFactory(new ParallaxFactory(this));
    }

    @Override
    public LayoutInflater cloneInContext(Context newContext) {
        // TODO Auto-generated method stub
        return new ParallaxLayoutInflater(this, newContext,fragment);
    }

    //自定义工厂类,视图创建的工厂类
    class ParallaxFactory implements Factory{

        private LayoutInflater inflater;
        public ParallaxFactory(LayoutInflater inflater) {
            this.inflater = inflater;
        }
        @Override
        public View onCreateView(String name, Context context,
                AttributeSet attrs) {
            //1.实例化里面的view
            View view = null;
            if(view==null){
                view = createView(name,context,attrs);
            }
            Log.i("ricky", "view:"+view);
            if(view!=null){
                //获取自定义属性,并将自定义标签值绑定到view上面
                getCustomAttrs(context,attrs,view);
                fragment.getViews().add(view);
            }
            return view;
        }

        private void getCustomAttrs(Context context, AttributeSet attrs, View view) {
            //所有自定义的属性
            int[] attrIds = {
                    R.attr.a_in,
                    R.attr.a_out,
                    R.attr.x_in,
                    R.attr.x_out,
                    R.attr.y_in,
                    R.attr.y_out,
            };

            TypedArray a = context.obtainStyledAttributes(attrs, attrIds);

            if(a!=null && a.length()>0){
                ParallaxViewTag tag = new ParallaxViewTag();
                tag.alphaIn = a.getFloat(0, 0f);
                tag.alphaOut = a.getFloat(1, 0f);
                tag.xIn = a.getFloat(2, 0f);
                tag.xOut = a.getFloat(3, 0f);
                tag.yIn = a.getFloat(4, 0f);
                tag.yOut = a.getFloat(5, 0f);
                view.setTag(R.id.parallax_view_tag, tag);
    //          view.setTag(obj);
            }
            a.recycle();
        }

        private final String[] prefixs = {
                "android.widget.",
                "android.view."
        };

        private View createView(String name, String prefix,Context context, AttributeSet attrs) {
            try {
                return inflater.createView(name, prefix, attrs);
            } catch (Exception e) {
                return null;
            }
        }

        private View createView(String name, Context context, AttributeSet attrs) {
            //通过系统的inflater类创建视图
            if(name.contains(".")){//自定义控件,已经是全类名了
                return createView(name, null,context, attrs);
            }else{
//              android.widget.ImageView   "android.widget."+name
//              android.view.SurfaceView
                for(String prefix:prefixs){
                    View view =  createView(name, prefix, context, attrs);
                    if(view!=null){
                        return view;
                    }
                }
            }

            return null;
        }

    }


}

封装的tag:

public class ParallaxViewTag {
    protected int index;
    protected float xIn;
    protected float xOut;
    protected float yIn;
    protected float yOut;
    protected float alphaIn;
    protected float alphaOut;


    @Override
    public String toString() {
        return "ParallaxViewTag [index=" + index + ", xIn=" + xIn + ", xOut="
                + xOut + ", yIn=" + yIn + ", yOut=" + yOut + ", alphaIn="
                + alphaIn + ", alphaOut=" + alphaOut + "]";
    }


}
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item name="parallax_pager" type="id"/>
    <item name="parallax_view_tag" type="id"/>
</resources>

viewpage滑动的时候处理:

public class ParallaxContainer extends FrameLayout implements OnPageChangeListener {

    private List<ParallaxFragment> fragments;
    private float containerWidth;

    public ParallaxContainer(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub

    }

    public void setUp(int... ids ){
        fragments = new ArrayList<ParallaxFragment>();
        for (int i = 0; i <ids.length ; i++) {
            ParallaxFragment f = new ParallaxFragment();
            Bundle args = new Bundle();
            args.putInt("layoutId", ids[i]);
            f.setArguments(args );
            fragments.add(f);
        }

        //1.viewpager
        ViewPager vp = new ViewPager(getContext());
        vp.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        vp.setId(R.id.parallax_pager);

        ParallaxAdapter adapter = new ParallaxAdapter(((FragmentActivity)getContext()).getSupportFragmentManager(), fragments);
        vp.setAdapter(adapter);

        addView(vp, 0);

        vp.addOnPageChangeListener(this);
    }

    @Override
    public void onPageScrollStateChanged(int arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onPageScrolled(int position, float positionOffset,
            int positionOffsetPixels) {
        containerWidth = getWidth();
        //进入的页面
        ParallaxFragment inFragment = null;
        try {
            inFragment = fragments.get(position-1);
        } catch (Exception e) {
        }
        //退出的页面
        ParallaxFragment outFragment = null;
        try {
            outFragment = fragments.get(position);
        } catch (Exception e) {
        }

        if(inFragment!=null){
            List<View> views = inFragment.getViews();
            for (int i = 0; i < views.size(); i++) {
                View view = views.get(i);
                ParallaxViewTag tag = (ParallaxViewTag) view.getTag(R.id.parallax_view_tag);
                Log.i("ricky", "tag_in:"+tag);
                if(tag==null) continue;
                //进来动画
                view.setTranslationX((containerWidth-positionOffsetPixels)*tag.xIn);
                view.setTranslationY((containerWidth-positionOffsetPixels)*tag.yIn);

            }
        }
        if(outFragment!=null){
            List<View> views = outFragment.getViews();
            for (int i = 0; i < views.size(); i++) {
                View view = views.get(i);
                ParallaxViewTag tag = (ParallaxViewTag) view.getTag(R.id.parallax_view_tag);
                Log.i("ricky", "tag_out:"+tag);
                if(tag==null) continue;
                //进来动画
                view.setTranslationX((-positionOffsetPixels)*tag.xOut);
                view.setTranslationY((-positionOffsetPixels)*tag.yOut);

            }
        }
    }

    @Override
    public void onPageSelected(int arg0) {
        // TODO Auto-generated method stub

    }


}

其中一个fragment的布局:

<?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/com.ricky.parallax"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <ImageView
        android:id="@+id/iv_0"
        android:layout_width="103dp"
        android:layout_height="19dp"
        android:layout_centerInParent="true"
        android:src="@drawable/intro1_item_0"
        app:x_in="1.2"
        app:x_out="1.2" />

    <ImageView
        android:id="@+id/iv_1"
        android:layout_width="181dp"
        android:layout_height="84dp"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="13dp"
        android:layout_marginTop="60dp"
        android:src="@drawable/intro1_item_1"
        app:x_in="0.8"
        app:x_out="0.8" />

    <ImageView
        android:id="@+id/iv_2"
        android:layout_width="143dp"
        android:layout_height="58dp"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_marginTop="109dp"
        android:src="@drawable/intro1_item_2"
        app:x_in="1.1"
        app:x_out="1.8" />

    <ImageView
        android:id="@+id/iv_3"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="40dp"
        android:layout_marginBottom="185dp"
        android:src="@drawable/intro1_item_3"
        app:x_in="0.8"
        app:x_out="0.8"
        app:a_in="0.8"
        app:a_out="0.8" />

    <ImageView
        android:id="@+id/iv_4"
        android:layout_width="fill_parent"
        android:layout_height="128dp"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="29dp"
        android:background="@drawable/intro1_item_4"
        app:a_in="0.8"
        app:a_out="0.8"
        app:x_in="0.8"
        app:x_out="0.8" />

    <ImageView
        android:id="@+id/iv_5"
        android:layout_width="260dp"
        android:layout_height="18dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_marginBottom="16dp"
        android:layout_marginLeft="15dp"
        android:src="@drawable/intro1_item_5"
        app:a_in="0.9"
        app:a_out="0.9"
        app:x_in="0.9"
        app:x_out="0.9" />

    <ImageView
        android:id="@+id/iv_6"
        android:layout_width="24dp"
        android:layout_height="116dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_marginBottom="35dp"
        android:layout_marginLeft="46dp"
        android:src="@drawable/intro1_item_6"
        app:x_in="0.6"
        app:x_out="0.6" />

    <ImageView
        android:id="@+id/iv_7"
        android:layout_width="45dp"
        android:layout_height="40dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_marginBottom="23dp"
        android:layout_marginLeft="76dp"
        android:src="@drawable/intro1_item_7"
        app:a_in="0.3"
        app:a_out="0.3"
        app:x_in="0.5"
        app:x_out="0.5" />

</RelativeLayout>

归根到底,无论是自定义view还是自定义动画框架,所做的事情都是在重新封装view,在view中处理各种事件和滑动。
其核心就是:
(1)如何获取布局中设置的属性值
(2)渲染view和动画处理
(3)事件监听处理
所以,必须掌握View的渲染流程和事件传递机制,这也是View的核心所在。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flutter提供了一个很好的动画框架,可以轻松地自定义任何动画效果。要自定义pop动画,可以使用`PageRouteBuilder`类来创建自定义路由。 下面是一个示例: ```dart class CustomPageRoute<T> extends PageRoute<T> { CustomPageRoute({ @required this.builder, this.transitionDuration = const Duration(milliseconds: 500), this.opaque = true, this.barrierDismissible = false, this.barrierColor, this.barrierLabel, this.maintainState = true, }); final WidgetBuilder builder; @override final Duration transitionDuration; @override final bool opaque; @override final bool barrierDismissible; @override final Color barrierColor; @override final String barrierLabel; @override final bool maintainState; @override Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { return builder(context); } @override Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { return SlideTransition( position: Tween<Offset>( begin: Offset(0.0, 1.0), end: Offset.zero, ).animate(animation), child: child, ); } } ``` 这里我们创建了一个`CustomPageRoute`类,它继承自`PageRoute`,并重了`buildTransitions`方法来定义我们的自定义动画效果。在这个例子中,我们使用`SlideTransition`来实现一个从底部向上滑动的动画效果。你可以根据需要替换为其他动画效果。 使用自定义动画时,可以使用`Navigator.push`来打开路由: ```dart Navigator.push(context, CustomPageRoute(builder: (context) => NextPage())); ``` 这将打开一个名为`NextPage`的路由,并使用我们定义的自定义动画效果进行过渡。同样,你可以使用`Navigator.pop`来关闭当前路由: ```dart Navigator.pop(context); ``` 希望这个例子能帮到你!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值