Android Activity切换动画多种实现方式与封装

来源:互联网 发布:虚拟机网络nat模式 编辑:程序博客网 时间:2024/04/27 16:04

关于Activity动画那些事

关于activity的动画,相信大家再熟悉不过了,我们开发中经常用到,网上资料也很多,但是也有一些小细节需要我们注意,今天这篇文章我总结了几种常用的动画实现方式,通过这篇文章,你可以了解到:

  • 几种常见的activity动画实现方式
  • activity动画中需要注意的细节
  • 这几种方式的优缺点比较,我们如何取舍
  • 对这几种方式进行简易封装,提高我们的开发效率

几种常见的实现方式:

1. activity.overridePendingTransition()

这种方式相信大家一定非常熟悉了,简单回忆一下,我们只需要在start另一个activity启动的时候调用就行,这个方法接收两个参数,一个是我们新启动的activity进入时的动画,另一个是当前activity退出时的动画(很多时候我们是不需要这个动画的,只需要另一个activity进入的效果),首先我们在我们res文件目录下新建一个anim文件夹,随便建一个动画效果,我这里以从右边划入为例,首先我建一个slide_in_right.xml的文件:

<set xmlns:android="http://schemas.android.com/apk/res/android">    <translate        android:duration="300"        android:fromXDelta="100.0%p"        android:toXDelta="0.0" /></set>

我们最熟悉的平移动画,用法:

private void start() {        startActivity(new Intent(MainActivity.this, TestActivity.class));        overridePendingTransition(R.anim.slide_in_right, 0);    }

效果如图:
这里写图片描述
这样写似乎是我们要的效果,然而,在有些手机上却出现了很奇怪的黑色平移块,此时我们需要保持前一个页面不同,所以我们假写一个动画 animo_no:

<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android">    <alpha        android:duration="300"        android:fromAlpha="1"        android:toAlpha="1" /></set>

这个动画是没有任何效果的,我们拿来抵消动画效果,现在跑起来就像是另一个页面执行动画,前面一个页面不动的效果了:

private void start() {        startActivity(new Intent(MainActivity.this, TestActivityWithTheme.class));        overridePendingTransition(R.anim.slide_in_right, R.anim.animo_no);            }

这里写图片描述

结束动画:
进入之后,为了保证动画的一致连贯性,所以我们需要在已经启动的activity中也覆盖同样的方法来执行结束动画,我们再建一个slide_out_right.xml:

<set xmlns:android="http://schemas.android.com/apk/res/android">    <translate        android:duration="300"        android:fromXDelta="0"        android:toXDelta="100.0%p" /></set>

使用:
我们重写finish方法,在finish的时候加上我们的动画效果:

 @Override    public void finish() {        super.finish();        overridePendingTransition(0, R.anim.slide_out_right);    }

这里有个细节需要注意一下,这个方法只不能在onBackPressed里面重写,重写的话只有在点回退键的时候才有动画效果,而在代码中调用finish的时候,是没有效果的,所以一定要重写在finish方法里面,如图,我们看看完整的效果,包括回退键和代码内部调用finish方法。
这里写图片描述


2. ActivityOptionsCompat

这个类是supportv4中新加的一个类,可以为activity添加各种动画效果,这里面的api至少要求4.0以上的系统,部分要求5.0以上,不过目前4.0一下的手机几乎没有了,这里权当对新方法的介绍了,为了实现我们刚刚才的效果,现在我们可以这么写:

private void start() {        ActivityOptionsCompat compat = ActivityOptionsCompat.makeCustomAnimation(MainActivity.this, R.anim.slide_in_right, R.anim.animo_no);        ActivityCompat.startActivity(MainActivity.this,Intent, compat.toBundle());    }

这样就可以达到跟我们之前一样的效果了,当然,结束的时候还是要调用overridePendingTransition,不过这样写起来好像还是之前那个overridePendingTransition更好用,当然他的功能远不止这些,包括谷歌我们封装好了的一些拉伸扩散动画,共享元素动画等等,其实这个方法也就是新的api对老的方法的兼容而已,今天这里只讨论同一种动画效果的多种方式,算总结了。


3.AppTheme: 直接在主题中修改activity动画样式:

首先我们在res的style.xml文件中写一个ActivityAnimation,继承自@android:style/Animation.Activity:分别把我们之前用的三个动画样式指定进去,代码如下
<style name="ActivityAnimation" parent="@android:style/Animation.Activity">item name="android:activityOpenEnterAnimation">@anim/slide_in_bottom</item><item name="android:activityOpenExitAnimation">@anim/animo_no</item><item name="android:activityCloseExitAnimation">@anim/slide_out_bottom</item></style>

在我们activity 用到的theme中加入android:windowAnimationStyle 属性:

<style name="AppThemeWithWindowAnimation" parent="Theme.AppCompat.Light.DarkActionBar"><item name="android:windowAnimationStyle">@style/ActivityAnimation</item></style>

然后我们在我们需要用到的activity中指定我们刚刚定义的theme——
AppThemeWithWindowAnimation

<activityandroid:name=".TestActivityWithTheme"        android:theme="@style/AppThemeWithWindowAnimation" />

如果我们需要所有页面都使用我们定制的样式,只需要在在整个application 的theme标签下指定即可:

<application        android:allowBackup="true"        android:icon="@mipmap/ic_launcher"        android:label="@string/app_name"        android:supportsRtl="true"           android:theme="@style/AppThemeWithWindowAnimation"></application>

so,大功告成,这样一来,我们不需要额外代码,自动替换了原有的app默认跳转样式,很方便有木有~!

比较与总结:

首先,这三种方式都能实现同一种效果,但是相比前两种方式,第三种更为方便的为所有页面指定动画样式,不需要额外的代码,但是不够灵活,不能代码控制,如果我们需要第一个页面左右划入,第二个页面上下划入,第三种方式就做不到了,所以往往实际开发者,往往结合起来使用会比较方便,针对于前两种方式,我们就可以灵活的在代码中更换动画样式,但是这两个方法的实现都是基于activity的,只有在activity中调用才能有效果,但有些时候,我们启动另一个activity的实例本身可能不是activity,也许是Service 也许是applicationContext(比如有时候我们需要从推送服务来启动某一个activity,但是推送回调提供的context就不是activity实例),所以在这种情况下,我们的第一二中方法是没有效果的,此时只能依赖第三种方法,开发中,我们需要根据自己需要的情况而具体变动。

简单封装:

当然,为了提高我们的开发效率,我们可以对前两种方式进行简单的封装,让我们在使用的时候,不需要写额外的代码,所以思路就是我为activity设计一个启动方法,将动画代码逻辑写在其中即可,我们自己新建activity的时候继承自这个activity即可:

public abstract class AnimationBaseActivity extends Activity {    /**     * 此变量用于启动activity时的标识,用于判断activity结束时是否覆盖原有动画,以保持启动退出动画一致性     */    private static boolean isFromActivityStart = false;    /**     * 结束动画     */    private static int exitAnimation = 0;    /**     * 获取启动intent     *     * @param context     * @param tClass     * @return     */    public static Intent getLaunchIntent(Context context, Class<? extends AnimationBaseActivity> tClass) {        return new Intent(context, tClass);    }    /**     * 带动画启动页面,覆盖系统原有动画样式原理是startactivity 前overridePendingTransition()     *     * @param context 这里需要context是activity实例才能覆盖动画效果,否则仍旧是系统原有动画样式     * @param intent  调用getLaunchIntent方法获取     */    public static void startWithOldAnimation(Context context, Intent intent) {        if (context instanceof Activity) {            isFromActivityStart = true;            exitAnimation = 0;            context.startActivity(intent);//须在activity启动前调用//            ((Activity) context).overridePendingTransition(R.anim.slide_in_right, 0);本应该是这个参数,但是有的机型会有前一个activity结束动画,所以需要虚拟一个动画代替,做到保持原有页面不动的效果            ((Activity) context).overridePendingTransition(R.anim.slide_in_right, R.anim.animo_no);        } else {//context 非activity的情形 ,无法调用覆盖方法,因此还是系统默认的跳转动画            isFromActivityStart = false;            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);            context.startActivity(intent);        }    }    /**     * @param context        这里需要context是activity实例才能覆盖动画效果,否则仍旧是系统原有动画样式     * @param intent         调用getLaunchIntent方法获取     * @param enterAnimation 进入动画     * @param exiteAnimation 结束动画     */    public static void startWithOldAnimation(Context context, Intent intent, int enterAnimation, int exiteAnimation) {        if (context instanceof Activity) {            isFromActivityStart = true;            exitAnimation = exiteAnimation;            context.startActivity(intent);            ((Activity) context).overridePendingTransition(enterAnimation, R.anim.animo_no);        } else {            isFromActivityStart = false;            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);            context.startActivity(intent);        }    }    /**     * 带动画启动页面,使用ActivityOptionsCompat实现 4.1以上有效     *     * @param context 这里需要context是activity实例才能覆盖动画效果,否则仍旧是系统原有动画样式     * @param intent  调用getLaunchIntent方法获取     */    public static void startWithNewAnimation(Context context, Intent intent) {        if (context instanceof Activity) {            isFromActivityStart = true;            exitAnimation = 0;            ActivityOptionsCompat compat = ActivityOptionsCompat.makeCustomAnimation(context, R.anim.slide_in_right, R.anim.animo_no);            ActivityCompat.startActivity((Activity) context, intent, compat.toBundle());        } else {//context 非activity的情形 ,无法调用覆盖方法,因此还是系统默认的跳转动画            isFromActivityStart = false;            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);            context.startActivity(intent);        }    }    /**     * @param context        这里需要context是activity实例才能覆盖动画效果,否则仍旧是系统原有动画样式     * @param intent         调用getLaunchIntent方法获取     * @param enterAnimation 进入动画     * @param exiteAnimation 结束动画     */    public static void startWithNewAnimation(Context context, Intent intent, int enterAnimation, int exiteAnimation) {        if (context instanceof Activity) {            isFromActivityStart = true;            exitAnimation = exiteAnimation;            ActivityOptionsCompat compat = ActivityOptionsCompat.makeCustomAnimation(context, enterAnimation, R.anim.animo_no);            ActivityCompat.startActivity((Activity) context, intent, compat.toBundle());        } else {            isFromActivityStart = false;            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);            context.startActivity(intent);        }    }    /**     * 在finish方法中覆盖原有动画样式,若之前不是通过activity启动的 则不覆盖,使动画样式一致     */    @Override    public void finish() {        super.finish();        if (isFromActivityStart)            overridePendingTransition(0, exitAnimation == 0 ? R.anim.slide_out_right : exitAnimation);    }}

然后我们在我们真正开发的页面里面这么写:

public class TestActivity extends AnimationBaseActivity {    private static String TEST_KEY = "test_key";    /**     * 随便写的启动方法用于测试参数传递这里以String类型为例,如果有别的需求直接自己随意加即可     *     * @param context     * @param testValue     */    public static void startWithNew(Context context, String testValue) {        Intent intent = getLaunchIntent(context, TestActivity.class).putExtra(TEST_KEY, testValue);        startWithNewAnimation(context, intent);//带动画效果启动    }    public static void startWithOld(Context context, String testValue) {        Intent intent = getLaunchIntent(context, TestActivity.class).putExtra(TEST_KEY, testValue);        startWithOldAnimation(context, intent);    }    public static void startWithOld(Context context, int enterAnimation, int exiteAnimation, String testValue) {        Intent intent = getLaunchIntent(context, TestActivity.class).putExtra(TEST_KEY, testValue);        startWithOldAnimation(context, intent, enterAnimation, exiteAnimation);    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.test_activity);        findViewById(R.id.finish).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                finish();            }        });        initData();    }    /**     * 处理数据传递     */    private void initData() {        Toast.makeText(this, getIntent().getExtras().getString(TEST_KEY, ""), Toast.LENGTH_SHORT).show();    }}

分析:
首先我们看到第10行,我们自己写来用于真正调用的启动方法,11行我们调用了基类的获取启动Intent的方法,拿到这个对象,我们可以处理我们自己所需要传递的参数什么的,我这里只是随便写的一个String类型作为测试参数传递,然后我们才调用基类中我们封装的方法执行动画。然后我们再来看看基类中的封装方法:首先看第29行,这里首先会判断当前context是否为activity的实例,是则isFromActivityStart 会被赋值为ture ,在启动的时候我们就可以调用overridePendingTransition()方法覆盖动画样式,这里我们分别指定了默认进入样式,然后启动activity 执行动画,如果不是activity的实例对象,这里需要对intent加入setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),这样才能保证由applicationcontext 启动activity不会报错,此时我们无法调用overridePendingTransition方法,isFromActivityStart=false,此时activity启动即为系统默认动画。
再来看看第105行,根据isFromActivityStart的值来确定是否重写我们的系统动画方法,来保证我们自己定义的动画启动和系统默认启动的动画开始和结束都保持一致。
同样的,在48行,我们重载了startWithOldAnimation方法,此时多了进入动画和退出动画,我们可以通过此方法灵活改变样式,exitAnimation 即我们指定的自定义退出动画样式,在退出时使用。

使用测试:

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        findViewById(R.id.btn_old).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {//                TestActivity.startWithOld(getApplicationContext(), "这是老的方法动画实现"); //applicationcontext 或者service 等其他context实例 启动activity 调用系统默认动画//                TestActivity.startWithOld(MainActivity.this, "这是老的方法动画实现");//用基类中默认的动画替换样式                TestActivity.startWithOld(MainActivity.this, R.anim.slide_in_bottom, R.anim.slide_out_bottom, "这是老的方法动画实现");//用自己指定的动画样式            }        });        findViewById(R.id.btn_new).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                TestActivity.startWithNew(MainActivity.this, "这是新的方法动画实现");            }        });        findViewById(R.id.btn_theme).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                startActivity(new Intent(MainActivity.this, TestActivityWithTheme.class));            }        });    }}

下面来看看效果

这里写图片描述

大功告成,这样一来将启动方法封装之后,我们就不用额外写动画代码了,偷懒什么的最开心了~


源码戳这儿

1 0