ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Android Activity转场动画从入门到进阶:共享元素、主题动画与性能调优

Android Activity转场动画从入门到进阶:共享元素、主题动画与性能调优 从老项目换到新项目之后我一直觉得App的“质感”差那么点意思。功能都齐全界面也不算丑但用起来就是觉得生硬。后来仔细复盘了一下问题大半出在转场上页面切换就是冷冰冰的“咔”一下直接换屏毫无过渡。Activity转场动画这个东西从来不是“锦上添花”的装饰它直接影响用户对App流畅度和品质感的感知。这篇Blog #158我就把自己在Activity转场动画上从入门到踩坑的完整经验整理出来从基础实现到共享元素动画再到几个容易被忽略的细节一次说透。这篇内容适合刚接触Android开发、想让App交互更细腻的新手也适合已经会用基础转场但想系统梳理原理和坑点的中级开发者。我会把每种实现方式的代码、参数、适用场景和背后的设计逻辑都讲清楚保证你看完能直接用到项目里。1. 转场动画的整体设计思路1.1 为什么要做Activity转场动画不只是“好看”很多开发者觉得转场动画是视觉层面的东西优先级低放到最后再做。我的观点刚好相反转场动画是交互反馈的一部分它承担着“解释页面关系”的任务。用户从列表页点击一条数据进入详情页如果详情页是瞬间硬切出来的大脑会短暂丢失“我从哪来、我在哪”的定位感如果有从左向右的滑动动画用户就能自动建立起“进入了更深一层”的心理模型。从技术角度说Activity转场动画是在两个Activity切换的窗口期对“退出动画”和“进入动画”做编排。操作系统给了我们几个层次的API从最底层的overridePendingTransition到Android 5.0引入的ActivityOptions共享元素动画再到后来推荐的MotionScene动画覆盖了从“简单滑动”到“复杂联动”的完整需求。我个人的项目经验是一个App里70%的转场用基础动画就够了20%的场景需要自定义Animation或Transition剩下10%的“高光时刻”比如从缩略图进入大图详情才值得用共享元素动画。不需要所有页面都炫技那反而会让用户觉得累。关键是把场景分级把动画用在真正需要引导视线的地方。1.2 技术选型四代转场方案怎么选Android的转场动画方案大致经历了四代演进每一代都有它诞生的背景和适用的场景我把它们的核心差异整理了一下方案最低版本要求实现成本动态效果能力适用场景overridePendingTransitionAPI 1所有版本极低只支持进入/退出两个动画通用页面切换最稳妥的兜底方案主题动画windowAnimationStyleAPI 1极低只支持进入/退出两个动画全局统一切换风格ActivityOptions.makeCustomAnimationAPI 16低两个动画可附带颜色/裁剪参数需要精细控制动画参数的场景ActivityOptions.makeSceneTransitionAnimationAPI 21Android 5.0中共享元素过渡支持多个元素图片详情、卡片展开等连续性强的场景MotionSceneMotionLayoutAPI 21需兼容处理高路径动画、关键帧、属性联动复杂页面内动画转场场景较少用选型有一个基本原则在不增加复杂度的前提下满足产品需求。如果你的App只要求“页面切换别太生硬”那overridePendingTransition加主题动画完全够用如果要求“从列表缩略图平滑放大到详情大图”共享元素是唯一合适的选择。不要在只需要基础动画的时候上共享元素它对布局和图片加载时序都有额外约束。1.3 设计转场前先想清楚的三件事动手写代码之前我建议你先花时间想清楚三个问题否则很容易返工。第一转场的触发方向是什么是层级加深列表→详情还是层级变浅详情→列表还是平级切换Tab页之间层级加深适合用从右滑入、当前页从左侧淡出缩小的动画层级变浅适合反过来平级切换适合用淡入淡出或上下滑动。搞反了方向用户会感觉“页面在倒退”。第二动画时长定多少Android官方推荐短动画200ms、中等动画300ms、长动画500ms。但实际项目里普通转场我习惯用250~300ms共享元素动画用300~400ms。低于200ms会感觉不到动画的存在高于500ms用户就会开始不耐烦。另外要留意系统“移除动画”设置项开启时所有转场动画会被禁用这是平台行为不是bug。第三转场期间的视觉空白如何处理如果新页面需要异步加载数据动画结束前页面是白底的观感很差。要么给目标页加过渡占位比如骨架屏要么先把数据准备好再启动转场。我踩过的坑是从相册列表进入大图页大图还在网络加载中共享元素动画已经把缩略图“拉”过去了结果大图位置是一片空白动画效果反而暴露了加载慢的问题。后来加了一个过渡用的本地缓存图问题才解决。2. 基础转场动画的三种实现路径2.1overridePendingTransition最直接也最容易踩坑的方案overridePendingTransition是所有转场方案的老祖宗用法也最简单。在startActivity()或finish()之后调用即可// 从A页面跳转到B页面 Intent intent new Intent(MainActivity.this, DetailActivity.class); startActivity(intent); overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left); // 从B页面返回时在onBackPressed或finish()后调用 Override public void onBackPressed() { super.onBackPressed(); overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right); }这里有个关键点overridePendingTransition必须紧跟在startActivity或finish之后立即调用不能在onResume或onPostResume里调否则动画不会生效。原因是它设置的是“下一次Activity切换时使用的动画”这个一次性flag如果调用时机晚了系统已经完成了切换流程flag就失效了。对应地我需要两个进场动画和两个退场动画。以经典的“从右滑入、向左滑出”为例动画资源如下!-- res/anim/slide_in_right.xml -- ?xml version1.0 encodingutf-8? set xmlns:androidhttp://schemas.android.com/apk/res/android android:interpolatorandroid:interpolator/decelerate_cubic translate android:duration300 android:fromXDelta100%p android:toXDelta0 / /set!-- res/anim/slide_out_left.xml -- ?xml version1.0 encodingutf-8? set xmlns:androidhttp://schemas.android.com/apk/res/android android:interpolatorandroid:interpolator/accelerate_cubic translate android:duration300 android:fromXDelta0 android:toXDelta-100%p / /set注意fromXDelta和toXDelta里的%p后缀它表示相对于父容器宽度的百分比。用100%p表示从屏幕右侧完全进入用-100%p表示向左侧完全移出。如果漏掉%p直接写100那指的是100像素动画效果几乎看不出来。2.2 主题动画一次配置全局生效如果你希望App里所有页面的转场风格一致一个个去调overridePendingTransition显然不现实。更合理的做法是通过主题的windowAnimationStyle统一配置。在styles.xml里自定义一个动画样式style nameAppTheme parentTheme.AppCompat.Light.NoActionBar item nameandroid:windowAnimationStylestyle/AppWindowAnimation/item /style style nameAppWindowAnimation item nameandroid:activityOpenEnterAnimationanim/slide_in_right/item item nameandroid:activityOpenExitAnimationanim/slide_out_left/item item nameandroid:activityCloseEnterAnimationanim/slide_in_left/item item nameandroid:activityCloseExitAnimationanim/slide_out_right/item /style四个item分别对应四种切换场景activityOpenEnterAnimation新Activity进入时的动画。activityOpenExitAnimation旧Activity在“打开新页面”时退出的动画。activityCloseEnterAnimation关闭当前Activity时下层Activity重新出现时的动画。activityCloseExitAnimation当前Activity关闭时自身的动画。这种方式的好处是全局自动生效不需要改任何Java/Kotlin代码。但它也有明显的缺点一旦某个页面需要特殊的转场效果就得单独为它指定主题或在代码里调用overridePendingTransition覆盖掉而且主题动画不能直接支持共享元素动画那种“元素跟随”的效果。我的建议是如果你的App页面层级模型很统一大部分App都是用主题动画作为默认方案再在个别页面用代码覆盖。这样既省代码量又能保留灵活性。2.3ActivityOptions基础动画的参数化和扩展除了上面两种“玩法”ActivityOptions提供了更现代、更灵活的基础动画方式。它在API 16引入也是后续共享元素动画的入口。Bundle options ActivityOptions.makeCustomAnimation( this, R.anim.slide_in_right, R.anim.slide_out_left ).toBundle(); startActivity(intent, options);这跟overridePendingTransition的效果是一样的但传参方式不同而且ActivityOptions还有两个很实用的扩展makeClipRevealAnimation裁剪揭示动画和makeScaleUpAnimation从指定点放大。其中makeClipRevealAnimation的效果很适合“从某个按钮展开新页面”的场景它从一个中心点向外裁剪展开// 从view的中心点展开新页面 int[] location new int[2]; view.getLocationOnScreen(location); int centerX location[0] view.getWidth() / 2; int centerY location[1] view.getHeight() / 2; Bundle options ActivityOptions.makeClipRevealAnimation( view, centerX, centerY, 0, 0, view.getWidth(), view.getHeight() ).toBundle(); startActivity(intent, options);这种动画不会像全屏滑动那样“打断”用户的视觉焦点而是从被点击的控件出发向外揭示新页面非常自然。缺点是Android 5.0以下不支持但可以用makeCustomAnimation做降级。2.4 用手势返回时的转场兼容当前端架构相关的补充如果你在用predictive back gestureAndroid 13的预测性返回手势基础转场动画会被系统拦截并转换为系统级的返回动画这在很多国产ROM上表现不一致。我的处理经验是对OnBackPressedDispatcher回调做一层封装在动画关闭的开关里判断版本和厂商必要时通过overridePendingTransition重新施加关闭动画避免返回手势导致的动画丢失。这里涉及编译SDK版本升级和兼容性测试工作量大但必须做否则用户用全面屏手势返回时动画会突然消失非常出戏。3. 共享元素转场让页面切换“跟着视觉焦点走”3.1 共享元素动画的原理与适用场景共享元素动画Shared Element Transition是我个人最喜欢的转场方案。它的核心思想是在两个页面之间找到“同一个元素”然后让这个元素在两个页面之间平滑变换位置、大小、形状而其他内容则用淡入淡出等动画过渡。最典型的场景是首页有一个商品缩略图网格点击某张图片进入详情页的大图。传统转场是“整个页面滑过去”共享元素转场是“缩略图在屏幕上放大变成详情大图”视觉上用户会感觉图片是连续的点击的“手感”特别跟手。Android实现共享元素动画的要素有三个源页面和目标页面的共享元素必须设置相同的transitionName。启动页面时使用ActivityOptions.makeSceneTransitionAnimation。两个页面的主题需要设置android:windowContentTransitions为true默认就是true并确保windowSharedElementsUseOverlay等配置没有被关掉。3.2 完整实现步骤图片缩略图到大图的场景以“图片列表→大图详情”为例我一步步说明完整实现。第一步源页面列表页的图片设置transitionNameImageView android:idid/iv_thumb android:layout_width100dp android:layout_height100dp android:scaleTypecenterCrop android:transitionNameshared_image /第二步目标页面详情页的图片也设置相同的transitionNameImageView android:idid/iv_large android:layout_widthmatch_parent android:layout_height300dp android:scaleTypecenterCrop android:transitionNameshared_image /第三步在列表页点击时Intent intent new Intent(MainActivity.this, DetailActivity.class); ActivityOptions options ActivityOptions.makeSceneTransitionAnimation( MainActivity.this, ivThumb, // 源页面共享元素 shared_image // 与目标页面相同的transitionName ); startActivity(intent, options.toBundle());这就是最基础的用法。如果多个共享元素同时参与转场可以用Pair.create创建多个ActivityOptions options ActivityOptions.makeSceneTransitionAnimation( MainActivity.this, Pair.create(ivThumb, shared_image), Pair.create(tvTitle, shared_title) );这里有一个容易踩的坑两个共享元素的transitionName必须全局唯一并且目标页面里不要使用android:transitionName的重复值。如果同一个页面里有多个控件使用相同的transitionName系统会抛出IllegalArgumentException动画直接崩溃。3.3 共享元素动画的三个关键细节导致动画断层的元凶共享元素动画看起来简单实际用起来容易在三个细节上翻车。第一图片加载时序。如果源页面的缩略图已经加载完成而目标页面的大图还在异步加载中共享元素动画在执行时会发生“视觉闪白”缩略图被拉伸成大图尺寸但目标ImageView的内容是空的看起来就像图片消失了一下。解决办法是要么提前将大图缓存在内存最简单的方式是通过图片加载库的diskCacheStrategy或memoryCache预加载要么在动画执行前先用缩略图临时占位。第二目标页面布局未就绪。共享元素动画需要目标页面已经完成布局测量后才能正确计算起始和结束的位置。如果你在onCreate里直接启动动画偶尔会碰到元素从“0,0”坐标飞入的诡异效果。稳妥的做法是在目标页面根布局的addOnLayoutChangeListener里再启动入场动画或者在onWindowFocusChanged里触发确保布局已经算完。第三根布局背景的影响。目标页面的根布局如果设了不透明的纯色背景共享元素动画执行时新页面会以“整块不透明背景”的形式盖上来视觉上就成了“一块色板飞过来”而不是“图片平滑放大”。解决方法是把根布局背景设为透明或用windowSharedElementsUseOverlay控制覆盖层。3.4 自定义Transition进阶版的过渡控制如果你觉得系统默认的ChangeImageTransform、ChangeBounds效果不够还可以自定义Transition。比如我想做一个“圆角卡片展开为详情页”的效果就需要自己处理圆角变化的平滑过渡。自定义Transition的核心是继承Transition类重写captureStartValues和captureEndValues分别捕获起始和目标状态public class CircularRevealTransition extends Transition { private static final String PROPNAME_RADIUS transform_radius; Override public void captureStartValues(TransitionValues transitionValues) { View view transitionValues.view; transitionValues.values.put(PROPNAME_RADIUS, view.getTag(R.id.radius_tag)); } Override public void captureEndValues(TransitionValues transitionValues) { View view transitionValues.view; transitionValues.values.put(PROPNAME_RADIUS, view.getTag(R.id.radius_tag)); } Override public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { if (startValues null || endValues null) return null; int startRadius (int) startValues.values.get(PROPNAME_RADIUS); int endRadius (int) endValues.values.get(PROPNAME_RADIUS); // 返回一个ValueAnimator动态改变View的圆角 View view endValues.view; return ObjectAnimator.ofFloat(...); } }这种自定义方式灵活性很高但复杂度也直线上升。我的建议是在动手写之前先确认系统自带的Transition组合是否满足需求ChangeBounds处理位置和尺寸、ChangeTransform处理缩放和旋转、ChangeImageTransform处理ImageView的scaleType变化。大部分场景用这三个的组合就够了。4. 转场动画的高阶细节与特殊情况4.1 页面禁止截屏时的动画处理开发中经常会遇到一个需求某些页面比如支付页面、订单详情页不允许用户截屏或录屏。系统提供的方法是给Window设置FLAG_SECUREgetWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);但很多开发者不知道的是FLAG_SECURE开启后共享元素转场动画会失效或出现黑屏。原因是系统为了安全不会把当前窗口内容作为动画的“截图”传给下一帧而共享元素转场在底层依赖对窗口内容的捕捉和绘制。我遇到过的现象是列表正常显示时点击图片FLAG_SECURE的详情页打开共享元素动画执行到一半源页面的内容从动画中消失变成一片漆黑。排查之后发现这就是系统安全策略和转场机制冲突导致的。这类场景我摸索出的方案是放弃共享元素转场改用普通的淡入淡出动画并在源页面点击瞬间用一个“纯色遮罩”过渡。虽然少了图片连续放大的视觉爽感但至少不会出现黑屏这种致命体验问题。如果产品经理坚持要共享元素动画那就得接受目标页面内容可以截屏的合规风险这个决定必须让产品/法务拍板不能由开发自己承担。顺带一提微信等超级App在特定业务页比如支付、小程序页面也大量使用了这类安全策略。能看到这类页面在转场动画上做了“妥协”或者有专门的动画替代方案说明这是一个非常贴近真实业务的需求。4.2 系统异常活动检测对转场的影响还有一个比较“隐晦”的问题部分定制ROM和系统安全组件会在后台做“异常行为检测”比如系统检测到某种自动化的页面跳转它们为了识别页面是否由用户真实点击触发会主动监控Activity切换的频次和方式。正常情况下这不影响转场动画。但如果你的App里有自动化测试脚本、无障碍服务批量操作、或者高频的startActivity调用比如倒计时自动跳页系统可能把这种行为识别为异常活动然后强制禁用或修改动画效果甚至直接拦截页面跳转。我处理过的真实案例是一个活动页有倒计时跳转逻辑倒计时结束后自动startActivity到下一页面同时带有转场动画。在部分测试机上这个自动跳转的转场动画时有时无最后发现是系统把无人值守的自动跳转识别为风险裁掉了动画表现。解决办法是在配置里把这类自动跳转改为由用户点击事件触发或者在跳转前模拟一次用户手势事件。从合规角度讲后者需要更谨慎因为绕过系统的安全检测并不是正路但如果确实是产品功能需要的自动续页优先考虑给用户一个明确的倒计时按钮而不是直接跳转才是体验和安全兼顾的方案。4.3 深色模式与转场动画的适配现在很多App都适配了深色模式这也是转场动画容易翻车的场景。基础动画滑动、淡入淡出对深色模式不敏感但共享元素动画一旦涉及背景透明度和颜色插值就会出现“浅色背景闪一下”的尴尬。我遇到过的情况是深色模式下详情页背景是深色的从白色列表页切换到深色详情页时由于共享元素动画的过渡层是透明的在动画执行的中途系统会用默认的windowBackground填充背景而默认背景在深色模式下如果没有单独配置就会闪一下浅色。解决思路有两个给详情页单独设置深色模式的android:windowBackground确保windowBackground和页面主背景一致。在共享元素动画的Transition里把背景颜色的插值也纳入动画范围让背景色跟随进度一起过渡。更省事的方案是用ChangeBackgroundColor这个系统Transition它可以直接处理背景色的平滑变化。但如果你的页面背景有渐变、图片或其他复杂元素就得自己写过渡逻辑了。4.4 动画性能与掉帧的检查思路转场动画掉帧是初级开发者容易忽略、却会直接影响体验的问题。判断掉帧最直接的方法是开启系统的“GPU呈现模式分析”开发者选项里的Profile GPU rendering观察转场期间柱状图是否长时间超过绿线。如果掉帧优先排查这三个方向第一布局复杂度过高。转场动画期间系统需要同时测量和绘制两个页面如果每个页面的View层级很深、嵌套了很多LinearLayout动画期间的绘制成本会成倍增加。用Layout Inspector看看层级尽量用ConstraintLayout扁平化布局。第二动画属性选择不当。在动画中操作layoutParams宽度、高度、边距会触发requestLayout这是性能杀手。应该操作translationX/Y、scaleX/Y、alpha等属性它们只触发绘制和合成不触发测量。第三主线程执行了耗时操作。最简单的验证方式是看转场期间是否有大量GC日志输出。图片解码、JSON解析这类耗时操作如果在转场时同步执行必然掉帧。把耗时操作放到子线程或提前预加载。5. 常见问题与排查技巧实录5.1 转场动画不生效的5个自查方向这是我被问得最多的问题也是我自己最开始频繁踩的坑。动画不生效90%是下面五个原因之一建议按顺序排查序号检查项说明1overridePendingTransition调用时机是否紧跟在startActivity/finish之后是否在onResume里误调用了2系统“移除动画”开关开发者选项里的“动画程序时长缩放”或“移除动画”是否设为了03主题是否覆盖了动画样式目标Activity的主题是否设置了自定义windowAnimationStyle把默认动画覆盖成“无”了4FLAG_SECURE是否开启安全窗口内容不参与部分动画会导致转场失效或黑屏。5动画资源文件语法错误%p是否写成了无后缀数字Interpolator是否引用错误索引文件引用是否正确5.2 共享元素动画的经典崩溃与修复共享元素最经典的崩溃就是IllegalArgumentException: Wrong transitionName或Unable to find transitionName。这类崩溃在列表页和详情页的transitionName不一致时必现。还有一个很隐蔽的崩溃是源页面有多个共享元素其中有一个在目标页面找不到对应的transitionName整个转场会直接失败甚至出现未捕获异常。我的习惯是写一个工具方法在启动前检查目标页面是否包含所有需要匹配的transitionName如果缺失就降级到基础转场避免崩溃。工具方法的思路如下伪代码private boolean checkTransitionNames(Context context, Bundle options, String[] names) { // 在实际项目里可以用反射或维护一个静态映射来校验 // 简单场景下可以直接让两端定义常量从互相引用的地方保证一致性 }更工程化的做法是把共享元素的transitionName定义成一个常量类两个页面都引用同一个常量从源头杜绝拼写不一致的问题。5.3 转场动画结束后偶发白屏这个问题的现象是转场动画播放完成但新页面显示的是白屏或黑屏过一会儿内容才出现。排查思路基本围绕“内容加载太慢”和“过渡层未正确销毁”两方面。如果是内容加载慢需要优先优化异步数据。如果是过渡层未销毁可以手动处理onTransitionEnd回调在动画结束后强制触发一次requestLayout或重绘Transition transition window.getSharedElementEnterTransition(); if (transition ! null) { transition.addListener(new Transition.TransitionListener() { Override public void onTransitionEnd(Transition transition) { View root findViewById(android.R.id.content); root.requestLayout(); root.invalidate(); } // 其他回调方法省略 }); }注意这个方案只能作为兜底如果白屏的原因是图片解码在主线程卡顿强制重绘只会让卡顿更难堪。还是得回到性能优化本身。5.4 用MotionEditor或MotionScene做复杂转场的建议如果你要做的转场动画涉及路径运动、多属性联动、手势驱动标准API可能不太够用。这种情况下可以考虑MotionLayout它是ConstraintLayout的子类通过MotionScene文件描述动画路径和属性。MotionScene的典型结构是定义ConstraintSet起始和结束状态和Transition动画动作与触发方式MotionScene xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto Transition app:constraintSetStartid/start_set app:constraintSetEndid/end_set app:duration400 OnSwipe app:touchAnchorIdid/image app:touchAnchorSideright app:dragDirectiondragLeft / /Transition ConstraintSet android:idid/start_set !-- 起始状态的约束 -- /ConstraintSet ConstraintSet android:idid/end_set !-- 结束状态的约束 -- /ConstraintSet /MotionSceneMotionScene用于Activity转场时通常是“单个Activity 多个Fragment”的架构通过Fragment事务配合MotionLayout实现更细腻的页面切换。它也能配合SharedElementCallback做复杂动画但实现成本确实高。如果你只是做常规的页面跳转没必要引入这层复杂度如果你是做那种“页面级交互动效”的产品MotionEditor的可视化编辑和实时预览能节省大量调参时间。6. 转场动画的性能取舍与工程化落地6.1 动画预算管理真实项目的取舍经验转场动画最容易被忽略的问题是“动画预算”。我这里说的预算不是CPU/GPU的帧率预算而是产品层面的“动画数量预算”一个页面启动过程最多承载几个动画事件。我见过一个反面案例详情页启动时同时做了共享元素图片放大、标题FromTranslationY滑入、底部按钮缩放、背景色渐变四个动画一起跑。结果就是视觉非常忙乱用户根本不知道眼睛该往哪放而且容易出现掉帧。动画设计里有“主次”概念一次转场通常只需要一到两个视觉焦点如果有图片共享元素动画其他元素就做简单的淡入淡出不要抢焦点。如果没有共享元素可以做一个“标题上移淡入”的重点动画其他内容跟随淡入即可。动画开始时序尽量错开通常用AnimatorSet或TransitionSet的setStartDelay做延迟编排让元素依序出现而不是同时全动。6.2 把转场动画沉淀成统一组件在真实项目中转场动画不应该散落在各个Activity的代码里应该沉淀成统一组件或工具类。我通常会在项目里维护一个TransitionHelper集中管理所有转场类型public class TransitionHelper { public static void startWithFade(Activity current, Class? target) { Intent intent new Intent(current, target); Bundle options ActivityOptions.makeCustomAnimation( current, R.anim.fade_in, R.anim.fade_out ).toBundle(); current.startActivity(intent, options); } public static void startWithSharedElement(Activity current, View sharedView, String transitionName, Class? target) { Intent intent new Intent(current, target); ActivityOptions options ActivityOptions.makeSceneTransitionAnimation( current, sharedView, transitionName ); current.startActivity(intent, options.toBundle()); } // 其他方法...... }这样做的意义有三个一是统一了动画调用的入口风格一致二是后续如果要整体替换动画风格只改一个类就行三是可以集中处理兼容性逻辑比如API level判断、降级策略。6.3 转场动画的测试建议最后聊聊测试。转场动画是那种“真机效果和模拟器效果差很多”的功能尤其是共享元素动画受设备性能、屏幕分辨率、图片解码速度的影响非常大。我的测试建议是至少用一台低端真机骁龙600系列或更低测试动画流畅度模拟器看不出真实性能问题。分别在大屏平板、小屏5寸左右、异形屏刘海屏、挖孔屏上检查动画是否被系统UI遮挡。开启开发者选项里的“动画程序时长缩放”为0.5x和0x验证动画关闭状态下App功能是否完全正常。深度测试系统返回手势和底部导航手势与转场动画的交互这在Android 10上尤其重要。顺便提一个运营向的细节如果App有活动页比如“subpackages/activity”这类业务页面这类页面内部的加载逻辑可能非常复杂图片多、接口多转场动画加载期间特别容易触发卡顿。活动页的转场我通常建议“轻量化”用最基础的淡入淡出把宝贵的计算资源留给页面内容本身。这也解释了为什么大厂App的活动页反而转场动画最朴素——不是没能力做是做复杂了会影响活动页自身的内容加载性能。7. 我踩过的一些坑给后来者的真心话这些东西教科书上不会写只有真实项目里踩过坑才知道。第一动画文件复用要谨慎。很多开发者习惯共用一个slide_in_right.xml但不同页面切换的时长、插值器需求其实不一样。列表页滚动速度快详情页希望更舒缓这时候共用一个动画就会显得手感不对。我后来是把动画按场景拆成多个文件命名规范统一管理虽然文件多了但维护体验反而更好。第二不要用Thread.sleep来“让动画多跑一会儿”。有人为了让转场动画和某些逻辑时序对齐在Activity切换之间加延迟这是极其糟糕的做法。它会卡主线程导致启动画面直接黑屏或卡顿。如果你确实需要延迟用Handler.postDelayed或View.postDelayed无论如何不要阻塞UI线程。第三交互手势的冲突要提前排查。如果你的页面里已经有SwipeRefreshLayout、ViewPager2或自定义的左右滑动关闭手势转场动画的方向最好和这些手势的方向错开。否则用户会在下拉刷新时不小心触发右滑转场体验非常割裂。我在一个项目里就遇到过页面支持“从右边缘左滑返回”的交互但又设置了“从右往左滑入”的转场动画结果每次启动新页面动画方向都和返回手势方向一致用户总感觉“页面又回来了”。第四动画结束后的状态一定要检查。转场动画执行完不代表页面状态正确。共享元素动画结束后ImageView的scaleType和位置必须和目标布局要求一致。我遇到过动画结束但图片变形的情况最后查到是ChangeImageTransform和自定义的scaleType冲突。动画结束后重置视图状态是一个值得养成的调试习惯。第五线上问题的监控要主动。转场动画的崩溃和卡顿这类问题在测试阶段不一定暴露到了用户设备上才显现。建议在线上打点统计Activity切换耗时和动画异常捕获尤其是共享元素动画相关的崩溃。我目前的做法是接入Android系统的jankStats库统计转场期间是否有卡顿帧以及在崩溃日志里单独标记转场相关的异常类型方便线上排查。8. 结尾的小建议这篇Blog #158写到这里核心内容基本覆盖了Activity转场动画的完整路径从overridePendingTransition到主题动画从ActivityOptions到共享元素再到自定义Transition和MotionScene以及一堆真实项目里才会踩到的坑点。最后分享一个我总结的经验做转场动画时先想清楚用户需要“理解”什么而不是先想“炫技”什么。页面加深用右滑入自然页面关闭用左滑出自然图片连续性场景用共享元素自然活动页用轻量动画克制但有诚意。自然和克制比花哨更重要。我一直会在项目的动画统一组件里加一行注释——“如果这个动画让用户注意到了动画本身那它就已经失败了。”希望这篇内容能帮你在Activity转场动画这条路上少走一些弯路。如果你在实现过程中遇到其他奇怪的问题欢迎交流我这几年攒了不少奇奇怪怪的适配经验也许恰好能帮上忙。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表