Android 游戏开卡 动画效果_Android 游戏开卡 动画效果


本文目录


一、属性动画和MaterialDesign动画

属性动画和MaterialDesign动画相关的内容,已经在前面写过,具体查看https://www.jianshu.com/p/15d25638c001(属性动画)https://www.jianshu.com/p/a8f1f36c3fe0(MaterialDesign动画)

二、Android自定义动画框架

前面的部分,是动画的理论部分,这一部分,是动画的应用部分。
无论是封装一个自己的动画框架,还是封装一个自定义View,都需要遵从封装的原则:便于使用和复用,便于配置。

今天来做这样一个效果:


Android 游戏开卡 动画效果_自定义_02


闪屏动画



略卡,是因为模拟器不好滑动的原因,在真机上的效果是非常流畅的,有点丑,个人能提供的设计有限,但跟那些绚丽的动画原理是一样的。

要封装成一个可以复用,便于配置的动画框架,如果只在xml文件中配置每个控件在X轴和Y轴上移动的加速度,就非常的方便。那么问题来了,我们在系统的控件上添加自定义属性,系统控件是无法识别的,但是如果在系统控件外包裹一层自定义的容器,让这个自定义的容器去识别这些自定义属性,这个问题就解决了。

那么我们现在就开始一步步的实现这样的效果。

1.分析页面,发现页面其实就是一个ViewPager,这里定义了一个ParallaxContainer继承自FrameLayout,在其中使用addView添加了一个ViewPager。其实也可以直接在Activity中写一个ViewPager,效果是一样的。

ParallaxContainer的代码如下

/**
 * Created by kimliu on 2018/12/25
 * 引导页最外层布局,里面有一个ViewPager
 */
public class ParallaxContainer extends FrameLayout implements ViewPager.OnPageChangeListener{
    
    private ArrayList<ParallaxFragment> fragments;
    private ParallaxPagerAdapter adapter;
    private float containerWidth;
    private ImageView iv_main;

    public ViewPagerScrollListener mListener;
    private ViewPagerScrollListener listener;

    private void setmListener(ViewPagerScrollListener mListener){
        this.mListener = mListener;
    }

    public ParallaxContainer(@NonNull Context context,
                             @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 初始化设置
     * @param childIds
     */
    public void setUp(int...childIds){
        fragments = new ArrayList<>();
        for (int i = 0 ; i < childIds.length; i++){
            ParallaxFragment fragment = new ParallaxFragment();
            Bundle bundle = new Bundle();
            bundle.putInt("index",i);
            bundle.putInt("layoutId",childIds[I]);
            fragment.setArguments(bundle);
            fragments.add(fragment);
        }

        SplashActivity splashActivity = (SplashActivity) getContext();
        adapter = new ParallaxPagerAdapter(splashActivity.getSupportFragmentManager(),
                fragments);

        ViewPager viewPager = new ViewPager(getContext());
        viewPager.setId(R.id.parallax_pager);
        viewPager.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
        viewPager.setAdapter(adapter);

        addView(viewPager);
        viewPager.addOnPageChangeListener(this);
    }


    /**
     *
     * @param position 位置
     * @param positionOffset 移动偏移量
     * @param positionOffsetPixels 移动偏移量像素值
     */
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        containerWidth = getWidth();

        //进入的页面
        ParallaxFragment inFragment = null;
        try {
            inFragment = fragments.get(position - 1);
        }catch (Exception e){
            //如果报错,那么inFragment为null,下面的ifelse就不会走

        }

        ParallaxFragment outFragment = null;
        try {
           outFragment = fragments.get(position);
        }catch (Exception e){
            //如果报错,那么outFragment为空,下面的ifelse就不会走
        }

        try {
            if(position != adapter.getCount() - 1) {
               //这里需要控制一下,因为最后一页是不需要设置动画,没有使用ViewContent包裹。
                if (inFragment != null) {
                    //获取到的是什么呢?获得的是Fragment中的最外层View
                    View view = inFragment.getView();
                    ViewContent viewContent = (ViewContent) view;
                    for (int i = 0; i < viewContent.getChildCount(); i++) {
                         //拿到其中的View们,对其进行动画的控制
                        View child = viewContent.getChildAt(i);
                        if (!(child instanceof ViewPagerScrollListener)) {
                            continue;
                        }
                        listener = (ViewPagerScrollListener) child;
                        listener.onViewScrollIn(positionOffsetPixels, containerWidth);
                    }
                }

                if (outFragment != null) {
                    //拿到其中的View们 进行动画的控制
                    View view = outFragment.getView();
                    ViewContent viewContent = (ViewContent) view;
                    for (int i = 0; i < viewContent.getChildCount(); i++) {
                        View child = viewContent.getChildAt(i);
                        if (!(child instanceof ViewPagerScrollListener)) {
                            continue;
                        }
                        //AnimationView继承了ViewPagerScrollListener
                        listener = (ViewPagerScrollListener) child;
                        listener.onViewScrollOut(positionOffsetPixels);
                    }
                }
            }
        }catch (Exception e){}

    }

    @Override
    public void onPageSelected(int position) {
        if(position == adapter.getCount() - 1){
            iv_main.setVisibility(INVISIBLE);
        }else{
            iv_main.setVisibility(VISIBLE);
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        //在滚动的状态下 动画开始 滚动停止 动画停止
        AnimationDrawable animationDrawable = (AnimationDrawable) iv_main.getBackground();
        switch (state){
            case ViewPager.SCROLL_STATE_DRAGGING:
                animationDrawable.start();
                break;
            case ViewPager.SCROLL_STATE_IDLE:
                animationDrawable.stop();
                break;
        }
    }

   /**
     * 拿到外层的ImageView,在这里需要根据ViewPager的滑动进行ImageView的动画控制,
     * 也可以使用接口回调的方式进行。但这里只是进行一个动画的开始和停止,不需要那么复杂
     * @param iv_main 外层ImageView:走路的小姑娘
     */
    public void setIv_main(ImageView iv_main) {
        this.iv_main = iv_main;
    }
}

亮点:

在获取ViewPager中滑进去的页面和滑出来的页面时,使用了trycatch,如果出错,那么获取到的页面为空,就不会进行下面的操作。

2. ViewPager中的六个页面,前五个是要给其中的View设置动画的,需要设置动画的页面,我们需要使用一个自定义的容器ViewContent包裹,这个ViewContent的作用就是,遍历其中的View,如果用户传了自定义属性,那么就在这个View的外面包裹一层自定义容器,让这个自定义容器去识别用户传的自定义属性。

ViewContent的代码如下:

/**
 * Created by kimliu on 2018/12/27
 * 给View的外层包裹一个自定义的RelativeLayout
 * 遍历其中的View,如果添加了自定义属性,就在这个View外层包裹一个自定义容器
 */
public class ViewContent extends RelativeLayout{

    private static final String TAG = ViewContent.class.getSimpleName();

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


    /**
     * 查看源码发现,我们自定义View的时候,创建LayoutParams是调用的这个方法,那么我们重写这个方法,就可以偷梁换柱把LayoutParams换成我们自己LayoutParams
     * @param attrs
     * @return
     */
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MyLayoutParams(getContext(),attrs);
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        MyLayoutParams p  = (MyLayoutParams) params;
        if(!isContent(p)){
            //如果没有传自定义属性,直接把系统的child添加到Relativelayout中
            super.addView(child,index,params);
        }else{
            //如果没有传自定义属性,在外层包裹一个View
            AnimationView view = new AnimationView(getContext());
            view.setAlphaIn(p.alphaIn);
            view.setAlphaOut(p.alphaOut);
            view.setxIn(p.xIn);
            view.setxOut(p.xOut);
            view.setyIn(p.yIn);
            view.setyOut(p.yOut);

//            Log.d(TAG, "addView: " + p.alphaIn +","+ p.alphaOut +","
//            +","+p.xIn +","+p.xOut +","+p.yIn +","+p.yOut);

           //把系统控件添加到AnimationView中
            view.addView(child);
            // 把包裹后的view添加到Relativelayout中
            super.addView(view, index, params);
        }
    }

    /**
     * 是否有自定义属性
     * @param params
     * @return
     */
    private boolean isContent(MyLayoutParams params){
        return params.alphaIn != 0 ||
                params.alphaOut != 0||
                params.xIn != 0||
                params.xOut != 0||
                params.yIn != 0||
                params.yOut != 0;
    }

    /**
     * 自定义LayoutParams,在其中获取自定义属性
     */
    public static class MyLayoutParams extends RelativeLayout.LayoutParams{

        public int index;
        public float xIn;
        public float xOut;
        public float yIn;
        public float yOut;
        public float alphaIn;
        public float alphaOut;

        public MyLayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ParallaxAnimation);
            alphaIn = a.getFloat(R.styleable.ParallaxAnimation_a_in,0f);//进入动画的透明度
            alphaOut = a.getFloat(R.styleable.ParallaxAnimation_a_out,0f);//出去动画的透明度
            xIn = a.getFloat(R.styleable.ParallaxAnimation_x_in,0f);//X轴上进入动画的加速度
            xOut = a.getFloat(R.styleable.ParallaxAnimation_x_out,0f);//X轴上出去动画的加速度
            yIn = a.getFloat(R.styleable.ParallaxAnimation_y_in,0f);//Y轴上进入动画的加速度
            yOut = a.getFloat(R.styleable.ParallaxAnimation_y_out,0f);//Y轴上出去动画的加速度
            a.recycle();
        }
    }
}

亮点:

  1. 查看源码可以发现,系统的View创建LayoutParams时,调用的是generateLayoutParams这个方法,如果我们重写这个方法,改变它的返回值,就可以偷梁换柱,把系统的LayoutParams换成我们自己写的LayoutParams。
    2.在我们自己的LayoutParams类MyLayoutParams中,获取用户输入的自定义属性,在addView中判断,如果用户添加了自定义属性,我们就在该View的外层包裹一个自定义容器,再添加到ViewContent中,如果用户没有添加自定义属性,我们就直接把系统控件添加到ViewContent中,又是一个偷梁换柱。
3.AnimationView,这个自定义容器是包裹在系统View外面的ViewGroup,用来根据用户输入的自定义属性来控制View的动画。在这里,我们使用了接口回调,用来传递ViewPager滑动的像素值。
/**
 * Created by kimliu on 2018/12/27
 * 在定义了自定义属性的View外层包裹一个ViewGroup 这个ViewGroup用来识别自定义属性
 * 并且根据这些自定义属性去做相应的操作
 *
 */
public class AnimationView extends FrameLayout implements ViewPagerScrollListener{

    public float xIn;
    public float xOut;
    public float yIn;
    public float yOut;
    public float alphaIn;
    public float alphaOut;

    public AnimationView(@NonNull Context context) {
        super(context);
    }

    public AnimationView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }


    public void setAlphaIn(float alphaIn) {
        this.alphaIn = alphaIn;
    }

    public void setAlphaOut(float alphaOut) {
        this.alphaOut = alphaOut;
    }

    public void setxIn(float xIn) {
        this.xIn = xIn;
    }

    public void setxOut(float xOut) {
        this.xOut = xOut;
    }

    public void setyIn(float yIn) {
        this.yIn = yIn;
    }

    public void setyOut(float yOut) {
        this.yOut = yOut;
    }



    @Override
    public void onViewScrollIn(float positionOffsetPixels, float containerWidth) {
        //实现属性动画
        ViewHelper.setTranslationX(this,(containerWidth - positionOffsetPixels) * xIn);
        ViewHelper.setTranslationY(this,(containerWidth - positionOffsetPixels) * yIn);
    }

    @Override
    public void onViewScrollOut(float positionOffsetPixels) {
        //实现属性动画
        ViewHelper.setTranslationX(this,0 - positionOffsetPixels * xOut);
        ViewHelper.setTranslationY(this,0 - positionOffsetPixels * yOut);
    }

    @Override
    public void resetView() {
        ViewHelper.setTranslationX(this,0);
        ViewHelper.setTranslationY(this,0);
    }
}
4. ViewPagerScrollListener,在其中实现了三个方法,分别是在页面滑入时调用的方法onViewScrollIn;页面滑出时调用的方法onViewScrollOut;页面看不见时讲所有View重置的方法resetView。
/**
 * Created by kimliu on 2018/12/27
 */
public interface ViewPagerScrollListener {

    /**
     * 页面进入时调用
     * @param positionOffsetPixels ViewPager滑动的偏移量
     * @param containerWidth 最外层container的宽度
     */
     void onViewScrollIn(float positionOffsetPixels,float containerWidth);

    /**
     * 页面滑出时调用
     * @param positionOffsetPixels ViewPager滑动的偏移量
     */
    void onViewScrollOut(float positionOffsetPixels);

    /**
     * 重置View 当View滑出屏幕时,将View的属性重置
     */
    void resetView();
}
5.Fragment的编写,这里要注意的是,在这里进行页面上所有View的重置。
public class ParallaxFragment extends Fragment {
    
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,
                             @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        Bundle bundle = getArguments();
        int layoutId = bundle.getInt("layoutId");
        int index = bundle.getInt("index");
        
        return LayoutInflater.from(getActivity()).inflate(layoutId,null);
    }
    
    /**
     * 页面看不见时,获取页面中所有的View,进行View的重置
     * @param isVisibleToUser
     */
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        View view = getView();
        if(view instanceof ViewContent){
            ViewContent viewContent = (ViewContent) view;
            for (int i = 0; i < viewContent.getChildCount() ; i++){
                View child = viewContent.getChildAt(i);
                if(child instanceof AnimationView){
                    ViewPagerScrollListener listener = (ViewPagerScrollListener) child;
                    listener.resetView();
                }
            }
        }
    }
}
6.Activity的编写:设置透明状态栏,这里使用了第三方工具类:
implementation 'com.blankj:utilcode:1.21.2'
public class SplashActivity extends AppCompatActivity {

    private ParallaxContainer parallax_container;
    private ImageView iv_splash;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);

        initStatusBar();
        
        parallax_container = findViewById(R.id.parallax_container);
        parallax_container.setUp(new int[]{
                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_login
        });


        iv_splash = findViewById(R.id.iv_splash);
        iv_splash.setBackgroundResource(R.drawable.splash_run);
        parallax_container.setIv_main(iv_splash);



    }

    /**
     * 沉浸式状态栏
     */
    private void initStatusBar() {
        RelativeLayout splash_relativelayout = findViewById(R.id.splash_relativelayout);

        splash_relativelayout.setPadding(splash_relativelayout.getPaddingLeft(),
                splash_relativelayout.getPaddingTop()+ BarUtils.getStatusBarHeight(),
                splash_relativelayout.getPaddingRight(),
                splash_relativelayout.getPaddingBottom());

        //设置为透明
        BarUtils.setStatusBarAlpha(this,0);
        BarUtils.setStatusBarLightMode(this,true);

    }
}
7. 最后要说一句,在ViewPager的前五个页面最外层,都需要包裹一个ViewContent,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<com.kimliu.kimliucustomview.ui.view.customAnimation.ViewContent
    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="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" />

</com.kimliu.kimliucustomview.ui.view.customAnimation.ViewContent>