相信大家在平常开发也会遇到类似的转场动画,如果想要要实现上图的效果有哪些方式呢?
首先分析一下转场过程,我们把起始 View
分别定义为 startView
和 endView
。startView
为常见的列表布局,左侧头像和右侧为文本介绍;endView
为详情页面,置顶的大图和详细的文本介绍。
不难发现,这些元素都是对应关系,只不过起始状态的基本属性不同:
- 头像,位置和大小以及
scaleType
发生变化 - 背景,颜色、位置和大小发生变化
- 名称,字体大小、颜色和位置发生变化
- 描述,字体大小和位置发生变化
对于此效果,有很多办法可以实现,综合其实现成本和预期效果进行最终选择,我能想到的大概有三种:
- 直接把上述的每个对象看做是独立个体,各自创建独立的动画对象,控制其执行和结束状态。
这种方式,无疑是最简单粗暴的,但是实现和维护起来都很困难,更不容易拓展
-
使用 MotionLayout,不得不说很强大,是 Google 推崇的动画组件,基本不用编写
java
代码就可完成负责的手势和动画,后面有时间会介绍。 -
使用
Transition
,Google 在 Android 5.0 完整引入,虽没有 MotionLayout 那么强大,但是其复用性很强,并且很容易理解,上手也很快。
今天咱们就以下面三个方向并结合对应效果来带大家了解一下 Transition。
- 原生提供的
Transition
类 - 自己实现
Transition
类 - Scene
原生 Transition
准备
核心关键类 TransitionManager
, TransitionManager.beginDelayedTransition(ViewGroup viewGroup, Transition transition);
作为动画的开始,传入需要做转场动画的父布局或根布局,随后改变 View
的相关属性,比如 setVisible()
,便可自动完成转场动画效果。
默认实现的 AutoTransition
,内部集成了基础动画:
private void init() {
setOrdering(ORDERING_SEQUENTIAL);
addTransition(new Fade(Fade.OUT)).
addTransition(new ChangeBounds()).
addTransition(new Fade(Fade.IN));
}
复制代码
Slide、Fade 和 Explode
这三者作为 Visibility
的三个子类,通过控制 view.setVisible()
的方式来达到具体的效果。
Fade,淡出 出场,淡入 入场
Slide,向下离开屏幕出场,向上进入屏幕入场
Explode,四边散开出场,四边汇入入场
同样,可以通过:
Fade fade = new Fade();
Slide slide = new Slide();
TransitionSet set = new TransitionSet();
set.addTransition(fade).addTransition(slide).setOrdering(TransitionSet.ORDERING_TOGETHER);
达到组合的效果:
ChangeBounds
此处开始同一个页面场景的切换,ChangeBounds
当 View 的位置或者大小发生变化时触发对应的转场效果。比如:
ChangeBounds transition = new ChangeBounds();
transition.setInterpolator(new AnticipateInterpolator());
TransitionManager.beginDelayedTransition(mRoot, transition);
ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) view3.getLayoutParams();
if (layoutParams.leftMargin == 400) {
layoutParams.leftMargin = 50;
} else {
layoutParams.leftMargin = 400;
}
view3.setLayoutParams(layoutParams);
最终的效果:
ChangeClipBounds
当调用 view.setClipBounds()
时会触发转场效果:
ChangeClipBounds transition = new ChangeClipBounds();
transition.setInterpolator(new BounceInterpolator());
TransitionManager.beginDelayedTransition(mRoot, transition);
int width = view2.getWidth();
int height = view2.getHeight();
int gap = 140;
Rect rect = new Rect(0, gap, width, height - gap);
if (rect.equals(view2.getClipBounds())) {
view2.setClipBounds(null);
} else {
view2.setClipBounds(rect);
}
最终效果:
ChangeScroll
当调用 view.scrollTo()
会触发转场效果:
ChangeScroll transition = new ChangeScroll();
transition.setInterpolator(new AnticipateOvershootInterpolator());
TransitionManager.beginDelayedTransition(mRoot, transition);
if (view1.getScrollX() == -100 && view1.getScrollY() == -100) {
view1.scrollTo(0, 0);
} else {
view1.scrollTo(-100, -100);
}
最终效果:
ChangeTransform
这个就厉害了,View 的 translation
、scale
和 rotation
发生改变时都会触发:
ChangeTransform transition = new ChangeTransform();
transition.setInterpolator(new OvershootInterpolator());
TransitionManager.beginDelayedTransition(mRoot, transition);
if (view1.getTranslationX() == 100 && view1.getTranslationY() == 100) {
view1.setTranslationX(0);
view1.setTranslationY(0);
} else {
view1.setTranslationX(100);
view1.setTranslationY(100);
}
if (view2.getRotationX() == 30f) {
view2.setRotationX(0);
} else {
view2.setRotationX(30);
}
if (view3.getRotationY() == 30f) {
view3.setRotationY(0);
} else {
view3.setRotationY(30);
}
if (view4.getScaleX() == 0.5f && view4.getScaleY() == 0.5f) {
view4.setScaleX(1f);
view4.setScaleY(1f);
} else {
view4.setScaleX(0.5f);
view4.setScaleY(0.5f);
}
最终效果:
自定义 Transition
介绍
其实 Transition
的原理很简单,大致的逻辑如下:
- 记录当前状态的属性值,比如位置大小或者自定义属性之类
- 创建执行动画,参数为当前值和目标值,根据对应算法来完成动画效果
- 根据目标状态的属性值和记录的缓存属性值,调用创建好的动画对象执行即可
那落实到代码中,首先先集成 Transition
类,会让你实现三个方法:captureStartValues
、captureEndValues
和createAnimator
。
- 定义你关心的属性值;
官方建议属性定义的规则为:package_name:transition_class:property_name
.
比如
private static String PROPNAME_TEXT_COLOR = “xiaweizi:changeTextColor:color”;
我想在文本颜色发生改变时做转场动画,就可以定义上述的属性。
- 记录起始状态的属性;
void captureStartValues(TransitionValues transitionValues)
void captureEndValues(TransitionValues transitionValues);
上述方法分别存储起始状态下对应的属性值:
transitionValues.values.put(PROPNAME_TEXT_COLOR, view.getCurrentTextColor());
- 创建动画;
Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues)
复制代码
参数值的 startValues
和endValues
分别可以拿到你存储的属性值,之后创建动画并返回即可,后续系统会根据你创建的动画进行转场。
是不是很简单,接下来通过几个案例带大家感受一下:
ChangeTextTransition
ChangeTextTransition.java 该类中定义了:
private static String PROPNAME_TEXT = “xiaweizi:changeText:text”;
private static String PROPNAME_TEXT_COLOR = “xiaweizi:changeTextColor:color”;
private static String PROPNAME_TEXT_SIZE = “xiaweizi:changeTextSize:size”;
private static String PROPNAME_TEXT_LEVEL = “xiaweizi:changeTextTypeface:level”;
分别代表文本内容变化、文本颜色变化、文本大小变化和文本字体变化。我们只挑一个文本颜色来看一下动画是如何实现的:
// 记录下起始状态属性值
private void captureValues(TransitionValues transitionValues) {
if (transitionValues == null || !(transitionValues.view instanceof TextView)) return;
TextView view = (TextView) transitionValues.view;
transitionValues.values.put(PROPNAME_TEXT, view.getText());
transitionValues.values.put(PROPNAME_TEXT_COLOR, view.getCurrentTextColor());
transitionValues.values.put(PROPNAME_TEXT_SIZE, view.getTextSize());
transitionValues.values.put(PROPNAME_TEXT_LEVEL, view.getTag(R.id.type_face_level));
}
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues) {
if (startValues == null || endValues == null) {
return null;
}
if (!(endValues.view instanceof TextView)) {
return super.createAnimator(sceneRoot, startValues, endValues);
}
TextView endView = (TextView) endValues.view;
int startTextColor = (int) startValues.values.get(PROPNAME_TEXT_COLOR);
int endTextColor = (int) endValues.values.get(PROPNAME_TEXT_COLOR);
ObjectAnimator animator = ObjectAnimator.ofArgb(endView, new TextColorProperty(), startTextColor, endTextColor);
animator.setDuration(300);
return animator;
}
看一下这四种属性发生变化时的效果:
ChangeBackgroundColorTransition
类似于文本颜色,只不过针对的是 view.setBackground()
,主要的代码在于创建 Animator
:
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues) {
if (startValues == null || endValues == null) {
return null;
}
final View endView = endValues.view;
ColorDrawable startColorDrawable = (ColorDrawable) startValues.values.get(PROPNAME_COLOR);
ColorDrawable endColorDrawable = (ColorDrawable) endValues.values.get(PROPNAME_COLOR);
if (startColorDrawable == null || endColorDrawable == null) return super.createAnimator(sceneRoot, startValues, endValues);
final int startColor = startColorDrawable.getColor();
final int endColor = endColorDrawable.getColor();
ValueAnimator animator = ValueAnimator.ofObject(new ArgbEvaluator(), startColor, endColor);
animator.setDuration(300);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int animatedValue = (int) animation.getAnimatedValue();
endView.setBackgroundColor(animatedValue);
}
});
return animator;
}
最终效果:
ChangeImageResourceTransition
有的时候发现,在切换图片的时候过度会很生硬,那可以通过在对 View
的 alpha
属性从 101 的过程中替换图片,这样显得很平滑。
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues) {
if (startValues == null || endValues == null) {
return null;
}
if (!(endValues.view instanceof ImageView)) {
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
g-q8HyK29T-1715795237676)]
[外链图片转存中…(img-4tDzK1vE-1715795237677)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!