转载请注明出处:http://blog.csdn.net/wl9739/article/details/52833668
Android 中的动画有很多,除了在一个界面上使用帧动画、属性动画将一个或多个 View 进行动画处理以外,还可以用于两个界面之间过渡、跳转。在 Android 5.0 之前,我们已经有了 overridePendingTransition() 方法来实现一些转场效果。然而,在 Android 5.0 以后,转场效果更加炫酷。 比如下面的动画:
本篇文章,主要就是解说如何实现上述的效果。主要内容包括:
实现转场动画只需三步:
AndroidManifest.xml
文件中为每个 Activity 设置对应的 android:theme。下面就来对这三步进行详细讲解。
在 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>
其中,<explode/>
是动画效果的名称,Android 5.0(API 级别 21)支持这些进入与退出转换:
而每一种动画效果,都有额外的属性。比如滑动 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
表示同时执行。比如像下面的代码:
<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 A 和 Activity B 这两个界面,可能的状态如下:
一般来说,所有的 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>
转场动画是在两个界面的跳转返回时发生的,所以,当使用 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();
}
当然,这只是简单的模拟,如果想要和 Android 5.0+ 的转场效果相同,还需要做很多的动画处理。
本篇文章的 Demo 地址:Demo
推荐阅读:定义定制动画