ObjectAnimator 实现基本动画操作
使得界面的切换更协调
public final class ObjectAnimator extends ValueAnimator {
目录
1. 旋转
// 旋转, 顺时针从-360 到0, 原始状态是0
private fun rotater() {
val animator = ObjectAnimator.ofFloat(star, View.ROTATION, -360f, 0f)
animator.duration = 1000
// 监听动画的开始 和 结束,
// AnimatorListenerAdapter抽象类,实现了AnimatorListener 和AnimatorPauseListener, 覆写需要的即可
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
rotateButton.isEnabled = false
}
override fun onAnimationEnd(animation: Animator?) {
rotateButton.isEnabled = true
}
})
animator.start()
}
监听动画状态可以写成扩展函数,如:
// 写成 ObjectAnimator 的拓展函数, 监听动画的开始和结束,控制控件的点击状态
// AnimatorListenerAdapter抽象类,实现了AnimatorListener 和AnimatorPauseListener, 覆写需要的即可
private fun ObjectAnimator.disableViewDuringAnimation(view: View) {
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
view.isEnabled = false
}
override fun onAnimationEnd(animation: Animator?) {
view.isEnabled = true
}
})
}
2. 移动
// 移动
private fun translater() {
val animator = ObjectAnimator.ofFloat(star, View.TRANSLATION_X, 200f)
animator.repeatCount = 1
animator.repeatMode = ObjectAnimator.REVERSE // 返回到原点
animator.disableViewDuringAnimation(translateButton)
animator.start()
}
3.伸缩
//伸缩
private fun scaler() {
// PropertyValuesHolder 保持动画处理的一些属性
val scaleX = PropertyValuesHolder.ofFloat(View.SCALE_X, 4f)
val scaleY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 4f)
val animator = ObjectAnimator.ofPropertyValuesHolder(star, scaleX, scaleY)
animator.repeatCount = 1
animator.repeatMode = ObjectAnimator.REVERSE
animator.disableViewDuringAnimation(scaleButton)
animator.start()
}
4.淡入、淡出
// 淡入、淡出
private fun fader() {
// 0f 完全透明; 1f 完全不透明
val animator = ObjectAnimator.ofFloat(star, View.ALPHA, 0f)
animator.repeatCount = 1
animator.repeatMode = ObjectAnimator.REVERSE
animator.disableViewDuringAnimation(fadeButton)
animator.start()
}
5.颜色
// 颜色
private fun colorizer() {
// ofArgb 渐变效果比 ofInt 效果好,
var animator = ObjectAnimator.ofArgb(star.parent,
"backgroundColor", Color.BLACK, Color.RED)
animator.duration = 500
animator.repeatCount = 1
animator.repeatMode = ObjectAnimator.REVERSE
animator.disableViewDuringAnimation(colorizeButton)
animator.start()
}
6.组合
private fun shower() {
// 父容器
val container = star.parent as ViewGroup
val containerW = container.width
val containerH = container.height
// 原始星星的大小
var starW: Float = star.width.toFloat()
var starH: Float = star.height.toFloat()
//创建新图片并且添加到父容器中
val newStar = AppCompatImageView(this)
newStar.setImageResource(R.drawable.ic_star)
newStar.layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT
)
container.addView(newStar)
// 新图片的大小(通过伸缩) 和位置(平移)
newStar.scaleX = Math.random().toFloat() * 1.5f + .1f
newStar.scaleY = newStar.scaleX
starW *= newStar.scaleX
starH *= newStar.scaleY
newStar.translationX = Math.random().toFloat() * containerW - starW / 2 //左到右,最左或最右一半显示
// 创建动画器
val mover = ObjectAnimator.ofFloat(newStar, View.TRANSLATION_Y,
-starH, containerH + starH) //从顶部到底部 (刚出现到完全消失)
mover.interpolator = AccelerateInterpolator(1f) //加速插值器 (平移)
val rotator =
ObjectAnimator.ofFloat(newStar, View.ROTATION, (Math.random() * 1080).toFloat())
rotator.interpolator = LinearInterpolator() // 旋转线性插值器
// AnimatorSet 组合使用
val set = AnimatorSet()
set.playTogether(mover, rotator)
set.duration = (Math.random() * 1500 + 500).toLong()
// 动画结束时,移除view
set.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
container.removeView(newStar)
}
})
// 启动动画
set.start()
}
7. 完整代码
ic_star.xml (drawable)
<!--
~ Copyright (C) 2019 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp" android:width="24dp"
android:viewportHeight="806.7" android:viewportWidth="807.1" >
<path android:fillColor="#FFFF00" android:pathData="M403.2,23.7l123.6,250l275.7,39.8l-199.6,194.3l47.4,275.2l-247.1,-130l-246.4,130l47.4,-275.2l-199.6,-194.3l275.7,-39.8z"/>
</vector>
strings.xml
<resources>
<string name="app_name">PropertyAnimation</string>
<string name="rotate">Rotate</string>
<string name="translate">Translate</string>
<string name="scale">Scale</string>
<string name="fade">Fade</string>
<string name="shower">Shower</string>
</resources>
dimens.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="standard_margin">8dp</dimen>
</resources>
colors.xml
<resources>
<color name="primaryColor">#66bb6a</color>
<color name="primaryLightColor">#98ee99</color>
<color name="primaryDarkColor">#338a3e</color>
<color name="primaryTextColor">#000000</color>
<color name="black">#000</color>
</resources>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2019 The Android Open Source Project
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~ http://www.apache.org/licenses/LICENSE-2.0
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:text="@string/rotate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/rotateButton" app:layout_constraintEnd_toStartOf="@+id/translateButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_margin="@dimen/standard_margin" android:textSize="12sp"/>
<Button
android:text="@string/translate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/translateButton" app:layout_constraintEnd_toStartOf="@+id/scaleButton"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/rotateButton"
android:layout_margin="@dimen/standard_margin" android:textSize="12sp"
android:layout_marginStart="14dp" android:layout_marginTop="16dp"/>
<Button
android:text="@string/scale"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/scaleButton"
app:layout_constraintEnd_toStartOf="@+id/fadeButton"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/translateButton"
android:layout_margin="@dimen/standard_margin" android:textSize="12sp"
android:layout_marginStart="15dp" android:layout_marginTop="@dimen/standard_margin"/>
<Button
android:text="@string/fade"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/fadeButton" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/scaleButton"
android:layout_margin="@dimen/standard_margin" android:textSize="12sp"
android:layout_marginTop="@dimen/standard_margin" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="15dp"/>
<Button
android:text="@string/shower"
android:layout_width="0dp"
android:layout_height="wrap_content" android:id="@+id/showerButton"
app:layout_constraintTop_toBottomOf="@+id/rotateButton"
app:layout_constraintStart_toStartOf="@+id/scaleButton"
android:layout_marginEnd="@dimen/standard_margin"
android:layout_marginBottom="@dimen/standard_margin"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:text="Background Color"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="@+id/translateButton"
app:layout_constraintTop_toBottomOf="@+id/rotateButton"
android:id="@+id/colorizeButton"
android:layout_marginStart="@dimen/standard_margin"
android:layout_marginBottom="@dimen/standard_margin"
app:layout_constraintStart_toStartOf="parent"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@color/black"
app:layout_constraintTop_toBottomOf="@+id/showerButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintStart_toStartOf="parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_star"
android:id="@+id/star"
android:layout_gravity="center"/>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.propertyanimation
import android.animation.*
import android.graphics.Color
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateInterpolator
import android.view.animation.LinearInterpolator
import android.widget.Button
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.appcompat.widget.AppCompatImageView
class MainActivity : AppCompatActivity() {
lateinit var star: ImageView
lateinit var rotateButton: Button
lateinit var translateButton: Button
lateinit var scaleButton: Button
lateinit var fadeButton: Button
lateinit var colorizeButton: Button
lateinit var showerButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
star = findViewById(R.id.star)
rotateButton = findViewById<Button>(R.id.rotateButton)
translateButton = findViewById<Button>(R.id.translateButton)
scaleButton = findViewById<Button>(R.id.scaleButton)
fadeButton = findViewById<Button>(R.id.fadeButton)
colorizeButton = findViewById<Button>(R.id.colorizeButton)
showerButton = findViewById<Button>(R.id.showerButton)
rotateButton.setOnClickListener {
rotater()
}
translateButton.setOnClickListener {
translater()
}
scaleButton.setOnClickListener {
scaler()
}
fadeButton.setOnClickListener {
fader()
}
colorizeButton.setOnClickListener {
colorizer()
}
showerButton.setOnClickListener {
shower()
}
}
// 写成 ObjectAnimator 的拓展函数, 监听动画的开始和结束,控制控件的点击状态
// AnimatorListenerAdapter抽象类,实现了AnimatorListener 和AnimatorPauseListener, 覆写需要的即可
private fun ObjectAnimator.disableViewDuringAnimation(view: View) {
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
view.isEnabled = false
}
override fun onAnimationEnd(animation: Animator?) {
view.isEnabled = true
}
})
}
// 旋转, 顺时针从-360 到0, 原始状态是0
private fun rotater() {
val animator = ObjectAnimator.ofFloat(star, View.ROTATION, -360f, 0f)
animator.duration = 1000
// 监听动画的开始 和 结束,
// AnimatorListenerAdapter抽象类,实现了AnimatorListener 和AnimatorPauseListener, 覆写需要的即可
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
rotateButton.isEnabled = false
}
override fun onAnimationEnd(animation: Animator?) {
rotateButton.isEnabled = true
}
})
animator.start()
}
// 移动
private fun translater() {
val animator = ObjectAnimator.ofFloat(star, View.TRANSLATION_X, 200f)
animator.repeatCount = 1
animator.repeatMode = ObjectAnimator.REVERSE // 返回到原点
animator.disableViewDuringAnimation(translateButton)
animator.start()
}
//伸缩
private fun scaler() {
// PropertyValuesHolder 保持动画处理的一些属性
val scaleX = PropertyValuesHolder.ofFloat(View.SCALE_X, 4f)
val scaleY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 4f)
val animator = ObjectAnimator.ofPropertyValuesHolder(star, scaleX, scaleY)
animator.repeatCount = 1
animator.repeatMode = ObjectAnimator.REVERSE
animator.disableViewDuringAnimation(scaleButton)
animator.start()
}
// 淡入、淡出
private fun fader() {
// 0f 完全透明; 1f 完全不透明
val animator = ObjectAnimator.ofFloat(star, View.ALPHA, 0f)
animator.repeatCount = 1
animator.repeatMode = ObjectAnimator.REVERSE
animator.disableViewDuringAnimation(fadeButton)
animator.start()
}
// 颜色
private fun colorizer() {
// ofArgb 渐变效果比 ofInt 效果好,
var animator = ObjectAnimator.ofArgb(
star.parent,
"backgroundColor", Color.BLACK, Color.RED
)
animator.duration = 500
animator.repeatCount = 1
animator.repeatMode = ObjectAnimator.REVERSE
animator.disableViewDuringAnimation(colorizeButton)
animator.start()
}
private fun shower() {
// 父容器
val container = star.parent as ViewGroup
val containerW = container.width
val containerH = container.height
// 原始星星的大小
var starW: Float = star.width.toFloat()
var starH: Float = star.height.toFloat()
//创建新图片并且添加到父容器中
val newStar = AppCompatImageView(this)
newStar.setImageResource(R.drawable.ic_star)
newStar.layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT
)
container.addView(newStar)
// 新图片的大小(通过伸缩) 和位置(平移)
newStar.scaleX = Math.random().toFloat() * 1.5f + .1f
newStar.scaleY = newStar.scaleX
starW *= newStar.scaleX
starH *= newStar.scaleY
newStar.translationX = Math.random().toFloat() * containerW - starW / 2 //左到右,最左或最右一半显示
// 创建动画器
val mover = ObjectAnimator.ofFloat(newStar, View.TRANSLATION_Y,
-starH, containerH + starH) //从顶部到底部 (刚出现到完全消失)
mover.interpolator = AccelerateInterpolator(1f) //加速插值器 (平移)
val rotator =
ObjectAnimator.ofFloat(newStar, View.ROTATION, (Math.random() * 1080).toFloat())
rotator.interpolator = LinearInterpolator() // 旋转线性插值器
// AnimatorSet 组合使用
val set = AnimatorSet()
set.playTogether(mover, rotator)
set.duration = (Math.random() * 1500 + 500).toLong()
// 动画结束时,移除view
set.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
container.removeView(newStar)
}
})
// 启动动画
set.start()
}
}