在开始之前吐槽下简书markdown竟然不支持生成目录列表,弄半天没弄出来,如果哪位知道,烦请告知。这里就简单的截图一下目录,讲究着看吧....
1. 转场动画
转场动画就是Activity通过元素之间的转换提供不同状态之间的视觉连接。你可以为进入和退出转换以及Activity之间共享元素的转换指定定制动画
1.1 Api21之前如何实现转场动画?
Api21之前我们实现转场动画有两种方式。
1.1.1 使用 overridePendingTransition
进入动画
startActivity(intent);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
退出动画
finish();
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
注意:
1.overridePendingTransition方法必须在startActivity()或者finish()方法的后面。
2.如果参数是0,表示没有动画。
1.1.2 使用 Activity主题Style配置
假设有两个Activity A和B。
A->B activityOpenEnterAnimation
B->A activityOpenExitAnimation
B退出A从新进入 activityCloseEnterAnimation
A退出 activityCloseExitAnimation
主题配置
<style name="AppThisTheme" parent="Theme.AppCompat.Light.NoActionBar">
...
<item name="android:windowAnimationStyle">@style/activityAnimation</item>
</style>
<style name="activityAnimation" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/slide_right_in</item>
<item name="android:activityOpenExitAnimation">@anim/slide_left_out</item>
<item name="android:activityCloseEnterAnimation">@anim/slide_left_in</item>
<item name="android:activityCloseExitAnimation">@anim/slide_right_out</item>
</style>
1.2 Api21之后如何实现转场动画?
在说Api21之后实现转场动画之前先来看一张图,来理清跳转Activity之间跳转设置的动画方法。
1.2.1 Activity转换动画原理
从图中可以看到Activity之间跳转可以有4个不同动作。
一般如果没有特殊需求,指定两个就可以了,
exitTransition和enterTransition。 设置进入和退出动画时,在进行returnTransition时,如果没有设置就会用renterTransition动画设置的值动作相反,同理在进行reenterTransition时,如果没有设置就会用exitTransition动画设置的值动作相反。
上面四个动作其实是View INVISIBLE 到 VISIBLE 或者 VISIBLE 到 INVISIBLE 转换过程。
exitTransition: A退出钱先获取试图为VISIBLE场景,设置试图为INVISIBLE获取INVISIBLE场景,根据transition差异的不同创建执行动画。
enterTransition:B进入时会把B中试图设置为INVISIBLE获取INVISIBLE 场景,然后将视图设置为VISIBLE,获取VISIBLE时的场景,根据transition差异的不同创建执行动画。
同理returnTransition 和 renterTransition 原理一样。
根据上面描述Activity场景转换动画时建立在Visibility基础上,支持Visibility有三种:
explode:爆炸效果(将视图从场景的中心移出或移出)
slide:移动效果(将视图从场景的一个边缘移动或移出,类似于目前项目中常用的activity切换动画,从右边进,从左边出。Slide还支持上面和下面进出)
fade :淡入淡出 。
Api21之后实现转场动画也有两种方式
1.2.2 使用 Activity主题Style配置
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
...
<!--必须制定该属性不然动画不起作用-->
<item name="android:windowActivityTransitions">true</item>
<!--activity进入动画-->
<item name="android:windowEnterTransition">@transition/slide_right</item>
<!--activity退出动画-->
<item name="android:windowExitTransition">@transition/slide_left</item>
<!--是否同时执行,如果同时执行A页面动画还没退出,B页面已经开始动画,感觉不是很和谐-->
<item name="android:windowAllowReturnTransitionOverlap">false</item>
<item name="android:windowAllowEnterTransitionOverlap">false</item>
</style>
注意:
- 如果使用主题继承自android:Theme.Material系列主题,则无需指定windowActivityTransitions,默认windowActivityTransitions属性为true。
- 使用主题指定activity进入和退出动画创建的xml 在 res/transition/ 文件下。
- 启动一个activity应使用兼容方式启动
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(MainActivity.this);
ActivityCompat.startActivity(MainActivity.this,
new Intent(MainActivity.this, TransitionSlideActivity.class), optionsCompat.toBundle());
启动一个Activity使用Api21之前的方法是没有任何效果转换动画效果的。
4.退出一个Activity使用兼容方式退出
ActivityCompat.finishAfterTransition(TransitionSlideActivity.this);
/res/transition/slide_left 文件内容如下:
<?xml version="1.0" encoding="utf-8"?>
<slide xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:duration="@android:integer/config_shortAnimTime"
android:slideEdge="left">
<targets>
<target
android:excludeId="@android:id/statusBarBackground"
tools:targetApi="lollipop" />
</targets>
</slide>
/res/transition/slide_right 文件内容如下:
<?xml version="1.0" encoding="utf-8"?>
<slide xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:duration="@android:integer/config_shortAnimTime"
android:slideEdge="right">
<targets>
<target
android:excludeId="@android:id/statusBarBackground"
tools:targetApi="lollipop" />
</targets>
</slide>
duration:表示动画时长
slideEdge:表示从哪边进入或退出取值有left|top|right|bottom|
targets:标记作用,例如上面例子中标记排除系统状态栏,其他View都应用于转场动画中。除了 排除 某个View,还有targetId 只针对某个View,其他View都不作用于转场动画。同理explode和fade也支持排除和针对某个View。
slide|explode|fade 之间还可以两两组合,比如退出的动画使用从左边退出和淡出。
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:duration="@android:integer/config_longAnimTime"
android:transitionOrdering="together">
<fade android:fadingMode="fade_in_out" />
<slide android:slideEdge="right" />
<targets>
<target
android:excludeId="@android:id/statusBarBackground"
tools:targetApi="lollipop" />
</targets>
</transitionSet>
效果图如下:
<center> 右进左出并且带有淡出淡入效果</center >
1.2.3 代码设置转场动画
Activity A:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setupWindowAnimations();
}
findViewById(R.id.btn_transition_slide_animation).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(MainActivity.this);
ActivityCompat.startActivity(MainActivity.this,
new Intent(MainActivity.this, TransitionSlideActivity.class), optionsCompat.toBundle());
}
});
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void setupWindowAnimations() {
TransitionSet transitionSet = new TransitionSet();
transitionSet.setDuration(300);
//一起动画
transitionSet.setOrdering(TransitionSet.ORDERING_TOGETHER);
Slide slideTransition = new Slide();
slideTransition.setSlideEdge(Gravity.LEFT);
transitionSet.addTransition(slideTransition);
Fade fadeTransition = new Fade();
transitionSet.addTransition(fadeTransition);
//排除状态栏
transitionSet.excludeTarget(android.R.id.statusBarBackground, true);
//是否同时执行
getWindow().setAllowEnterTransitionOverlap(false);
getWindow().setAllowReturnTransitionOverlap(false);
//退出这个界面
getWindow().setExitTransition(slideTransition);
}
Activity B:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_slide_transition);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setupWindowAnimations();
}
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ActivityCompat.finishAfterTransition(TransitionSlideActivity.this);
}
});
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void setupWindowAnimations() {
TransitionSet transitionSet = new TransitionSet();
transitionSet.setDuration(300);
//一起动画
transitionSet.setOrdering(TransitionSet.ORDERING_TOGETHER);
Slide slideTransition = new Slide();
slideTransition.setSlideEdge(Gravity.RIGHT);
transitionSet.addTransition(slideTransition);
Fade fadeTransition = new Fade();
transitionSet.addTransition(fadeTransition);
//排除状态栏
transitionSet.excludeTarget(android.R.id.statusBarBackground, true);
//是否同时执行
getWindow().setAllowEnterTransitionOverlap(false);
getWindow().setAllowReturnTransitionOverlap(false);
//进入
getWindow().setEnterTransition(slideTransition);
}
具体explode效果代码和效果图就不贴了可以自己去尝试看看。
2. 共享元素动画
在说Activity之间共享动画之前还是来一张官方图
2.1 描述
共享动画是分析两个界面共享view的尺寸,位置,样式的不同创建动画化的。从上图看出Activity1到Activity2,Android小机器人是一个被共享的元素。由于两个页面共享元素尺寸位置不一样,所以实现效果就是Activity1小机器人被放大然后显示在Activity2上,当然还可以有Fade效果。关于尺寸改变动画可以使用ChangeBounds,另外还有
ChangeTransform,ChangeClipBounds以及ChangeImageTransform。
关于四种ChangeXXX解释如下:
- ChangeBounds:检测view的位置边界创建移动和缩放动画
- ChangeTransform:检测view的scale和rotation创建缩放和旋转动画
- ChangeClipBounds:检测view的剪切区域的位置边界,和ChangeBounds类似。不过ChangeBounds针对的是view而ChangeClipBounds针对的是view的剪切区域(setClipBound(Rect rect) 中的rect)。如果没有设置则没有动画效果
- ChangeImageTransform:检测ImageView(这里是专指ImageView)的尺寸,位置以及ScaleType,并创建相应动画。
说那么多不如来张上面效果图的动图:
2.2 实现共享动画
一般情况下两个Activity ShareElementActivity 和 ShareElement1Activity ,假如ShareElementActivity->ShareElement1Activity,配置ShareElement1Activity 中 进入的共享动画,ShareElementActivity中配置退出转场动画即可。
假设两个Activity:ShareElementActivity 和 ShareElement1Activity。
ShareElementActivity 示例代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_share_element);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setupWindowAnimations();
}
final ImageView shareElement = findViewById(R.id.iv_share_element);
shareElement.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//共享shareElement这个View
ActivityOptionsCompat activityOptionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(ShareElementActivity.this, shareElement,
"shareElement");
ActivityCompat.startActivity(ShareElementActivity.this,
new Intent(ShareElementActivity.this, ShareElement1Activity.class), activityOptionsCompat.toBundle());
}
});
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ActivityCompat.finishAfterTransition(ShareElementActivity.this);
}
});
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void setupWindowAnimations() {
//爆炸效果进入进出
Explode explodeTransition = new Explode();
explodeTransition.setDuration(300);
//排除状态栏
explodeTransition.excludeTarget(android.R.id.statusBarBackground, true);
//是否同时执行
getWindow().setAllowEnterTransitionOverlap(false);
getWindow().setAllowReturnTransitionOverlap(false);
//进入
getWindow().setEnterTransition(explodeTransition);
}
上述代码是从ShareElementActivity到ShareElement1Activity 其中共享的View是
shareElement,transitionName是shareElement,这个transitionName是开启共享动画的重要因素,通过,transitionName 指定的值来匹配下个页面共享的View。
ShareElement1Activity 示例代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_share_element1);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setupWindowAnimations();
}
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ActivityCompat.finishAfterTransition(ShareElement1Activity.this);
}
});
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void setupWindowAnimations() {
ChangeBounds changeBounds = new ChangeBounds();
changeBounds.setDuration(300);
//排除状态栏
changeBounds.excludeTarget(android.R.id.statusBarBackground, true);
//是否同时执行
getWindow().setAllowEnterTransitionOverlap(false);
getWindow().setAllowReturnTransitionOverlap(false);
//进入
getWindow().setEnterTransition(changeBounds);
}
@Override
public void onBackPressed() {
ActivityCompat.finishAfterTransition(ShareElement1Activity.this);
}
可以看到几乎都类似,只不过两个Activity中进入退出动画不一样,需要注意的地方就是返回该页面不能用finish,而是用finishAfterTransition,不然动画不起作用,还有在ShareElement1Activity的布局文件中必须制定共享View的transitionName属性。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".share.ShareElement1Activity">
...
<ImageView
android:id="@+id/iv_share_element1"
...
android:transitionName="shareElement"
tools:targetApi="lollipop" />
...
</RelativeLayout>
如果实现多个共享View动画使用以下伪代码获取ActivityOptions
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this,
Pair.create(view1, "transitionName1"),
Pair.create(view2, "transitionName2"));
当然Activity设置主题属性也可以实现共享动画只需在该Activity主题中配置
<item name="android:windowSharedElementEnterTransition">..</item>
<item name="android:windowSharedElementExitTransition">...</item>
3. 实战
可以看到这张gif图item跳转用到共享元素动画,在第二个详情页面共享动画进入后开启页面内其他元素透明+放大 动画。关闭详情页面依次反向执行开启的流程:详情页面描述和标题还有图片左上角的关闭按钮依次执行透明+缩放动画,等这些操作执行完毕,执行finishAfterTransition,共享元素自会回到之前列表中开启的位置。
实现代码就不贴了,点我查看代码