Android动画(三)

一、简介

布局动画视作特殊场景下的动画。在ViewGroup的布局发生变换时提供的过渡功能。

二、基于补间动画

ViewGroup首次加载时完成布局的动画。由于使用的是补间动画,因此在设置后,控件位置依旧不变。并且只在ViewGroup第一次layout的时候播放。

在使用补间动画会创建动画方式(移动,透明、缩放、旋转)并写好配置文件(xml或代码),之后调用,start即可。

xml

此处也很像,调用android:layoutAnimation来设置布局动画资源给我们的ViewGroup控件。而layoutAnimation则会调用原始的补间动画来进行二次封装。

布局文件:

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layoutAnimation="@anim/layout_anim">

LayoutAnimation文件:
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="@anim/tran">
</layoutAnimation>

补间动画文件:

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0"
android:toXDelta="30"
android:duration="1000"
android:fillAfter="true">
</translate>

熟悉补间动画对上述不难理解了。中间只是封装了一层,当然他还有一下属性:

  • interpolator:插值器
  • delay:延迟每个子View动画起点的比例值
    如:delay = 1,则每个子View的动画延迟都是delay *duration
  • animationOrder:子View动画的执行顺序,包括normal、reverse(倒置)、random(随机)

代码

以上xml方式也可以使用代码来实现。使用LayoutAnimationController类来完成。最后在View.setLayoutAnimation来设置该配置。

val animation = AnimationUtils.loadAnimation(this, R.anim.tran)
val layoutAnimationController = LayoutAnimationController(animation)
lay.layoutAnimation = layoutAnimationController
layoutAnimationController.start()

该子类GridLayoutAnimationController是专门针对GridLayout的布局动画。

三、基于属性动画

不同与上述的效果,该类即用于被控制的View,也可以作用到其他的子类上(增删改)。
提供了以下几类:
|名称 | 说明|
| ——— | —— |
|APPEARING|View的添加或显示,作用在该新View上|
|DISAPPEARING|View的删除或隐藏,作用在该旧View上|
|CHANGE_APPEARING|View的添加或显示,作用在该其他View上|
|CHANGE_DISAPPEARING|View的删除或隐藏,作用在该其他View上|
|CHANGING|由于布局更改而更改,作用于所有View上,不是由添加到容器中或从容器中删除的项目引起的|

通过android:animateLayoutChanges="true"来开启系统默认的过渡动画效果。

如果需要自定义动画,就需要上述的LayoutTransition,但是需要注意,APPEARING、DISAPPEARINGCHANGE_XXXXX的使用方式不同,后者需要注意很多。

APPEARING & DISAPPEARING

代码如下:

val layoutTransition = LayoutTransition()
// 加入(主)
val objectAnimator = ObjectAnimator.ofFloat(null, "translationX", 0f, 30f, 0f)
layoutTransition.setDuration(LayoutTransition.APPEARING, 300)
layoutTransition.setAnimator(LayoutTransition.APPEARING, objectAnimator)

// 推出(主)
val animatorSet = AnimatorSet()
val objectAnimator1 = ObjectAnimator.ofFloat(null, "scaleX",1f, 0f)
val objectAnimator2 = ObjectAnimator.ofFloat(null, "scaleY", 1f, 0f)
animatorSet.playTogether(objectAnimator1, objectAnimator2)
layoutTransition.setDuration(LayoutTransition.DISAPPEARING, 300)
layoutTransition.setAnimator(LayoutTransition.DISAPPEARING, animatorSet)
lll.layoutTransition = layoutTransition

这里给lll的layout控件添加了两种动画方式,方式也相对简单,将需要添加的动画添加至layoutTransition并添加对应动画模式的时间即可。

CHANGE_XXXXX

这类动画模式,即添加或移除时的其他子控件动画效果。相对注意很多:

使用ObjectAnimator动画:

如果传入的是ObjectAnimator,就需要注意:

1.动画必须由PropertyValuesHolder组建
2.在组建动画时必须添加left、top、right、bottom中的两个(即使不需要这些功能)
3.组建的动画过程起始值必须和结束值相等

val pvhLeft = PropertyValuesHolder.ofInt("left", 0, 0)
val pvhTop = PropertyValuesHolder.ofInt("right", 0, 0)
val properViewHolder = PropertyValuesHolder.ofFloat("rotation", 0f, 360f, 0f)
val objectAnimator4 = ObjectAnimator.ofPropertyValuesHolder(this, properViewHolder, pvhLeft, pvhTop)

如上代码,必须符合上两点要求,否则动画无效。

使用AnimatorSet动画:

相对简单,注意点不多:

1.自动画可以由ObjectAnimator直接定义动画,再组合成AnimatorSet即可
2.组建的动画过程起始值必须和结束值相等

val animatorSet1 = AnimatorSet()
animatorSet1.playTogether(ObjectAnimator.ofFloat(null, "rotation", 0f, 360f, 0f))

CHANGING

指示由于布局更改而更改的项目上运行的动画,这些项目不是由添加到容器中或从容器中删除的项目引起的。
新增View是无法引起CHANGE动画的,只有改变布局大小才能引起动画效果。
CHANGE动画默认关闭,必须调用enableTransitionType(int)方法开启

其他函数

函数名 功能
setDuration(long duration) 设置此过渡对象的所有动画使用的持续时间 设置此过渡使用的其中一个动画对象的持续时间
setDuration(int transitionType, long duration) 设置此过渡使用的其中一个动画对象的持续时间
setInterpolator(int transitionType, TimeInterpolator interpolator) 在此转换使用的动画对象之一上设置插补器
setStagger(int transitionType, long duration) 设置在其中一个更改动画期间开始每个动画之间延迟的时间长度
setStartDelay(int transitionType, long delay) 设置此转换使用的某个动画对象的启动延迟

四、Activity动画

overridePendingTransition

startActivity(android.content.intent)finish()之后立即调用,以指定要执行next的显式转换动画。

// 进入
startActivity(Intent(this, Main6Activity::class.java))
overridePendingTransition(R.anim.alpha_in_anim, R.anim.alpha_out_anim)
// 退出
finish()
overridePendingTransition(R.anim.alpha_in_anim, R.anim.alpha_out_anim)

传入的参数分别为:新Activity进入(旧Activity恢复)和旧Activity隐藏(新Activity隐藏)的两个参数。有点绕口,就是需要显示的Activity和需要隐藏的Activity的两个动画xml资源文件。

而Fragment可以使用transaction.setCustomAnimations(...函数来添加动画。

注意:但是需要注意在设置进入动画xml,但是没哟设置退出动画,则会出现黑屏的情况

overridePendingTransition(R.anim.alpha1_anim,0)

在调试会发现退出的Activity黑屏现象。可以设置一个不动的动画xml设置到退出参数即可。

可以使用AnimatorSet来完成组合动画效果

内置的Activity切换动画

进入 & 退出

提供了除“进入”和“退出”两种情景下的动画以外,还提供了一种共享元素动画
提供了如下功能:

enter:用于决定第一次打开当前Activity时的动画
exit : 用于决定退出当前Activity时的动画
reenter: 用于决定如果当前Activity已经打开过,并且再次打开该Activity时的动画
shared elements:用于决定在两个Activity之间切换时,指定两个Activity中对应的View的过渡效果

进入退出效果:

explode:从屏幕中间进入或退出
slide:从屏幕边缘进入或退出
fade:淡入淡出

如从ActivityA跳至ActivityB
ActivityB代码如下:

override fun onCreate(savedInstanceState: Bundle?) {
// 提示切换需要使用动画
window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS)
// 加载动画
window.enterTransition = Explode()
window.enterTransition = Slide()
window.enterTransition = Fade()

super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)


可以看到加载动画的函数为enterXXXXX,表示用于第一次打开当前Activity时的动画。

Window.FEATURE_CONTENT_TRANSITIONS
Window.FEATURE_ACTIVITY_TRANSITIONS

ActivityA代码如下:

//打开MainActivity
startActivity(Intent(this, MainActivity::class.java), ActivityOptions.makeSceneTransitionAnimation(this).toBundle())

最后使用该方式打开ActivityB。

或者还可以在Styles.xml文件中添加上述这些动画效果:

<item name="android:windowExitTransition">@transition/explode</item>
<item name="android:windowEnterAnimation">@transition/explode</item>
<item name="android:windowReenterTransition">@transition/explode</item>

ActivityOptions & ActivityOptionsCompat

最后在使用了该函数来生成Bundle。他会完成Activity的切换动画,但是功能有限。ActivityOptionsCompat是ActivityOptions的兼容包。

makeCustomAnimation(Context context, int enterResId, int exitResId)

指定进入和退出的Activity动画

enterResId:进入动画
exitResId:退出动画

makeClipRevealAnimation (View source, int startX, int startY, int width, int height)

从一个控件的裁剪区域放大然后打开新的Activity

startX & startY:裁剪的起点
width & height:区域的宽高

makeScaleUpAnimation(View source, int startX, int startY, int width, int height)

放大一个view,然后显示新的Activity

view:指定的控件
startX & startY:指定的起始点
width & height:放大时的起始宽高

makeThumbnailScaleUpAnimation(View source, Bitmap thumbnail, int startX, int startY)

放大一张图片,然后打开activity

source:此缩略图的动画视图。这定义了startX和startY的坐标空间
thumbnail:将显示为动画的初始缩略图的位图
startX & startY:放大时的起始位置

makeSceneTransitionAnimation(Activity activity, View sharedElement, String sharedElementName)

添加一个共享元素动画的元素

makeSceneTransitionAnimation(Activity activity, Pair\ sharedElements…)

添加共享元素动画的多个元素,与上述相同

自定义效果

上述都使用java自带的类来产生效果,当然也可以使用xml文件来给这些效果DIY。
需要声明的xml资源文件目录指定在xml/transition目录下。

xml文件配置

  • explode
  • slide
    • android:slideEdge:边缘的位置
  • fade
    • android:fadingMode:显示还是隐藏
  • transition
    • android:duration:动画时长毫秒
    • android:interpolator:动画使用的插值器
    • android:matchOrder:过渡动画执行顺序
    • android:startDelay:在过渡动画之前延迟时间
  • transitionSet
    • android:transitionOrdering:确认是同时运行,还是顺序运行动画

引用

val explode = TransitionInflater.from(this).inflateTransition(R.transition.slide)
window.enterTransition

这里只需要引用到xml文件即可。

共享元素

进入进出提供了三种效果,而在共享元素则提供了5种效果:

ChangeBounds:改变目标View的边界完成动画
ChangeClipBounds:裁剪目标View的边界
ChangeImageTransform:改变目标图片的大小和缩放比例
ChangeTransform:改变目标View的缩放比例和旋转角度
ChangeScroll:改变滑动位置

同样,共享元素也可以在xml中编写配置文件,并引用。

连接Activity的组件

在ActivityA和ActivityB中的关联组件中添加transitionName="...",相同的名字就会跳到同名下的组件。

startActivity

在开启Activity的时候,将上述的控件对象、设置的名称添加进去即可:
startActivity(Intent, ActivityOptions.makeSceneTransitionAnimation(this, open_two, "shared").toBundle())
也可以添加多个控件:
startActivity(Intent, ActivityOptions.makeSceneTransitionAnimation(this, Pair.create(open_one, "shareds"), Pair.create(open_two, "shared")).toBundle())

finishAfterTransition

在回退Acitivity时调用finishAfterTransition()来退出并显示回退动画。

自定义共享元素动画

使用自定义元素路线来完成自定义共享元素动画。需要一个自定义的路径类:如PatternPathMotionArcMotion等。
然后让组件在设定的路径上完成动画即可。

ArcMotion arcMotion = new ArcMotion();
// 用于描述两点之间的弧的最小角度
arcMotion.setMinimumHorizontalAngle(50f);
arcMotion.setMinimumVerticalAngle(50f);

然后在使用上述的效果ChangeBounds,即可:

// 新建动画效果
val myChangsBounds = ChangeBounds()
// 新建曲线路径
val arcMotion = ArcMotion()
// 设置最小弧
arcMotion.minimumHorizontalAngle = 50f
arcMotion.minimumVerticalAngle = 50f
myChangsBounds.pathMotion = arcMotion
window.sharedElementEnterTransition = myChangsBounds

super.onCreate(savedInstanceState)

TransitionManager

运用Transition框架来完成更完美的场景切换。使用Scene(场景)来创建起始场景和结束场景,TransitionManager负责执行动画任务。

Scene

场景,即保存了View集合的一个状态,从起始到结束即从一个状态到另一个状态。
如何创建Scene:

  • 构造函数Scene
    Scene(ViewGroup sceneRoot, View layout)

    sceneRoot:进入场景的根节点
    layout:指定的一个场景状态(view布局)

如:

val view = LayoutInflater.from(this).inflate(R.layout.layout_two, null, false)
scene2 = Scene(linearlayoutView, view)

即将状态view插入到linearlayoutView根节点中

  • getSceneForLayout()
    Scene.getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context)

    sceneRoot:进入场景的根节点
    layoutId:指定的一个状态
    context:上下文

TransitionManager

TransitionManager用于场景变换时的切换功能,切换的方式默认使用AutoTransition

开启动画

  • beginDelayedTransition()
    当调用方法后,会保存当前根节点下的视图场景,在修改View的属性后,在自动会根据View的变换自动开启动画。
    beginDelayedTransition(ViewGroup sceneRoot, Transition transition)
    beginDelayedTransition(ViewGroup sceneRoot)

    sceneRoot:场景的根节点
    transition:动画方式(默认AutoTransition)

如:

TransitionManager.beginDelayedTransition(layoutView);
if (flag) {
FrameLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) imageView.getLayoutParams();
layoutParams.height =200;
layoutParams.width =200;
imageView.setLayoutParams(layoutParams);
} else {
FrameLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) imageView.getLayoutParams();
layoutParams.height = 700;
layoutParams.width = 200;
imageView.setLayoutParams(layoutParams);
}

flag = !flag;

  • go()
    指定一个结束的Scene场景,来完成动画效果。
    go(Scene scene, Transition transition)
    go(Scene scene)

    scene:指定的结束场景
    transition:动画效果(默认AutoTransition)

Transition效果

以下效果:

Explode,Fade,Slide:根据View的显示和隐藏完成爆炸、透明、滑动效果。
ChangeBounds:改变目标View的边界完成动画
ChangeClipBounds:裁剪目标View的边界
ChangeImageTransform:改变目标图片的大小和缩放比例
ChangeTransform:改变目标View的缩放比例和旋转角度
ChangeScroll:改变滑动位置

也可以使用上述的transitionSet在xml文件(res/transition)中完成混合动画效果。

transition = TransitionInflater.from(this).inflateTransition(R.transition.test_transition);
TransitionManager.go(scene1, transition);

注意:在编写xml文件的时候,上述效果中可以指定layout中的view

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeBounds />
<fade android:fadingMode="fade_in_out">
<targets>
<target android:targetId="@id/transition_title" />
</targets>
</fade>
</transitionSet>

tragetId:指定该id的View完成动画
excludeId:指定除了该id以外的View完成动画

以上在java代码中也可以用实现

只对单纯View完成动画

使用Transition的addTarget()excludeTarget()来完成上述中的功能,即指定Viewid和指定除了Viewid的其他View。如果同时调用两者,则调用excludeTarget()会从addTarget()中的View查找并移除。

四、Circular Reveal

material design设计使App的交互方式更丰富。官方将这一动画称为揭露效果。
可以显示或隐藏一组UI,使过程更加的连续。

ViewAnimationUtils.createCircularReveal()

ViewAnimationUtils.createCircularReveal()为功能的构造函数,返回一个Anmation对象。

形参:

view:动画的区域
centerX:起始点x坐标
centerY:起始点y坐标
startRadius:起始半径
endRadius:结束半径