• 高级UI-自定义动画框架


    有的时候会需要做一些自定义的动画效果,在会反复用到的动画效果可以考虑做成动画框架,方便使用,做成框架的话就需要考虑很多的问题,最典型的问题就是属性和方法必须要是可配置的,这里就来聊一聊自定义动画框架的做法

    重难点分析

    在自定义动画框架里面,最难的一个问题就是怎么样获得属性,如果直接写自定义的属性,那么编译时候就会报错了,那么自然就想到了在外层包裹自定义的属性,通过处理自定义的属性来得到,这样便是android源代码一些控件的解决办法,其实就是一招偷梁换柱,在处理的时候,如果是自定义的属性,就自己处理,如果不是自定义的属性,就交由系统处理,然后在addView上再做手脚,另外从xml的解析入手,可以自定义LayoutInflater,然后重写onCreateView(),在里面就可以完成偷梁换柱,故这里有两种方法可以完成

    在addView时候拦截做法

    这里做一个滑动的自定义动画
    在滑动的View里面,需要实现滑动时候和重置的工作,这里有一个接口,及实现接口的方法来做

    public interface DiscrollvableInterface {
        //当滑动的时候调用该方法,用来控制里面的控件执行相应的动画
        void onDiscrollve(float ratio);
        //重置view的属性----恢复view的原来属性
        void onResetDiscrollve();
    }
    

    实现上面的接口

    public class DiscrollvableView extends FrameLayout implements DiscrollvableInterface {
    
        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;
            onResetDiscrollve();
        }
    
    
        public DiscrollvableView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public DiscrollvableView(Context context) {
            super(context);
        }
    
        @Override
        public void onDiscrollve(float ratio) {
            // ratio:0~1
            //控制自身的动画属性
            if (mDiscrollveAlpha) {
                setAlpha(ratio);
            }
            if (mDiscrollveScaleX) {
                setScaleX(ratio);
            }
            if (mDiscrollveScaleY) {
                setScaleY(ratio);
            }
            //判断到底是哪一种值:fromTop,fromBottom,fromLeft,fromRight
            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);
            }
            //判断到底是哪一种值:fromTop,fromBottom,fromLeft,fromRight
            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(代表原来的位置)
            }
        }
    }
    

    那么便完成了对于滑动效果的控制,为了便于控制,自定义View继承ScrollView

    public class DiscrollView extends ScrollView {
    
        private DiscrollViewContent mContent;
    
        public DiscrollView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onFinishInflate() {
            super.onFinishInflate();
            View content = getChildAt(0);
            mContent = (DiscrollViewContent) content;
        }
    
        @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();
            //监听滑动----接口---->控制DiscrollViewContent的属性
            //遍历MyLinearLayout里面所有子控件(MyViewGroup)
            for (int i = 0; i < mContent.getChildCount(); i++) {
                View child = mContent.getChildAt(i);
                if (!(child instanceof DiscrollvableInterface)) {
                    continue;
                }
    
                //ratio:0~1
                DiscrollvableInterface discrollvableInterface = (DiscrollvableInterface) child;
                //1.child离scrollview顶部的高度
                int discrollvableTop = child.getTop();
                int discrollvableHeight = child.getHeight();
    
                //2.得到scrollview滑出去的高度
                //3.得到child离屏幕顶部的高度
                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);
        }
    }
    

    设置属性,偷梁换柱

    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) {
            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();
            }
        }
    }
    

    最后是一些自定义属性

    <?xml version="1.0" encoding="UTF-8"?>
    <resources>
        <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"/>
        </declare-styleable>
    
        <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>
    </resources>
    

    布局

    <com.cj5785.customanimationaddview.DiscrollView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:custom="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <com.cj5785.customanimationaddview.DiscrollViewContent
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <TextView
                android:layout_width="match_parent"
                android:layout_height="560dp"
                android:background="@android:color/white"
                android:gravity="center"
                android:text="测试文字使用情况"
                android:textSize="25sp" />
    
            <View
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:background="@android:color/darker_gray"
                custom:discrollve_alpha="true" />
    
            <ImageView
                android:layout_width="89dp"
                android:layout_height="80dp"
                android:src="@drawable/duck"
                custom:discrollve_alpha="true"
                custom:discrollve_translation="fromLeft|fromBottom" />
    
            <View
                android:layout_width="match_parent"
                android:layout_height="200dp"
                custom:discrollve_fromBgColor="#ffff00"
                custom:discrollve_toBgColor="#88EE66" />
    
            <ImageView
                android:layout_width="150dp"
                android:layout_height="106dp"
                android:layout_gravity="right"
                android:src="@drawable/camera"
                custom:discrollve_translation="fromRight" />
    
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:text="第二段文字使用测试"
                android:textSize="28sp"
                custom:discrollve_alpha="true"
                custom:discrollve_translation="fromBottom" />
    
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_margin="20dp"
                android:src="@drawable/sun"
                custom:discrollve_scaleX="true"
                custom:discrollve_scaleY="true" />
    
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_margin="20dp"
                android:src="@drawable/balloon"
                custom:discrollve_translation="fromLeft|fromBottom" />
    
        </com.cj5785.customanimationaddview.DiscrollViewContent>
    
    </com.cj5785.customanimationaddview.DiscrollView>
    

    效果如下
    自定义动画框架-addView

    自定义LayoutInflater

    这里实现一个仿小红书的视差动画效果
    首先定义一个根布局,然后实例化其自定义控件

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white">
    
        <com.cj5785.customanimationlayoutinflater.ParallaxContainer
            android:id="@+id/parallax_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    
        <ImageView
            android:id="@+id/iv_man"
            android:layout_width="66dp"
            android:layout_height="202dp"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:layout_marginBottom="10dp"
            android:background="@drawable/intro_item_manrun_1"/>
    
    </RelativeLayout>
    

    实例化自定义控件,在最后一个登陆界面,由于与前面的不同,使用切换Fragment的方式,需要使用到适配器
    适配器

    public class ParallaxAdapter extends FragmentPagerAdapter {
        private List<ParallaxFragment> fragmentList;
    
        public ParallaxAdapter(FragmentManager fm, List<ParallaxFragment> fragmentList) {
            super(fm);
            this.fragmentList = fragmentList;
        }
    
        @Override
        public Fragment getItem(int position) {
            return fragmentList.get(position);
        }
    
        @Override
        public int getCount() {
            return fragmentList.size();
        }
    }
    

    自定义Fragment,其主要的偷梁换柱工作就是在这里完成的,返回我们处理以后的View

    public class ParallaxFragment extends Fragment {
        private List<View> parallaxViews = new ArrayList<>();
    
        @Nullable
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            Bundle bundle = getArguments();
            int layoutId = bundle.getInt("layoutId");
            //正常情况下在这里获得view,然后返回,我们就在这里偷梁换柱
    //        View view = inflater.inflate(layoutId, container);
            //使用自定义的渲染器渲染
            ParallaxLayoutInflater layoutInflater = new ParallaxLayoutInflater(inflater, getActivity(), this);
    
            return layoutInflater.inflate(layoutId, null);
        }
    
        public List<View> getParallaxViews() {
            return parallaxViews;
        }
    }
    

    动画引导层

    //引导页的最外层布局
    public class ParallaxContainer extends FrameLayout implements OnPageChangeListener {
        private List<ParallaxFragment> fragmentList;
        private ParallaxAdapter adapter;
        private float containerWidth;
        private ImageView iv_man;
    
        public ParallaxContainer(@NonNull Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    
        //指定引导页的所有页面布局文件
        public void setUp(int... ids) {
            //初始化framentList,在调用处直接调用setUp()
            fragmentList = new ArrayList<>();
            for (int i = 0; i < ids.length; i++) {
                ParallaxFragment fragment = new ParallaxFragment();
                Bundle bundle = new Bundle();
                //Fragment中需要加载的布局文件id
                bundle.putInt("layoutId", ids[i]);
                fragment.setArguments(bundle);
                fragmentList.add(fragment);
            }
            //设置ViewPager
            ViewPager viewPager = new ViewPager(getContext());
            viewPager.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
            viewPager.setId(R.id.parallax_pager);
            adapter = new ParallaxAdapter(((MainActivity) getContext()).getSupportFragmentManager(), fragmentList);
            viewPager.setAdapter(adapter);
            addView(viewPager, 0);
            //设置监听
            viewPager.addOnPageChangeListener(this);
        }
    
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            containerWidth = getWidth();
            //在翻页的过程中,不断根据视图的标签中对应的动画参数,改变视图的位置或者透明度
            //获取到进入的页面
            ParallaxFragment inFragment = null;
            try {
                inFragment = fragmentList.get(position - 1);
            } catch (Exception e) {
            }
            //获取到退出的页面
            ParallaxFragment outFragment = null;
            try {
                outFragment = fragmentList.get(position);
            } catch (Exception e) {
            }
    
            if (inFragment != null) {
                //获取Fragment上所有的视图,实现动画效果
                List<View> inViews = inFragment.getParallaxViews();
                if (inViews != null) {
                    for (View view : inViews) {
                        //获取标签,从标签上获取所有的动画参数
                        ParallaxViewTag tag = (ParallaxViewTag) view.getTag(R.id.parallax_view_tag);
                        if (tag == null) {
                            continue;
                        }
                        //translationY改变view的偏移位置,translationY=100,代表view在其原始位置向下移动100
                        //仔细观察进入的fragment中view从远处过来,不断向下移动,最终停在原始位置
                        view.setTranslationX((containerWidth - positionOffsetPixels) * tag.xIn);
                        view.setTranslationY((containerWidth - positionOffsetPixels) * tag.yIn);
                    }
                }
            }
    
            if (outFragment != null) {
                List<View> outViews = outFragment.getParallaxViews();
                if (outViews != null) {
                    for (View view : outViews) {
                        ParallaxViewTag tag = (ParallaxViewTag) view.getTag(R.id.parallax_view_tag);
                        if (tag == null) {
                            continue;
                        }
                        //仔细观察退出的fragment中view从原始位置开始向上移动,translationY应为负数
                        view.setTranslationX((-positionOffsetPixels) * tag.xOut);
                        view.setTranslationY((-positionOffsetPixels) * tag.yOut);
                    }
                }
            }
        }
    
        @Override
        public void onPageSelected(int position) {
            if (position == adapter.getCount() - 1) {
                iv_man.setVisibility(INVISIBLE);
            } else {
                iv_man.setVisibility(VISIBLE);
            }
        }
    
        @Override
        public void onPageScrollStateChanged(int state) {
            AnimationDrawable animation = (AnimationDrawable) iv_man.getBackground();
            switch (state) {
                case ViewPager.SCROLL_STATE_DRAGGING:
                    animation.start();
                    break;
                case ViewPager.SCROLL_STATE_IDLE:
                    animation.stop();
                    break;
                default:
                    break;
            }
        }
    
        public void setIv_man(ImageView iv_man) {
            this.iv_man = iv_man;
        }
    }
    

    拦截系统处理xml

    public class ParallaxLayoutInflater extends LayoutInflater {
    
        private static final String TAG = "ParallaxLayoutInflater";
        private ParallaxFragment parallaxFragment;
    
        protected ParallaxLayoutInflater(LayoutInflater original, Context newContext, ParallaxFragment parallaxFragment) {
            super(original, newContext);
            this.parallaxFragment = parallaxFragment;
            //源代码中存在:
            //View view;
            //if (mFactory2 != null) {
            //  view = mFactory2.onCreateView(parent, name, context, attrs);
            //} else if (mFactory != null) {
            //  view = mFactory.onCreateView(name, context, attrs);
            //} else {
            //  view = null;
            //}
            //if (view == null && mPrivateFactory != null) {
            //  view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            //}
            //也就是说只要实现Factory,那么就不会调用后面的方法
            setFactory(new ParallaxFactory(this));
        }
    
        @Override
        public LayoutInflater cloneInContext(Context newContext) {
            //实际用于创建LayoutInflater的方法
            return new ParallaxLayoutInflater(this, newContext, parallaxFragment);
        }
    
        //这个框架最核心的地方就是在这里设置Factory,从而进行拦截
    	class ParallaxFactory implements LayoutInflater.Factory{
    
            private LayoutInflater inflater;
            //系统提供的视图分为android.widget.xxx和android.view.xxx
            private final String[] prefixs = {
                    "android.widget.",
                    "android.view."
            };
    
            public ParallaxFactory(LayoutInflater inflater) {
                this.inflater = inflater;
            }
    
            @Override
            public View onCreateView(String name, Context context, AttributeSet attrs) {
                //实例化view
                View view = null;
                if (view == null) {
    				view = createViewOrFailQuietly(name,context,attrs);
                }
                if (view != null) {
                    //获取自定义属性,并将自定义标签值绑定到view上面
                    getCustomAttrs(context, attrs, view);
                    parallaxFragment.getParallaxViews().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 typedArray = context.obtainStyledAttributes(attrs, attrIds);
                if (typedArray != null && typedArray.length() > 0) {
    				//获取自定义属性的值
                    Log.d(TAG, "getCustomAttrs");
                    ParallaxViewTag tag = new ParallaxViewTag();
                    tag.alphaIn = typedArray.getFloat(0, 0f);
                    tag.alphaOut = typedArray.getFloat(1, 0f);
                    tag.xIn = typedArray.getFloat(2, 0f);
                    tag.xOut = typedArray.getFloat(3, 0f);
                    tag.yIn = typedArray.getFloat(4, 0f);
                    tag.yOut = typedArray.getFloat(5, 0f);
                    view.setTag(R.id.parallax_view_tag, tag);
                }
                typedArray.recycle();
            }
    
            private View createViewOrFailQuietly(String name, String prefix, Context context, AttributeSet attrs) {
                try {
    				//通过系统的inflater创建视图,读取系统的属性
                    return inflater.createView(name, prefix, attrs);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                    return null;
                }
            }
    
            private View createViewOrFailQuietly(String name, Context context, AttributeSet attrs) {
                //通过系统inflater创建视图
    			//1.自定义控件标签名称带点,所以创建时不需要前缀
                if (name.contains(".")) {
                    return createViewOrFailQuietly(name, null, context, attrs);
                }
    			//2.系统视图需要加上前缀
    			                for (String prefix : prefixs) {
                        View view = createViewOrFailQuietly(name, prefix, context, attrs);
                        if (view != null) {
                            return view;
                        }
                    }
                return null;
            }
        }
    }
    

    参数控制

    //视差动画播放时参数的控制
    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"?>
    <animation-list xmlns:android="http://schemas.android.com/apk/res/android"
        android:oneshot="false">
        <item
            android:drawable="@drawable/intro_item_manrun_1"
            android:duration="200" />
        <item
            android:drawable="@drawable/intro_item_manrun_2"
            android:duration="200" />
    </animation-list>
    

    自定义属性

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <attr name="a_in" format="float" />
        <attr name="a_out" format="float" />
        <attr name="x_in" format="float" />
        <attr name="x_out" format="float" />
        <attr name="y_in" format="float" />
        <attr name="y_out" format="float" />
    </resources>
    

    ids

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

    实现效果如下图所示
    自定义动画框架-inflater

  • 相关阅读:
    LFU
    poj 3581 -- 后缀数组
    leetcode 679
    poj 两条线段接雨水
    poj 1696极角排序
    判断平面上是否有一条直线与所有线段相交
    洛谷P3808 【模板】AC自动机(简单版)
    Most Distant Point from the Sea UVA
    P2742 [USACO5.1]圈奶牛Fencing the Cows /【模板】二维凸包
    P2249
  • 原文地址:https://www.cnblogs.com/cj5785/p/10664587.html
Copyright © 2020-2023  润新知