动画入门和进阶文章列表:
Animation动画概述和执行原理
Android动画之补间动画TweenAnimation
Android动画之逐帧动画FrameAnimation
Android动画之插值器简介和系统默认插值器
Android动画之插值器Interpolator自定义
Android动画之视图动画的缺点和属性动画的引入
Android动画之ValueAnimator用法和自定义估值器
Android动画之ObjectAnimator实现补间动画和ObjectAnimator自定义属性
Android动画之ObjectAnimator中ofXX函数全解析-自定义Property,TypeConverter,TypeEvaluator
Android动画之AnimatorSet联合动画用法
Android动画之LayoutTransition布局动画
Android动画之共享元素动画
Android动画之ViewPropertyAnimator(专用于view的属性动画)
Android动画之Activity切换动画overridePendingTransition实现和Theme Xml方式实现
Android动画之ActivityOptionsCompat概述
Android动画之场景变换Transition动画的使用
Android动画之Transition和TransitionManager使用
Android动画之圆形揭露动画Circular Reveal
1 Transition概述
Transition 在前一篇已经简单讲解了它的使用Android动画之场景变换Transition动画的使用,这次接着讲解Transition的其他用法和TransitionManager。
Transition内部使用了属性动画实现,所以它可以认为是属性动画的封装。Transition两个核心概念为:场景(scenes)和变换(transitions),场景是UI当前状态,变换则定义了在不同场景之间动画变化的过程。所以Transition主要负责两个方面的事,一是保存开始和结束场景的两种状态,二是在两种状态之间创建动画。由于场景记录了内部所有View的开始和结束状态,所以Transition动画更具连贯性。谁执行动画呢?TransitionManager负责执行动画的任务。
2 Scene 场景
Scene 场景 场景过渡动画就是实现View从一种状态变化到另外一种状态,Scene就代表一个场景,它内部保存一个完整地视图结构,从根ViewGroup到所有子view,还有它们的所有状态信息。所以Scene最终就一个设置了不同属性特征的ViewGroup。
Scene构造函数
Scene(ViewGroup sceneRoot)
sceneRoot:根ViewGroup,用于承载Scene view
Scene(ViewGroup sceneRoot, View layout)
参数说明:
sceneRoot:根ViewGroup
layout:view布局
Scene一般利用getSceneForLayout()函数生成Scene场景
Scene.getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context)
参数说明
sceneRoot:根ViewGroup,内部包含多个场景viewGroup。
layoutId:view的布局文件资源id,代表一个场景。
context:上下文
代码示例:
Activity layout
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
tools:context=".Main9Activity">
<FrameLayout
android:id="@+id/viewcontainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</FrameLayout>
</android.support.constraint.ConstraintLayout>
scene1 layout
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageview1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/image_home_game_nor"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="10dp"/>
<ImageView
android:id="@+id/imageview2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/image_home_game_nor2"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="10dp"
app:layout_constraintRight_toRightOf="parent"/>
</android.support.constraint.ConstraintLayout>
scene2 layout 只是图片调换了位置
生成Scene方法一:
Scene scene1 = Scene.getSceneForLayout(mContainer, R.layout.scene1, Main9Activity.this);
Scene scene2 = Scene.getSceneForLayout(mContainer, R.layout.scene2, Main9Activity.this);
除了利用getSceneForLayout外,根据Scene的构造函数,我们还可以传入View直接构造Scene。
生成Scene方法二:
View view1 = LayoutInflater.from(Main9Activity.this).inflate(R.layout.scene1, null);
View view2 = LayoutInflater.from(Main9Activity.this).inflate(R.layout.scene2, null);
Scene scene1 = new Scene(mContainer, view1);
Scene scene2 = new Scene(mContainer, view1);
3 TransitionManager
TransitionManager在场景变换时控制transitions的执行。通过TransitionManager可以添加场景和Transition变换,但为场景变化设置一个默认transitions是没有必要的,因为默认会使用AutoTransition。
TransitionManager当场景变换时开启动画的方式:
beginDelayedTransition(ViewGroup sceneRoot, Transition transition)
beginDelayedTransition(ViewGroup sceneRoot)
场景变幻时传入场景的view根sceneRoot,和transition动画。如果不指定Transition,默认为AutoTransition。
go(Scene scene, Transition transition)
go(Scene scene)
go的方式需要传入scene,scene由Scene利用view生成。如果不指定Transition,则默认为AutoTransition。
Transiton系统效果
继承Transiton类的效果
- ChangeBounds:检测view的位置边界创建移动和缩放动画
- ChangeTransform:检测view的scale和rotation创建缩放和旋转动画
- ChangeClipBounds:检测view的剪切区域的位置边界,和ChangeBounds类似。不过ChangeBounds针对的是view而ChangeClipBounds针对的是view的剪切区域(setClipBound(Rect rect) 中的rect)。如果没有设置则没有动画效果
- ChangeImageTransform:检测ImageView的尺寸,位置以及ScaleType,并创建相应动画。
- Fade,Slide,Explode:根据view的visibility的状态执行渐入渐出,滑动,分解动画。
go 两个场景切换
public class Main9Activity extends AppCompatActivity {
private FrameLayout mContainer;
boolean togger = true;
Scene scene1 ;
Scene scene2 ;
@Override
protected void onCreate(Bundle savedInstanceState) {
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main9);
mContainer = findViewById(R.id.viewcontainer);
mContainer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (togger){
TransitionManager.go(scene1,new ChangeBounds());
}else{
TransitionManager.go(scene2,new ChangeBounds());
}
togger = !togger;
}
});
init();
}
private void init() {
scene1 = Scene.getSceneForLayout(mContainer, R.layout.scene1, Main9Activity.this);
scene2 = Scene.getSceneForLayout(mContainer, R.layout.scene2, Main9Activity.this);
}
如果不指定Transition为ChangeBounds。
mContainer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (togger){
// TransitionManager.go(scene1,new ChangeBounds());
TransitionManager.go(scene1);
}else{
TransitionManager.go(scene2);
}
togger = !togger;
}
});
效果图虽然没有指定Transition但是也有动画效果,其实如果不指定Transition,系统默认的Transition为AutoTransition。
AutoTransition关键源码:
private void init() {
setOrdering(ORDERING_SEQUENTIAL);
addTransition(new Fade(Fade.OUT)).
addTransition(new ChangeBounds()).
addTransition(new Fade(Fade.IN));
}
AutoTransition是个组合变换。
slide效果
TransitionManager.go(scene1,new Slide());
Explode效果
TransitionManager.go(scene1,new Explode());
上面的例子都是对于xml layout 文件做变换,下面我们对已存在sceneRoot中的view做动画
Activity的layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
tools:context=".Main9Activity">
<FrameLayout
android:id="@+id/viewcontainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<FrameLayout
android:id="@+id/viewcontainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_gravity="center"
android:id="@+id/imageview1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/image_home_game_nor"
android:scaleType="matrix"
android:layout_marginTop="10dp"/>
<ImageView
android:layout_gravity="center"
android:id="@+id/imageview2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/image_home_game_nor2"
android:scaleType="matrix"
android:layout_marginTop="10dp"/>
</FrameLayout>
</FrameLayout>
</android.support.constraint.ConstraintLayout>
然后取imageview1生成Scene,
imageView = mContainer.findViewById(R.id.imageview1);
imageView = mContainer.findViewById(R.id.imageview1);
imageView2 = mContainer.findViewById(R.id.imageview2);
scene1 = new Scene(mContainer, imageView);
scene2 = new Scene(mContainer, imageView2);
Container.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (togger){
Transition transition = new Fade();
transition.setDuration(1000);
TransitionManager.go(scene1,transition);
}else{
Transition transition = new Fade();
transition.setDuration(1000);
TransitionManager.go(scene2,transition);
}
togger = !togger;
}
});
运行出错:
Process: com.ldx.canvasdrawdemo, PID: 18825
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child’s parent first.
注意:
利用Scene构造函数生成Scene时,需要sceneRoot,sceneRoot在动画开始时,会将sceneRoot中的所有子View都remove掉,然后在sceneRoot中加载结束场景。通过代码new Scene(mSceneRoot, view)生成Scene,view必须是sceneRoot的直接子view,或者view是没有parentview的,不然在addview的时候会报错。
修改如下:
让图片作为sceneRoot的直接子view。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
tools:context=".Main9Activity">
<FrameLayout
android:id="@+id/viewcontainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<ImageView
android:layout_gravity="center"
android:id="@+id/imageview1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/image_home_game_nor"
android:scaleType="matrix"
android:layout_marginTop="10dp"/>
<ImageView
android:layout_gravity="center"
android:id="@+id/imageview2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/image_home_game_nor2"
android:scaleType="matrix"
android:layout_marginTop="10dp"/>
</FrameLayout>
</android.support.constraint.ConstraintLayout>
mContainer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (togger){
Transition transition = new Fade();
transition.setDuration(1000);
TransitionManager.go(scene1,transition);
}else{
Transition transition = new Fade();
transition.setDuration(1000);
TransitionManager.go(scene2,transition);
}
togger = !togger;
}
});
只对布局中的某些view做动画
scene1和scene2 xml文件类似,只列举scene1 layout 文件
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageview1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/image_home_game_nor"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="10dp"/>
<ImageView
android:id="@+id/imageview2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/image_home_game_nor2"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="10dp"
app:layout_constraintLeft_toLeftOf="parent"
/>
</android.support.constraint.ConstraintLayout>
可以利用Transition的addTarget(),removeTarget(),只对某些view做动画,或者不对某些view做动画。
如果调用了addTarget则只对调用了这个函数的View做动画,其他View直接完成最终状态,如果调用了removeTarget则是对没有调用这个函数的其他view做动画。如果同时调用了两个函数,则调用removeTarget会从调用了addTarget中的view查找,然后剔除。
mContainer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (togger){
Transition transition = new ChangeBounds();
transition.addTarget(R.id.imageview1);
TransitionManager.go(scene1,transition);
}else{
Transition transition = new ChangeBounds();
transition.addTarget(R.id.imageview1);
TransitionManager.go(scene2,transition);
}
togger = !togger;
}
});
只对其中一个ImageView做动画:
可以看到效果图中,有一个ImageView没有动画效果。
beginDelayedTransition(ViewGroup sceneRoot, Transition transition)
TransitionManager.go函数需要生成对应的Scene,beginDelayedTransiton则不需要,只需要填入sceneRoot和Transition就可以实现Transition动画。
这个函数里面没有Scene,那它何时执行动画呢,很简单当view的某些属性信息改变时,就会执行动画,上面介绍go函数时使用了ChangeTransform,ChangeBounds,ChangeClipBounds
,ChangeImageTransform,但感觉他们效果差不多,其实这些属性主要用于beginDelayedTransition这个函数,当对应的属性改变时,会自动触发Transition动画。
执行TransitionManager.beginDelayedTransition后,系统会保存一个当前视图树状态的场景,修改view的属性信息,在下一次绘制时,系统会自动对比之前保存的视图树,然后执行一步动画
重要提醒:如果想让beginDelayedTransition有效果,必须每次改变视图属性之后,重新调用beginDelayedTransition,或者改变之前调用beginDelayedTransition,这样才能够保存当前view的状态,否则存储的属性没有改变,不会有动画效果。
ChangeBounds尺寸改变
mContainer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Transition transition = new ChangeBounds();
transition.setDuration(1000);
TransitionManager.beginDelayedTransition(mContainer,transition);
if (togger){
FrameLayout.LayoutParams layoutParams1 = (FrameLayout.LayoutParams) imageView.getLayoutParams();
layoutParams1.height =100;
layoutParams1.width =100;
imageView.setLayoutParams(layoutParams1);
}else{
FrameLayout.LayoutParams layoutParams2 = (FrameLayout.LayoutParams) imageView.getLayoutParams();
layoutParams2.height = 700;
layoutParams2.width = 700;
imageView.setLayoutParams(layoutParams2);
}
togger = !togger;
}
});
Transition 缩放效果。
ChangeImageTransform
<ImageView
android:layout_gravity="center"
android:id="@+id/imageview1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/image_home_game_nor"
android:scaleType="matrix"
android:layout_marginTop="10dp"/>
ImageView的scaleType 设置为matrix
mContainer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Transition transition = new ChangeImageTransform();
transition.setDuration(1000);
TransitionManager.beginDelayedTransition(mContainer,transition);
if (togger){
imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
}else{
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
}
togger = !togger;
}
});
效果图:
利用matrix缩放旋转ImageView,然后设置Transition,没有发现动画效果,有点疑惑还在看。