笔者已将本节的代码上传至 Github,大家可以结合着学习。以下内容是对官方文档的翻译。

当你在使用APP时,如果立即从旧的内容切换到新内容,则很容易让用户感到不适,所以我们需要转场动画来平滑地过渡这种新旧内容的切换过程。

有三种常用的动画适合该场景,他们分别是淡入淡出动画、翻牌动画、揭露动画。

淡入淡出动画

淡入淡出动画顾名思义是在一个 View 或者 ViewGroup 消失时,另外一个View同步显示的动画。本节采用ViewPropertyAnimator实现淡入淡出动画,从Android 3.1 (API level 12)开始支持 ViewPropertyAnimator。

这是一个使用淡入淡出动画的例子。

创建views

首先,你需要创建两个你需要使用淡入淡出动画的 View。下面创建了一个进度指示 View 和一个可滑动的文本 View:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/content"

android:layout_width="match_parent"

android:layout_height="match_parent">

style="?android:textAppearanceMedium"

android:lineSpacingMultiplier="1.2"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="@string/lorem_ipsum"

android:padding="16dp" />

android:id="@+id/loading_spinner"

style="?android:progressBarStyleLarge"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center" />

创建淡入淡出动画

分为3个步骤:

1、创建成员变量以便接下来对其添加动画。

2、对于将要淡入的 View,提前将 visibility 属性设置为GONE。这不仅能够避免该 View 在动画开始之前占用 layout 空间,同时也避免了不必要的 layout 计算

3、预先保存config_shortAnimTime属性值。这个属性值表示标准的短暂动画时长,这个时长是很理想的数值对于频繁使用的动画来说。除此之外,还有config_longAnimTime和config_mediumAnimTime可供选择。

下面的代码使用了之前创建的 layout 作为活动的 content view:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25public class CrossfadeActivity extends Activity {

private View contentView;

private View loadingView;

private int shortAnimationDuration;

...

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_crossfade);

contentView = findViewById(R.id.content);

loadingView = findViewById(R.id.loading_spinner);

// Initially hide the content view.

contentView.setVisibility(View.GONE);

// Retrieve and cache the system's default "short" animation time.

shortAnimationDuration = getResources().getInteger(

android.R.integer.config_shortAnimTime);

}

...

}

添加淡入淡出动画

最后,我们要实现淡入淡出动画还需要如下3个步骤:

1、对于将要淡入的 View,设置它的 alpha 属性为0并且设置 visiblity 为VISIBLE(该 View 之前的 visibility 为GONE)。这一步让该 View 处于可见但完全透明的状态。

2、对将要淡入的view,让它的透明度从0变化到1。对于将要淡出的view,让它的透明度从1到0。

3、在Animator.AnimatorListener的onAnimationEnd()方法中设置淡出view 的 visibility 属性为GONE。注意,虽然该 View 已经完全透明,但是设置属性 visibility 为GONE不仅可以阻止该 View 占用 layout 空间,同时还避免了不必要的 layout 计算

下面是这几步的例子:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24public class CrossfadeActivity extends Activity {

private View contentView;

private View loadingView;

private int shortAnimationDuration;

...

private void crossfade() {

contentView.setAlpha(0f);

contentView.setVisibility(View.VISIBLE);

contentView.animate()

.alpha(1f)

.setDuration(shortAnimationDuration)

.setListener(null);

loadingView.animate()

.alpha(0f)

.setDuration(shortAnimationDuration)

.setListener(new AnimatorListenerAdapter() {

@Override

public void onAnimationEnd(Animator animation) {

loadingView.setVisibility(View.GONE);

}

});

}

}

翻牌动画

该动画适用于在两个 View 之间实现类似翻牌的动效。本节的翻牌动画借助了FragmentTransaction类的setCustomAnimations方法,该类从 Android3.0(API等级11) 开始可以调用。当然,你也可以借助其他的方式实现咯。

创建Animator object

为了创建翻牌动画,你一共需要四个 animators。两个分别控制前面的内容(卡片正面)从左边翻出和从左边翻入。同时需要两个 animators 分别控制后面的内容(卡片反面)从右边翻入和右边翻出。

card_flip_left_in.xml1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

android:valueFrom="1.0"

android:valueTo="0.0"

android:propertyName="alpha"

android:duration="0" />

android:valueFrom="-180"

android:valueTo="0"

android:propertyName="rotationY"

android:interpolator="@android:interpolator/accelerate_decelerate"

android:duration="@integer/card_flip_time_full" />

android:valueFrom="0.0"

android:valueTo="1.0"

android:propertyName="alpha"

android:startOffset="@integer/card_flip_time_half"

android:duration="1" />

card_flip_left_out.xml1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

android:valueFrom="0"

android:valueTo="180"

android:propertyName="rotationY"

android:interpolator="@android:interpolator/accelerate_decelerate"

android:duration="@integer/card_flip_time_full" />

android:valueFrom="1.0"

android:valueTo="0.0"

android:propertyName="alpha"

android:startOffset="@integer/card_flip_time_half"

android:duration="1" />

card_flip_right_in.xml1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

android:valueFrom="1.0"

android:valueTo="0.0"

android:propertyName="alpha"

android:duration="0" />

android:valueFrom="180"

android:valueTo="0"

android:propertyName="rotationY"

android:interpolator="@android:interpolator/accelerate_decelerate"

android:duration="@integer/card_flip_time_full" />

android:valueFrom="0.0"

android:valueTo="1.0"

android:propertyName="alpha"

android:startOffset="@integer/card_flip_time_half"

android:duration="1" />

card_flip_right_out.xml1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

android:valueFrom="0"

android:valueTo="-180"

android:propertyName="rotationY"

android:interpolator="@android:interpolator/accelerate_decelerate"

android:duration="@integer/card_flip_time_full" />

android:valueFrom="1.0"

android:valueTo="0.0"

android:propertyName="alpha"

android:startOffset="@integer/card_flip_time_half"

android:duration="1" />

创建view

卡片的正反面是两个独立的 layout,方便之后将这两个独立的 layout 分别绑定到两个 Fragment 上。下面是这两个独立的 layout 之一:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20<?xml version="1.0" encoding="utf-8"?>

android:layout_width="match_parent"

android:layout_height="match_parent"

android:padding="130dp">

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="#a6c">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerInParent="true"

android:text="FONT"

android:textColor="#FFFFFF"

android:textStyle="bold" />

下面是另一个:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20<?xml version="1.0" encoding="utf-8"?>

android:layout_width="match_parent"

android:layout_height="match_parent"

android:padding="130dp">

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="@color/colorPrimary">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerInParent="true"

android:text="BACK"

android:textColor="#FFFFFF"

android:textStyle="bold" />

创建fragments

创建两个 Fragment 作为卡片的正反面,将之前的两个 layout 分别绑定到这两个 Fragment 上。然后将这两个 Fragment 作为 FragmentActivity 的展示内容,该 Activity 就是你要展示翻牌动画的页面。下面是两个 Fragment 的定义:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24public class CardFlipActivity extends FragmentActivity {

...

/**

* A fragment representing the front of the card.

*/

public class CardFrontFragment extends Fragment {

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container,

Bundle savedInstanceState) {

return inflater.inflate(R.layout.fragment_card_front, container, false);

}

}

/**

* A fragment representing the back of the card.

*/

public class CardBackFragment extends Fragment {

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container,

Bundle savedInstanceState) {

return inflater.inflate(R.layout.fragment_card_back, container, false);

}

}

}

实现动画

现在,你需要在 Activity 中展示这两个 Fragment 的内容。为了实现此需求,你应该为你的 Activity 创建一个 layout。下面的例子在此 layout 中创建了一个FrameLayout作为 Fragment 的容器:1

2

3

4

android:id="@+id/container"

android:layout_width="match_parent"

android:layout_height="match_parent" />

在 Activity 中,将以上的 layout 设置为 content view。然后在 Activity 的oncreate阶段显示卡片的正面内容。下面的例子展示了这一过程:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16public class CardFlipActivity extends FragmentActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_activity_card_flip);

if (savedInstanceState == null) {

getSupportFragmentManager()

.beginTransaction()

.add(R.id.container, new CardFrontFragment())

.commit();

}

}

...

}

现在你已经展示了卡片的正面,接下来要做的就是如何使用翻牌动画翻开卡片的背面,当卡片翻转到背面后,再次将其翻转到正面。下面的代码实现这一功能。1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32public class CardFlipActivity extends FragmentActivity {

...

private void flipCard() {

if (showingBack) {

//Flip to the font.

showingBack = false;

getSupportFragmentManager()

.beginTransaction()

//注意setCustomAnimations()方法必须在add、remove、replace调用之前被设置,否则不起作用。

.setCustomAnimations(

R.animator.card_flip_left_in,

R.animator.card_flip_left_out,

0,

0)

.replace(R.id.flContainer, new CardFrontFragment())

.commit();

return;

}

// Flip to the back.

showingBack = true;

getSupportFragmentManager()

.beginTransaction()

.setCustomAnimations(

R.animator.card_flip_right_in,

R.animator.card_flip_right_out,

0,

0)

.replace(R.id.flContainer, new CardBackFragment())

.commit();

}

最终实现的效果:

揭露动画

当需要显示或者隐藏view时,揭露动画给用户提供了一种视觉上的延续。ViewAnimationUtils.createCircularReveal()方法可以帮助你实现此动画,此方式在 Android 5.0(API level 21) 以上提供。

下面的代码展示了如何使用揭露动画展示初始状态为 invisible 的 View:1

2

3

4

5

6

7

8

9

10

11

12

13// previously invisible view

View myView = findViewById(R.id.my_view);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

int cx = myView.getWidth() / 2;

int cy = myView.getHeight() / 2;

float finalRadius = (float) Math.hypot(cx, cy);

Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0f, finalRadius);

myView.setVisibility(View.VISIBLE);

anim.start();

} else {

// set the view to visible without a circular reveal animation below Lollipop

myView.setVisibility(View.VISIBLE);

}

ViewAnimationUtils.createCircularReveal()动画一共有5个参数。第一个参数表示目标 View。接下来的两个参数代表了揭露动画开始的圆心坐标。一般地,这通常是目标 View的中心点坐标,但是你也可以将它定义为你的手指触摸点的坐标,从而使得揭露动画从你的手指触摸点开始揭露。第四个参数表示动画开始的圆形区域半径。

在上面的例子中,初始的圆形半径为0,从而目标 View 初始状态是隐藏的。最后一个参数代表揭露区域(圆形区域)的最大半径。值得注意的是,最后一个参数必须保证能够完全覆盖你的目标 View。

下面是使用揭露动画隐藏视图的代码:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19// previously visible view

final View myView = findViewById(R.id.my_view);

if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {

int cx = myView.getWidth() / 2;

int cy = myView.getHeight() / 2;

float initialRadius = (float) Math.hypot(cx, cy);

Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0f);

anim.addListener(new AnimatorListenerAdapter() {

@Override

public void onAnimationEnd(Animator animation) {

super.onAnimationEnd(animation);

myView.setVisibility(View.INVISIBLE);

}

});

anim.start();

} else {

// set the view to invisible without a circular reveal animation below Lollipop

myView.setVisibility(View.INVISIBLE);

}

实例中,揭露动画的初始半径足够覆盖整个目标视图,所以初始时的视图是完全可见的。最终的半径设置为0,则表示动画结束后会隐藏目标视图。注意,当动画结束后要将目标视图的 visiblility 属性设置为INVISIBLE以提高性能。

最终效果如下:

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐