Android平行动画(首次进入app显示)

在我们打开一个新的app时,几乎每个app都会有一个初始化动画显示,这个动画只显示一次,第二次打开就不会显示了,通常来说这个动画都在尽可能地向用户介绍这个app,也可以最大化的展示app的特点,亮点。比如这样:

在这里插入图片描述

它被称为平行动画,它的实现很简单,只是一个简单的ViewPager和一个ImageView控件而已。

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.splash.ParallaxContainer
        android:id="@+id/parallax_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <ImageView
        android:id="@+id/iv_man"
        android:layout_alignParentBottom="true"
        android:layout_width="67dp"
        android:layout_marginBottom="10dp"
        android:layout_centerHorizontal="true"
        android:layout_height="202dp"/>
</RelativeLayout>

它的布局文件内容仅此而已。

parallax_container.setUp(intArrayOf(
            R.layout.view_intro_1,
            R.layout.view_intro_2,
            R.layout.view_intro_3,
            R.layout.view_intro_4,
            R.layout.view_intro_5,
            R.layout.view_intro_6,
            R.layout.view_intro_7,
            R.layout.view_login
        ))

代码中将布局文件的ID全部传给自定义控件,自定义控件内又做了什么呢?

public void setUp(int[] ids){
        for (int id : ids) {
            ParallaxFragment fragment = new ParallaxFragment();
            Bundle bundle = new Bundle();
            bundle.putInt(LAYOUT_ID,id);
            fragment.setArguments(bundle);
            fragments.add(fragment);
        }

        adapter = new ParallaxAdapter(((AppCompatActivity) getContext()).getSupportFragmentManager(),
                FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,fragments);
        ViewPager pager = new ViewPager(getContext());
        pager.setId(R.id.parallax_viewpager);
        pager.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
        pager.addOnPageChangeListener(this);   //添加事件回调
        pager.setAdapter(adapter);
        addView(pager); //其实这个自定义ViewGroup只有这么一个ViewPager
    }

首先要为每个layout布局新建一个Fragment,以内每个Fragment都要显示一个布局文件,然后使用ViewPager来装载这些Fragment,最后可以发现,其实这个自定义ViewGroup(View容器)只有一个View,那就是ViewPager。ViewPager是一个谷歌官方提供的可用来对页面进行左右滑动的高级控件,与常见的ListView和RecyclerView类似,也需要一个适配器Adapter(适配器代码比较简单)

Fragment中也比较简单:

	@Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        Bundle bundle = getArguments();
        if(bundle != null) {
            int layoutId = bundle.getInt(ParallaxContainer.LAYOUT_ID);
            if(layoutId != 0){
                return inflater.inflate(layoutId,null);
            }
        }
        return null;
    }

到了这一步就完成了。但是。。还没有设置页面中ImageView的动画效果,如果对每一个ImageView都单独设置动画效果,显得比较繁琐(如果有几百个。。。。后果不堪设想),可以通过直接为ImageView添加属性:

   <ImageView
        android:id="@+id/iv_7"
        android:layout_width="260dp"
        android:layout_height="67dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentEnd="true"
        android:layout_marginBottom="44dp"
        android:src="@drawable/intro3_item_7"
        app:a_in="1"
        app:a_out="1"
        app:x_in="1"
        app:x_out="1" />

这样直接设置肯定是不行的,还需要在values文件夹中新建一个attrs文件,这个文件可以用来自定义属性,文件名很特殊,系统可以根据这个特定的文件找到自定义属性:

<resources>
    <attr name="x_in" format="float"/>
    <attr name="x_out" format="float"/>
    <attr name="y_in" format="float"/>
    <attr name="y_out" format="float"/>
    <attr name="a_in" format="float"/>
    <attr name="a_out" format="float"/>
</resources>

在Fragment的onCreateView方法中,正常来说都会直接
return inflater.inflate(layoutId,null);
就完事了,但是并没有对View的自定义属性进行处理,因此只在这里设置是没用的。重写LaoutInflater方法:

public ParallaxLayoutInflater(LayoutInflater original, Context newContext,ParallaxFragment fragment) {
        super(original, newContext);
        this.fragment = fragment;
        setFactory2(new MyFactory(this));
    }

首先LayoutInflater在将xml文件转换为View的过程中会回调Factory2接口,因此可以重新实现这个接口:

private class MyFactory implements Factory2 {

        private LayoutInflater inflater;

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

        private MyFactory(LayoutInflater inflater){
            this.inflater = inflater;
        }

        private final int[] attrsID = new int[]{
                R.attr.x_in,
                R.attr.x_out,
                R.attr.y_in,
                R.attr.y_out,
                R.attr.a_in,
                R.attr.a_out
        };

        @Nullable
        @Override
        public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
            //name是控件名,系统控件如:ImageView、TextView
            //自定义控件如:xx.xx.xx.MyView
            View view = createMyView(name,attrs);
            if(view != null){
                TypedArray array = context.obtainStyledAttributes(attrs,attrsID);
                if(array.length() != 0) {
                    ParallaxTag tag = new ParallaxTag();
                    tag.xIn = array.getFloat(0, 0f);
                    tag.xOut = array.getFloat(1, 0f);
                    tag.yIn = array.getFloat(2, 0f);
                    tag.yOut = array.getFloat(3, 0f);
                    tag.aIn = array.getFloat(4, 0f);
                    tag.aOut = array.getFloat(5, 0f);
                    view.setTag(tag);
                }
                fragment.addView(view);
                array.recycle();
            }
            return view;
        }

        @Nullable
        @Override
        public View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
            return null;
        }

        public View createMyView(String name,AttributeSet attributeSet){
            try {
                if (name.contains(".")) {   //自定义控件
                    return inflater.createView(name, null, attributeSet);
                } else {
                    for (String prefix : PREFIX) {
                        View view = inflater.createView(name,prefix,attributeSet);
                        if(view != null){
                            return view;
                        }
                    }
                }
            }catch (ClassNotFoundException e){}
            return null;
        }
    }

可以看出实现接口必须实现onCreateView方法,这个方法名已经十分明显了,对应xml文件中的每一个View都会回调一次这个方法,有多少个回调多少次,对应系统控件,回调参数name名称通常为:ImageView、TextView,而对于自定义控件,参数name名称则为xxx.yyy.MyView这样带有包名的名称。因此首先做判断,做不同的处理,使用inflater的createView来生成View。

还有一个很重要的参数就是attrs,这个参数包含了当前控件的所有属性信息,当然它也包括了自定义属性,如上面自定义的x_in,x_out等。获取这些属性,将他们保存在View的Tag中。

回到自定义控件中来,刚刚说到,它只有一个View,它就是ViewPager,ViewPager内部定义了许多回调方法:

@Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        ParallaxFragment outFragment = null;
        try {
            outFragment = fragments.get(position -1);
        }catch (Exception e){}

        if(outFragment != null){
            List<View> outViews = outFragment.getViews();
            if(outViews != null) {
                for (View outView : outViews) {
                    ParallaxTag tag = (ParallaxTag) outView.getTag();
                    if (tag == null) {
                        continue;
                    }
                    outView.setTranslationX((getWidth() - positionOffsetPixels) * tag.xOut);
                    outView.setTranslationY((getWidth() - positionOffsetPixels) * tag.yOut);
                }
            }
        }

        ParallaxFragment inFragment = null;
        try {
            inFragment = fragments.get(position);
        }catch (Exception e){}

        if(inFragment != null){
            List<View> inViews = inFragment.getViews();
            if(inViews != null) {
                for (View inView : inViews) {
                    ParallaxTag tag = (ParallaxTag) inView.getTag();
                    if (tag == null) {
                        continue;
                    }
                    inView.setTranslationX(( - positionOffsetPixels) * tag.xIn);
                    inView.setTranslationY( (- positionOffsetPixels) * tag.yIn);
                }
            }
        }
    }

    @Override
    public void onPageSelected(int position) {
        if(position == fragments.size() - 1){
            iv_man.setVisibility(GONE);
        }else {
            iv_man.setVisibility(VISIBLE);
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        AnimationDrawable drawable = (AnimationDrawable) iv_man.getBackground();
        switch (state){
            case ViewPager.SCROLL_STATE_DRAGGING:   //当ViewPager在滚动时
                drawable.start();
                break;
            case ViewPager.SCROLL_STATE_IDLE:   //当ViewPager停止滚动时
                drawable.stop();
                break;
        }
    }

onPageScrolled表示在滑动过程中会调用这个方法,这个方法会被调用若干次(滑动过程中,调用很多很多次),在方法中,首先可以得到滑动时即将离开屏幕的Fragment(简称outFragment)和即将进入的Fragment(简称inFragment)

在这里插入图片描述如图,以向左滑动为例,在滑动过程中,分别遍历outFragment和inFragment中所有的View,然后获取他们对应的Tag,Tag中保存着他们对应的动画执行名称,这个可以自己设置,接着执行相应的设置方法就行了。
比如setTranslationX。因为onPageScrolled被回调的次数很多,很快,所以看起来就是一个动画了,其实只不过是调用了很多很多次setTranslationX,况且每次的传入参数都不一样而已。

onPageSelected表示当前Page。

onPageScrollStateChanged表示滑动的状态改变,如:

SCROLL_STATE_DRAGGING表示正在滑动,
SCROLL_STATE_IDLE表示停止滑动.

demo地址:https://github.com/lyx19970504/Splash

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哒哒呵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值