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

   2016-10-17 0
核心提示:Android 中的动画有很多,除了在一个界面上使用帧动画、属性动画将一个或多个 View 进行动画处理以外,还可以用于两个界面之间过渡、跳转。在 Android 5.0 之前,我们已经有了 overridePendingTransition() 方法来实现一些转场效果。然而,在 Android 5.0 以

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

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

  • Android 5.0+ 的转场动画
  • Android 4.X 模拟实现 Android 5.0+ 转场效果。

Android L 中的转场动画

实现转场动画只需三步:

  • res/ 目录下创建 transition 文件夹,在该文件夹下定义界面转场动画和共享元素的动画。
  • res/value/style 文件中为每个 Activity 指定转场动画的 style ,并在 AndroidManifest.xml 文件中为每个 Activity 设置对应的 android:theme
  • 在 Activity 调用 startActivity() 切换动画前,使用 ActivityOptionsCompat 来创建转场动画时的共享对象。

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

定义转场动画

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

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

<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>

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

  • 分解(explode) :从场景中心移入或移出视图。
  • 滑动(slide) :从场景边缘移入或移出视图。
  • 淡入淡出(fade) :通过调整透明度在场景中增添或移除视图。

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

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

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

<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 这两个界面,可能的状态如下:

  • 界面 A 跳转至界面 B :这时界面 A 是 退出(exit ) 过渡状态,而对应的界面B是 进入(enter) 过渡状态。
  • 界面 B 返回到界面 A :这时界面 A 是 重新进入(reenter) 过渡,而对应的界面B则是 返回(return) 过渡。

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

<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 中一个界面的转场动画文件如下:

<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 的模板代码如下:

// 创建一个包含过渡动画信息的 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<> 来实现:

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

手动实现一个转场动画

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

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

  • 确定第一个界面的共享元素,将其信息传递个第二个界面
  • 第二个界面接收信息,开始的时候将界面设置为透明,并只显示共享元素。
  • 将第二个界面的共享元素进行动画处理。

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

获取共享元素位置信息

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

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。

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

模拟转场动画

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

  • 获取上共享元素信息。
  • 计算共享元素缩放比例和位移距离。
  • 调用动画,完成模拟转场效果。

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

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() 方法的代码如下:

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

动画处理

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

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

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

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

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

所以,是不是很简单?

本篇文章的 Demo 地址: UITransitionDemo

推荐阅读: 定义定制动画

 
标签: 安卓开发
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • 安卓中通知功能的具体实现
    安卓中通知功能的具体实现
    通知[Notification]是Android中比较有特色的功能,当某个应用程序希望给用户发出一些提示信息,而该应用程序又不在前台运行时,就可以借助通知实现。使用通知的步骤1、需要一个NotificationManager来获得NotificationManager manager = (NotificationManager
    02-05 安卓开发
  • Android view系统分析-setContentView
    Android view系统分析-setContentView
    第一天上班,列了一下今年要学习的东西。主要就是深入学习Android相关的系统源代码,夯实基础。对于学习Android系统源代码,也没什么大概,就从我们平常使用最基础的东西学起,也就是从view这个切入点开始学习Android的源码,在没分析源码之前,我们有的时候
    02-05 安卓开发
  • 如何进行网络视频截图/获取视频的缩略图
    如何进行网络视频截图/获取视频的缩略图
    小编导读:获取视频的缩略图,截图正在播放的视频某一帧,是在音视频开发中,常遇到的问题。本文是主要用于点播中截图视频,同时还可以获取点播视频的缩略图进行显示,留下一个问题,如下图所示, 如果要获取直播中节目视频缩略图,该怎么做呢?(ps:直播是直
  • Android NDK 层发起 HTTP 请求的问题及解决
    Android NDK 层发起 HTTP 请求的问题及解决
    前言新的一年,大家新年快乐~~鸡年大吉!本次给大家带来何老师的最新文章~虽然何老师还在过节,但依然放心不下广大开发者,在此佳节还未结束之际,给大家带来最新的技术分享~ 事件的起因不说了,总之是需要实现一个 NDK 层的网络请求。为了多端适用,还是选择
  • Android插件化(六): OpenAtlasの改写aapt以防止资源ID冲突
    Android插件化(六): OpenAtlasの改写aapt以防
    引言Android应用程序的编译中,负责资源打包的是aapt,如果不对打包后的资源ID进行控制,就会导致插件中的资源ID冲突。所以,我们需要改写aapt的源码,以达到通过某种方式传递资源ID的Package ID,通过aapt打包时获取到这个Package ID并且应用才插件资源的命名
    02-05 安卓开发
  • Android架构(一)MVP架构在Android中的实践
    Android架构(一)MVP架构在Android中的实践
    为什么要重视程序的架构设计 对程序进行架构设计的原因,归根结底是为了 提高生产力 。通过设计是程序模块化,做到模块内部的 高聚合 和模块之间的 低耦合 (如依赖注入就是低耦合的集中体现)。 这样做的好处是使得程序开发过程中,开发人员主需要专注于一点,
    02-05 安卓开发
  • 安卓逆向系列教程 4.2 分析锁机软件
    安卓逆向系列教程 4.2 分析锁机软件
    安卓逆向系列教程 4.2 分析锁机软件 作者: 飞龙 这个教程中我们要分析一个锁机软件。像这种软件都比较简单,完全可以顺着入口看下去,但我这里还是用关键点来定位。首先这个软件的截图是这样,进入这个界面之后,除非退出模拟器,否则没办法回到桌面。上面那
    02-05 安卓开发
  • Android插件化(二):OpenAtlas插件安装过程分析
    Android插件化(二):OpenAtlas插件安装过程分析
    在前一篇博客 Android插件化(一):OpenAtlas架构以及实现原理概要 中,我们对应Android插件化存在的问题,实现原理,以及目前的实现方案进行了简单的叙述。从这篇开始,我们要深入到OpenAtlas的源码中进行插件安装过程的分析。 插件的安装分为3种:宿主启动时立
    02-05 安卓开发
  • [译] Android API 指南
    [译] Android API 指南
    众所周知,Android开发者有中文网站了,API 指南一眼看去最左侧的菜单都是中文,然而点进去内容还是很多是英文,并没有全部翻译,我这里整理了API 指南的目录,便于查看,如果之前还没有通读,现在可以好好看一遍。注意,如果标题带有英文,说明官方还没有翻
  • 使用FileProvider解决file:// URI引起的FileUriExposedException
    使用FileProvider解决file:// URI引起的FileUri
    问题以下是一段简单的代码,它调用系统的相机app来拍摄照片:void takePhoto(String cameraPhotoPath) {File cameraPhoto = new File(cameraPhotoPath);Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);takePhotoIntent.putExtra(Medi
    02-05 安卓开发
点击排行