继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Android 中的转场动画及兼容处理

桃花长相依
关注TA
已关注
手记 113
粉丝 15
获赞 56

Android 中的动画有很多,除了在一个界面上使用帧动画、属性动画将一个或多个 View 进行动画处理以外,还可以用于两个界面之间过渡、跳转。在 Android 5.0 之前,我们已经有了 overridePendingTransition() 方法来实现一些转场效果。然而,在 Android 5.0 以后,转场效果更加炫酷。 比如下面的动画:

                                               5bac9f7900016e4002400423.jpg

本篇文章,主要就是解说如何实现上述的效果。主要内容包括:

o    Android 5.0+ 的转场动画

o    Android 4.X 模拟实现 Android 5.0+ 转场效果。

Android 5.0+ 中的转场动画

实现转场动画只需三步:

o    在 res/ 目录下创建 transition 文件夹,在该文件夹下定义界面转场动画和共享元素的动画。

o    在 res/value/style 文件中为每个 Activity 指定转场动画的 style ,并在 AndroidManifest.xml 文件中为每个 Activity 设置对应的 android:theme

o    在 Activity 调用 startActivity() 切换动画前,使用 ActivityOptionsCompat 来创建转场动画时的共享对象。

下面就来对这三步进行详细讲解。

定义转场动画

在 res/ 目录下创建了 transition 资源文件夹后,就可以在该文件夹下对每一种动画进行定义。

一般来说,对 Activity 定义一个过渡动画可以写成下面的形式:

1
2
3
4
5
6
<explode xmlns:android="http://schemas.android.com/apk/res/android">
   <targets>
      <target android:excludeId="@android:id/statusBarBackground"/>
      <target android:excludeId="@android:id/navigationBarBackground"/>
   </targets>
</explode>

其中 <explode/> 是动画效果的名称,Android 5.0(API 级别 21)支持这些进入与退出转换:

o    分解(explode):从场景中心移入或移出视图。

o    滑动(slide):从场景边缘移入或移出视图。

o    淡入淡出(fade):通过调整透明度在场景中增添或移除视图。

而每一种动画效果,都有额外的属性。比如滑动 slide,可以使用 android:slideEdge="top" 设置滑动的方向;淡入淡出(fade)可以使用 android:fadingMode="fade_in" 设置具体是淡入(fade_in)还是淡出(fade_out)等。

<targets/> 标签里面定义需要转场(或者不需要转场)的目标 id ,这个 id 可以使系统自带的,也可以是我们自己视图中的 view 的 id,每一个 id 需要单独在 <target/> 标签中定义,android:targetId 表示目标ID需要进行过渡转换的 view,而 android:excludeId 表示我们不需要该 ID 的 view 进行过渡转场。上面的那段代码的意思是说,除了状态栏和导航栏以外所有的 view,都执行 explode 动画。

如果我们想要在同一个过渡状态中实现两种或多种动画效果怎么办?也简单,将根标签替换为 <transitionSet/>,然后定义每一种动画效果,最后记得在根标签中使用 android:transitionOrdering 注明这几种动画的演示顺序,sequential 表示顺序执行,而 together 表示同时执行。比如像下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <slide android:slideEdge="bottom">
        <targets>
            <target android:targetId="@id/cardview"/>
        </targets>
    </slide>
    <fade>
        <targets>
            <target android:excludeId="@android:id/statusBarBackground"/>
            <target android:excludeId="@android:id/navigationBarBackground"/>
            <target android:excludeId="@id/cardview"/>
        </targets>
    </fade>
</transitionSet>

这段代码的意思就很简单了,该 xml 定义了两个过渡动画,并且同时执行。第一个动画是针对 id 为 cardView 的 view 进行滑动,第二个动画将除了状态栏、导航栏和 cardview 以外的 view,进行淡入淡出。

为每个 Activity 定义转场样式

这里的每一种动画,指的是在进行界面跳转过渡时,两个界面的状态。比如对于 Activity A 和 Activity B 这两个界面,可能的状态如下:

o    界面 A 跳转至界面 B :这时界面 A 是退出(exit )过渡状态,而对应的界面B是进入(enter)过渡状态。

o    界面 B 返回到界面 A :这时界面 A 是重新进入(reenter)过渡,而对应的界面B则是返回(return)过渡。

一般来说,所有的 Activity 过渡动画都可以定义成如下的形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<style name="BaseAppTheme" parent="android:Theme.Material">
  <!-- 开启过渡效果 -->
  <item name="android:windowContentTransitions">true</item>
  <!-- 指定界面进入/退出动画效果 -->
  <item name="android:windowEnterTransition">@transition/explode</item>
  <item name="android:windowExitTransition">@transition/explode</item>
  <!-- 指定共享元素进入/退出的动画效果 -->
  <item name="android:windowSharedElementEnterTransition">
    @transition/change_image_transform</item>
  <item name="android:windowSharedElementExitTransition">
    @transition/change_image_transform</item>
</style>

当然,你可以不用写全,比如在我的 Demo 中一个界面的转场动画文件如下:

1
2
3
4
5
6
7
<style name="AppTheme.Detail">
    <item name="windowActionBar">false</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowTranslucentStatus">true</item>
    <item name="android:windowAllowEnterTransitionOverlap">false</item>
    <item name="android:windowEnterTransition">@transition/detail_enter</item>
</style>

调用 ActivityOptionsCompat

转场动画是在两个界面的跳转返回时发生的,所以,当使用 intent 跳转界面时,需要调用 ActivityOptionsCompat来指定动画的运行。

一般来说,调用 ActivityOptionsCompat 的模板代码如下:

1
2
3
4
5
6
// 创建一个包含过渡动画信息的 ActivityOptions 对象
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this, view, getString(R.string.image_transition_name));
// 使用 Intent 跳转界面,并传递共享对象信息
Intent intent = new Intent(this, DetailActivity.class);
startActivity(intent, optionsCompat.toBundle());

ActivityOptionsCompat 是在support v4 包里面的,其实它是 ActivityOptions 的一个兼容(ActivityOptions是API 16引入的)。

然后,我们需要在第二个 Activity 中,将转场的图片获取并显示到界面中。

多个共享元素的过渡实现

有时候我们需要让多个元素产生动画效果,可以使用 Pair<> 来实现:

1
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this, Pair.create(view1, "agreedName1"), Pair.create(view2, "agreedName2"));

手动实现一个转场动画

现在市面上,Android 5.0 以下的手机系统还有一定的市场份额,所以为了照顾这些用户,我们只能手动实现一下共享元素的转场动画效果。

实现的思路也比较简单,大概的步骤如下:

o    确定第一个界面的共享元素,将其信息传递个第二个界面

o    第二个界面接收信息,开始的时候将界面设置为透明,并只显示共享元素。

o    将第二个界面的共享元素进行动画处理。

那么我们开始一步步实现上面的步骤。

获取共享元素位置信息

在第一个界面中,我们需要获取到共享元素的位置信息,并将其传递给下一个界面。于是乎,我们可以在第一个界面元素点击事件中,这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void imageClick(View view) {
    Intent intent = new Intent(AnimeActivity.this, AnimeDetailActivity.class);
    // 创建一个 rect 对象来存储共享元素位置信息
    Rect rect = new Rect();
    // 获取元素位置信息
    view.getGlobalVisibleRect(rect);
    // 将位置信息附加到 intent 上
    intent.setSourceBounds(rect);
    CustomImage customImage = (CustomImage) view;
    intent.putExtra(AnimeDetailActivity.EXTRA_IMAGE, customImage.getImageId());
    startActivity(intent);
    // 屏蔽 Activity 默认转场效果
    overridePendingTransition(0, 0);
}

其中 getGlobalVisibleRect() 方法的含义是,获取 可见的状态栏高度+可见的标题栏高度+Rect左上角到标题栏底部的距离,如果标题栏被隐藏了,那么可见标题栏高度为0。

接下来,就在在第二个界面接收位置信息并将该图片展示出来了。

模拟转场动画

在第二个界面中,我们需要做如下的操作:

o    获取上共享元素信息。

o    计算共享元素缩放比例和位移距离。

o    调用动画,完成模拟转场效果。

我将上面三个步骤的代码如下,你也可以下载我完整的 Demo 来查看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private void initial() {
    // 获取上一个界面传入的信息
    mRect = getIntent().getSourceBounds();
    mRescourceId = getIntent().getExtras().getInt(EXTRA_IMAGE);
    // 获取上一个界面中,图片的宽度和高度
    mOriginWidth = mRect.right - mRect.left;
    mOriginHeight = mRect.bottom - mRect.top;
    // 设置 ImageView 的位置,使其和上一个界面中图片的位置重合
    FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(mOriginWidth, mOriginHeight);
    params.setMargins(mRect.left, mRect.top - getStatusBarHeight(), mRect.right, mRect.bottom);
    mImageView.setLayoutParams(params);
    // 设置 ImageView 的图片和缩放类型
    mImageView.setImageResource(mRescourceId);
    mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
    // 根据上一个界面传入的图片资源 ID,获取图片的 Bitmap 对象。
    BitmapDrawable bitmapDrawable = (BitmapDrawable) getResources().getDrawable(mRescourceId);
    Bitmap bitmap = bitmapDrawable.getBitmap();
    // 计算图片缩放比例和位移距离
    getBundleInfo(bitmap);
    // 创建一个 Pallette 对象
    mImagePalette = Palette.from(bitmap).generate();
    // 使用 Palette 设置背景颜色
    mContainer.setBackgroundColor(
            mImagePalette.getVibrantColor(ContextCompat.getColor(this, android.R.color.black)));
}

在12行,通过设置 Margin 的形式来确定图片的位置,需要注意的是,由于状态栏是在父控件 FramLayout 之外的,因此我们要将 Rect.top 的值减去状态栏的高度,这样才是相对于屏幕的绝对位置。然后,getBundleInfo() 方法的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
private void getBundleInfo(Bitmap bitmap) {
    // 计算图片缩放比例,并存储在 bundle 中
    if (bitmap.getWidth() >= bitmap.getHeight()) {
        mScaleBundle.putFloat(SCALE_WIDTH, (float) mScreenWidth / mOriginWidth);
        mScaleBundle.putFloat(SCALE_HEIGHT, (float) bitmap.getHeight() / mOriginHeight);
    } else {
        mScaleBundle.putFloat(SCALE_WIDTH, (float) bitmap.getWidth() / mOriginWidth);
        mScaleBundle.putFloat(SCALE_HEIGHT, (float) mScreenHeight / mOriginHeight);
    }
    // 计算位移距离,并将数据存储到 bundle 中
    mTransitionBundle.putFloat(TRANSITION_X, mScreenWidth / 2 - (mRect.left + (mRect.right - mRect.left) / 2));
    mTransitionBundle.putFloat(TRANSITION_Y, mScreenHeight / 2 - (mRect.top + (mRect.bottom - mRect.top) / 2));
}

动画处理

最后我们需要使用动画来模拟转场效果,代码如下:

1
2
3
4
5
6
7
8
9
10
private void runEnterAnim() {
    mImageView.animate()
              .setInterpolator(DEFAULT_INTERPOLATOR)
              .setDuration(DURATION)
              .scaleX(mScaleBundle.getFloat(SCALE_WIDTH))
              .scaleY(mScaleBundle.getFloat(SCALE_HEIGHT))
              .translationX(mTransitionBundle.getFloat(TRANSITION_X))
              .translationY(mTransitionBundle.getFloat(TRANSITION_Y))
              .start();
}

很简单,自此,入场动画效果基本模拟完毕。

而退场动画就更简单了,直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void runExitAnim() {
    mImageView.animate()
              .setInterpolator(DEFAULT_INTERPOLATOR)
              .setDuration(DURATION)
              .scaleX(1)
              .scaleY(1)
              .translationX(0)
              .translationY(0)
              .withEndAction(new Runnable() {
                  @Override
                  public void run() {
                      finish();
                      overridePendingTransition(0, 0);
                  }
              })
              .start();
}

所以,是不是很简单?

原文链接:http://www.apkbus.com/blog-705730-62529.html

打开App,阅读手记
1人推荐
发表评论
随时随地看视频慕课网APP