一款APP让人赏心悦目的APP,动画是它并不可少的部分,Android提供很多原生动画方式如帧动画,属性动画等,也提供了一些组件动画套装,如Activity间的过渡跳转动画,还有Fragment,Dialog等展示动画。本文只讲Android转场动画和共享元素动画。
Google在Android5.0时开始集成了更多更全面更易于开发者定义的的转场动画和共享元素动画。
下面我们看看示例Google Play:
当你点击app logo时,这个logo会通过动画引导出下一个界面,引导过程中逐渐显示界面剩余的所有元素(View),完成非常平滑界面过渡。由于录制和图片压缩的原因,效果图画面比较模糊,画面有白闪,卡顿现象,真实效果大家可以下载体验下。
Transition 框架是 Android 4.4 KitKat 中加入的,但在 5.0 才开始被人应用起来,
而且这一部分也涉及了 22.0 的 API,虽然有对应的 support.v4 包,但也还是有点问题。
所以这一部分可以说是 5.0 以上适用的方法。
1.设置 Activity 引用的 theme
设置 windowContentTransitions 为 true,即开启窗口内容过渡
这里遇到一点小问题,即上述 Activity 引用的 style 中不仅设置了 android:windowContentTransitions,也设置了 android:windowIsTranslucent : 让 Activity 的背景为透明,在我测试的时候发现使用共享元素的时候出现了返回时闪屏的现象,解决方法是设置 Activity 背景颜色为透明。
在 onCreate 中:
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().getWindow().setBackgroundDrawableResource(R.color.transparent);
}
或者在上述的 style 中的 theme 添加:
- @android:color/transparent
2.设置共享元素
其实实现这样的效果就是把第一个界面的 ImageView 移动、放大到第二个界面的 ImageView 的位置,借助 API 实现效果可以省去自己写动画的逻辑,但就需要让系统关联两个 View。
而关联两个 View 通过设置 android:transitionName 属性。
首先在第一个界面的 activity_main.xml:
...
在打开的 Activity 的 xml 中
...
对应的 ImageView 中的android:transitionName属性值必须相同,而对两个控件的大小、id 等属性并无要求。
3.启动设置
第一个 Activity 中,使用共享元素启动新界面方法
MainActivity:
Intent intent = new Intent(getActivity(),SecondActivity.class);
ActivityOptionsCompat options = ActivityOptionsCompat
.makeSceneTransitionAnimation(getActivity(), mImgView,"testImg");
startActivity(intent,options.toBundle());
在 makeSceneTransitionAnimation 传入的参数中,mImgView 是第一个界面中 ImageView 的实例,第三个参数对应 xml 中的 android:transitionName 的值。
在被打开的 Activity 中
首先加载图片还是要自己写的,其余的需要注意:
返回不再调用finishActivity() 而是 supportFinishAfterTransition()。
@Override
public void onBackPressed() {
supportFinishAfterTransition();
}
因为打开新的 Activity 的时候,可能要去加载新的图片,这时候我们需要延迟过渡动画的开始,直到图片加载完成之后再开始动画。否则会出现各种 bug。
所以要在第二个 Activity 中的 onCreate() 中阻止动画的执行:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 延迟共享动画的执行
postponeEnterTransition();
}
然后在图片加载完成后开始动画:
Glide.with(this)
.load(data.getImage())
.priority(Priority.HIGH)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(new GlideDrawableImageViewTarget(mImageView){
@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation super GlideDrawable> animation) {
super.onResourceReady(resource, animation);
//图片加载完成的回调中,启动过渡动画
supportStartPostponedEnterTransition();
}
});
当然,启动动画不一定要等待图片加载完成再进行,因为还存在着图片加载失败、加载时间过长等问题,这里只是提出一种方法,实际还是自己看情况决定。
以上只是简单的实现了一种效果,关于 Transition 的使用、共享元素在
Fragment 中的使用、多个共享元素的使用等,在这里暂时不打算细讲。
然而作为挖坑大师的Google从不吝啬在自己的每一个产品上给开发者挖坑–Android5.0以下版本无法使用该类型的动画。
5.0以下手动撸代码实现效果,当然5.0以上版本也同样适用。
获取ActivityA元素信息(如View的大小,位置,id)即属性值材料,作为转场动画的初始值
ActivityA通过Intent传递给ActivityB
ActivityB设为全透明(Window和Content Layout以及元素View皆为透明)
ActivityB读取Intent中的初始值信息并为其元素准备转场动画
ActivityB执行共享元素转场动画
我们以上述google play的转场动画为例,ActivityA上有个app logo即为ImageViewA,ActivityB的即为ImageViewB,logo图片的资源地址为imgUrl。下面用代码示例走流程:
1.获取ActivityA的元素信息
private Bundle createMaterials(View view) {
// 位置坐标
int[] location = new int[2];
view.getLocationOnScreen(location);
Bundle b = new Bundle();
int left = location[0];
int top = location[1];
// 宽高大小
int width = view.getWidth();
int height = view.getHeight();
// 传入Bundle
b.putInt("left", left);
b.putInt("top", top);
b.putInt("width", width);
b.putInt("height", height);
return b;
}
2.在ActivityA中构建Intent
// 获取初始值材料
Bundle materials=createMaterials(imageViewA);
Intent intent = new Intent(activityA, ActivityB.class);
intent.putExtra(EXTRA_IMG_URL, imgUrl);
// 传入构建好的View属性值材料
intent.putExtra(EXTRA_VIEW_ATTRS_MATERIALS , materials);
startActivity(intent);
// 禁止系统默认动画
overridePendingTransition(0, 0);
3.ActivityB设为全透明
Window设为透明,在ActivityB主题(style)中设置
Layout以及元素View设为透明
4.ActivityB读取Intent转场信息
private void prepareTransition() {
int[] location = new int[2];
imageViewB.getLocationOnScreen(location);
// 移动到ActivityA元素imageViewA同样的位置
deltaX = imageViewALeft - location[0];
deltaY = imageViewATop - location[1];
imageViewB.setTranslationX(deltaX);
imageViewB.setTranslationY(deltaY);
// 缩放到imageViewA同样的大小
scaleX = (float) imageViewAWidth / imageViewB.getWidth();
scaleY = (float) imageViewAHeight / imageViewB.getHeight();
imageViewB.setScaleX(scaleX);
imageViewB.setScaleY(scaleY);
}
5.ActivityB执行共享元素转场动画
private void runEnterAnimation() {
imageViewB.setVisibility(View.VISIBLE);
// 加载图片,如Glide
Glide.with(context).load(imgUrl).into(imageViewB);
// 执行动画
imageViewB.animate()
.setDuration(DEFAULT_DURATION)
.setInterpolator(DEFAULT_INTERPOLATOR)
.scaleX(1f)
.scaleY(1f)
.translationX(0)
.translationY(0)
.start();
}
ActivityB回退至ActivityA
流程比较简单,直接上代码喽:
private void runExitAnimation() {
// 执行回退动画
imageViewB.animate()
.setDuration(DEFAULT_DURATION)
.setInterpolator(DEFAULT_INTERPOLATOR)
.scaleX(scaleX)
.scaleY(scaleY)
.translationX(deltaX)
.translationY(deltaY)
.withEndAction(new Runnable() {
@Override
public void run() {
// 关闭ActivityB
finish();
// 禁止系统默认动画
overridePendingTransition(0, 0);
}
}).start();
}
OK,大体流程如上,Demo效果图如下:
参考
https://blog.csdn.net/ausboyue/article/details/80035452
https://www.jianshu.com/p/ea51eefa0b5c