Share
Embed
Android Animation 10% 더 활용하기
Android Platform의 Animation을 모두 알아본다.
- Drawable Animation
- Animator
- Interpolator
- ViewPropertyAnimator
- View Animation
- DynamicAnimation
- Transition
Sungyong An
September 19, 2019
Transcript
272 slides
-
animation 10% • "a method in which pictures are manipulated
to appear as moving images." Animation Link: https://en.wikipedia.org/wiki/Animation -
animation 10% • Drawable Animation • View Animation • Animator
• ViewPropertyAnimator • StateListAnimator • CircularReveal animation • Interpolator • DynamicAnimation • Transition • Shared Elements • MotionLayout • 3rd party Library (ex. Lottie) ❌ ❌ -
animation 10% • res/drawable/ic_battery_anim.xml AnimationDrawable <animation-list android:oneshot="false"> <item android:duration="200" android:drawable="@drawable/ic_battery_20"
/> <item android:duration="200" android:drawable="@drawable/ic_battery_30" /> ... </animation-list> -
animation 10% • res/drawable/ic_battery_anim.xml AnimationDrawable <animation-list android:oneshot="false"> <item android:duration="200" android:drawable="@drawable/ic_battery_20"
/> <item android:duration="200" android:drawable="@drawable/ic_battery_30" /> ... </animation-list> -
animation 10% • res/drawable/ic_battery_anim.xml AnimationDrawable <animation-list android:oneshot="false"> <item android:duration="200" android:drawable="@drawable/ic_battery_20"
/> <item android:duration="200" android:drawable="@drawable/ic_battery_30" /> ... </animation-list> -
animation 10% AnimationDrawable // AnimationDrawable.java private void setFrame( int frame,
boolean unschedule, boolean animate) { ... selectDrawable(frame); ... scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]); } -
animation 10% AnimationDrawable // AnimationDrawable.java private void setFrame( int frame,
boolean unschedule, boolean animate) { ... selectDrawable(frame); ... scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]); } -
animation 10% AnimationDrawable // AnimationDrawable.java private void setFrame( int frame,
boolean unschedule, boolean animate) { ... selectDrawable(frame); ... scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]); } -
animation 10% • ױੌ Animationী ৈ۞ ܳ ࢎਊೠ. •
ೞ݅, • ࠗ٘۞ Animationਸ ࠁৈ۰ݶ ݆ о ਃೞ. (60fps) • ܳ ৈ۞ ࢎਊೞݶ ݫݽܻ ޙઁо ѣػ. (OOM) ױࣽೠ Frame Animation AnimationDrawable -
animation 10% Drawable Animation A N D R O I
D animation +10% -
animation 10% • AnimationDrawable • AnimatedVectorDrawable • AnimatedStateListDrawable • AnimatedRotateDrawable
• AnimatedImageDrawable Animationਸ ਗೞח Drawable Drawable Animation -
animation 10% • AnimationDrawable • AnimatedVectorDrawable • AnimatedStateListDrawable • AnimatedRotateDrawable
• AnimatedImageDrawable Animationਸ ਗೞח Drawable Drawable Animation -
animation 10% DrawableInflater Link: http://androidxref.com/9.0.0_r3/xref/frameworks/base/graphics/java/android/ graphics/drawable/DrawableInflater.java#148 • res/drawable/{file_name}.xml <animated-selector />
<ripple /> <animated-vector /> <rotate /> <animated-rotate /> <animation-list /> <animated-image /> ... -> AnimatedStateListDrawable -> RippleDrawable -> AnimatedVectorDrawable -> RotateDrawable -> AnimatedRotateDrawable -> AnimationDrawable -> AnimatedImageDrawable ... -
animation 10% • ੌ߈ਵ۽ Drawable Animation द/݅ оמೞ. Animatable android.graphics.drawable
public interface Animatable { void start(); void stop(); boolean isRunning(); } -
animation 10% • Notification SmallIcon 24 x 24 ӝܳ ӂೠ.
• ਗೞח Drawable ഋध: • AnimationDrawable • AnimatedVectorDrawable • ex) ੌ ۽٘ ੌ ٸ, ח ই द ঌܿ ইী Animation द Animated Notification Icon Link: ExpandableNotificationRow.java#392 API 21 -
animation 10% • res/drawable/stat_sys_download.xml Animated Notification Icon <animation-list android:oneshot="false"> <item
android:drawable="@drawable/stat_sys_download_anim0" android:duration="200" /> <item android:drawable="@drawable/stat_sys_download_anim1" android:duration="200" /> ... </animation-list> -
animation 10% • res/drawable/stat_sys_download.xml Animated Notification Icon <animation-list android:oneshot="false"> <item
android:drawable="@drawable/stat_sys_download_anim0" android:duration="200" /> <item android:drawable="@drawable/stat_sys_download_anim1" android:duration="200" /> ... </animation-list> -
animation 10% AnimatedStateListDrawable <selector> <item android:id="@+id/selected" android:drawable="@drawable/ic_battery_100" android:state_checked="true" /> <item
android:id="@+id/unselected" android:drawable="@drawable/ic_battery_0" android:state_checked="false" /> </selector> API 21 -
animation 10% <animated-selector> <item android:id="@+id/selected" android:drawable="@drawable/ic_battery_100" android:state_checked="true" /> <item android:id="@+id/unselected"
android:drawable="@drawable/ic_battery_0" android:state_checked="false" /> </animated-selector> AnimatedStateListDrawable API 21 -
animation 10% android:id="@+id/selected" android:drawable="@drawable/ic_battery_100" android:state_checked="true" /> <item android:id="@+id/unselected" android:drawable="@drawable/ic_battery_0" android:state_checked="false"
/> <transition android:drawable="@drawable/ad_battery_select" android:fromId="@id/unselected" android:toId="@id/selected" /> <transition android:drawable="@drawable/ad_battery_unselect" android:fromId="@id/selected" android:toId="@id/unselected" /> </animated-selector> AnimatedStateListDrawable API 21 -
animation 10% <!-- res/drawable/ad_battery_select.xml --> <animation-list android:oneshot="true"> <item android:drawable="@drawable/ic_battery_0" android:duration="32"
/> <item android:drawable="@drawable/ic_battery_20" android:duration="32" /> ... </animation-list> AnimatedStateListDrawable API 21 -
animation 10% + AnimatedVectorDrawable <selector> <item android:id="@+id/selected" android:drawable="@drawable/ic_nav_agenda" android:state_checked="true" />
<item android:id="@+id/unselected" android:drawable="@drawable/ic_nav_agenda" android:state_checked="false" /> </selector> API 21 -
animation 10% <selector> <item android:id="@+id/selected" android:drawable="@drawable/ic_nav_agenda" android:state_checked="true" /> <item android:id="@+id/unselected"
android:drawable="@drawable/ic_nav_agenda" android:state_checked="false" /> </selector> + AnimatedVectorDrawable API 21 -
animation 10% + AnimatedVectorDrawable <selector> <item android:id="@+id/selected" android:drawable="@drawable/ic_nav_agenda" android:state_checked="true" />
<item android:id="@+id/unselected" android:drawable="@drawable/ic_nav_agenda" android:state_checked="false" /> </selector> API 21 -
animation 10% <animated-selector> <item android:id="@+id/selected" android:drawable="@drawable/ic_nav_agenda" android:state_checked="true" /> <item android:id="@+id/unselected"
android:drawable="@drawable/ic_nav_agenda" android:state_checked="false" /> </animated-selector> + AnimatedVectorDrawable API 21 -
animation 10% <item android:id="@+id/selected" android:drawable="@drawable/ic_nav_agenda" android:state_checked="true" /> <item android:id="@+id/unselected" android:drawable="@drawable/ic_nav_agenda"
android:state_checked="false" /> <transition android:drawable="@drawable/avd_nav_schedule_select" android:fromId="@id/unselected" android:toId="@id/selected" /> </animated-selector> + AnimatedVectorDrawable API 21 -
animation 10% <animated-vector> <aapt:attr name="android:drawable"> <vector android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:name="calendar" android:pathData="M 9.5 15 C 10.881 15 12 ..." android:fillColor="#ffffff" android:fillAlpha="0.7"/> </vector> </aapt:attr> <target android:name="calendar"> <aapt:attr name="android:animation"> + AnimatedVectorDrawable API 21 -
animation 10% <animated-vector> <aapt:attr name="android:drawable"> <vector android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:name="calendar" android:pathData="M 9.5 15 C 10.881 15 12 ..." android:fillColor="#ffffff" android:fillAlpha="0.7"/> </vector> </aapt:attr> <target android:name="calendar"> <aapt:attr name="android:animation"> <set> <objectAnimator android:propertyName="pathData" android:duration="400" android:valueFrom="M 9.5 15 C 10.881 15 12 13.881 12 12..." android:valueTo="M 9.49 14.6 C 10.607 14.6 11.99 13.481..." + AnimatedVectorDrawable API 21 -
animation 10% <animated-vector> <aapt:attr name="android:drawable"> <vector android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:name="calendar" android:pathData="M 9.5 15 C 10.881 15 12 ..." android:fillColor="#ffffff" android:fillAlpha="0.7"/> </vector> </aapt:attr> <target android:name="calendar"> <aapt:attr name="android:animation"> <set> <objectAnimator android:propertyName="pathData" android:duration="400" android:valueFrom="M 9.5 15 C 10.881 15 12 13.881 12 12..." android:valueTo="M 9.49 14.6 C 10.607 14.6 11.99 13.481..." android:valueType="pathType" + AnimatedVectorDrawable API 21 -
animation 10% android:name="calendar" android:pathData="M 9.5 15 C 10.881 15 12
..." android:fillColor="#ffffff" android:fillAlpha="0.7"/> </vector> </aapt:attr> <target android:name="calendar"> <aapt:attr name="android:animation"> <set> <objectAnimator android:propertyName="pathData" android:duration="400" android:valueFrom="M 9.5 15 C 10.881 15 12 13.881 12 12..." android:valueTo="M 9.49 14.6 C 10.607 14.6 11.99 13.481..." android:valueType="pathType" android:interpolator="@android:interpolator/fast_out_slow_in"/> <objectAnimator android:propertyName="pathData" android:startOffset="420" android:duration="150" android:valueFrom="M 9.49 14.6 C 10.607 14.6 11.99 13.4..." android:valueTo="M 9.5 15 C 10.881 15 12 13.881 12 12.5..." android:valueType="pathType" + AnimatedVectorDrawable API 21 -
animation 10% <target android:name="calendar"> <aapt:attr name="android:animation"> <set> <objectAnimator android:propertyName="pathData" android:duration="400"
android:valueFrom="M 9.5 15 C 10.881 15 12 13.881 12 12..." android:valueTo="M 9.49 14.6 C 10.607 14.6 11.99 13.481..." android:valueType="pathType" android:interpolator="@android:interpolator/fast_out_slow_in"/> <objectAnimator android:propertyName="pathData" android:startOffset="420" android:duration="150" android:valueFrom="M 9.49 14.6 C 10.607 14.6 11.99 13.4..." android:valueTo="M 9.5 15 C 10.881 15 12 13.881 12 12.5..." android:valueType="pathType" android:interpolator="@android:interpolator/fast_out_linear_in"/> <objectAnimator android:propertyName="pathData" android:startOffset="570" android:duration="150" + AnimatedVectorDrawable API 21 -
animation 10% <target android:name="calendar"> <aapt:attr name="android:animation"> <set> <objectAnimator android:propertyName="pathData" android:duration="400"
android:valueFrom="M 9.5 15 C 10.881 15 12 13.881 12 12..." android:valueTo="M 9.49 14.6 C 10.607 14.6 11.99 13.481..." android:valueType="pathType" android:interpolator="@android:interpolator/fast_out_slow_in"/> <objectAnimator android:propertyName="pathData" android:startOffset="420" android:duration="150" android:valueFrom="M 9.49 14.6 C 10.607 14.6 11.99 13.4..." android:valueTo="M 9.5 15 C 10.881 15 12 13.881 12 12.5..." android:valueType="pathType" android:interpolator="@android:interpolator/fast_out_linear_in"/> <objectAnimator android:propertyName="pathData" android:startOffset="570" android:duration="150" + AnimatedVectorDrawable API 21 -
animation 10% android:interpolator="@android:interpolator/linear_out_slow_in"/> <objectAnimator android:propertyName="pathData" android:startOffset="750" android:duration="200" android:valueFrom="M 9.5 15.5
C 11.318 15.5 12 14.081 12..." android:valueTo="M 9.5 15 C 10.881 15 12 13.881 12 12.5 ..." android:valueType="pathType" android:interpolator="@android:anim/overshoot_interpolator"/> <objectAnimator android:propertyName="fillAlpha" android:duration="300" android:valueFrom="0.7" android:valueTo="1" android:valueType="floatType" android:interpolator="@android:interpolator/fast_out_slow_in"/> </set> </aapt:attr> </target> </animated-vector> + AnimatedVectorDrawable API 21 -
animation 10% + AnimatedVectorDrawable <com.google.android.material.bottomnavigation.BottomNavigationView ... app:menu="@menu/navigation" /> <!-- res/menu/navigation.xml
--> <menu> <item android:id="@+id/navigation_schedule" android:icon="@drawable/asld_nav_schedule" android:title="Schedule" /> </menu> API 21 -
data class HomeUiModel( val isChecked: Boolean ) button.checked = uiModel.isChecked
Link: Motional Intelligence: Build Smarter Animations (Google I/O'19) -
animation 10% RippleDrawable <ripple android:color="?colorControlHighlight"> <item android:drawable="@drawable/ic_rectangle" /> <item android:id="@android:id/mask"
android:drawable="@drawable/ic_rectangle" /> </ripple> API 21 -
animation 10% RippleDrawable <ripple android:color="?colorControlHighlight"> <item android:drawable="@drawable/ic_rectangle" /> <item android:id="@android:id/mask"
android:drawable="@drawable/ic_rectangle" /> </ripple> API 21 -
animation 10% RippleDrawable <ripple android:color="?colorControlHighlight"> <item android:drawable="@drawable/ic_rectangle" /> <item android:id="@android:id/mask"
android:drawable="@drawable/ic_rectangle" /> </ripple> API 21 -
animation 10% RippleDrawable <ripple android:color="?colorControlHighlight"> <item android:drawable="@drawable/ic_rectangle" /> <item android:id="@android:id/mask"
android:drawable="@drawable/ic_toys" /> </ripple> API 21 -
animation 10% • ࠄޙ ߣ૩ • ࠄޙ ف
ߣ૩ • ࠄޙ ࣁ ߣ૩ • ࠄޙ ֎ ߣ૩ • ࠄޙ ࢽ ߣ૩ GradientDrawable class AnimatedGradientView : View() { private val colors = intArrayOf( Color.RED, Color.GREEN, Color.BLUE ) private val drawable = GradientDrawable( GradientDrawable.Orientation.BR_TL, colors.take(2).toIntArray() ) private val animator = createGradientAnimator() private fun createGradientAnimator(): Animator { -
animation 10% class AnimatedGradientView : View() { private val colors
= intArrayOf( Color.RED, Color.GREEN, Color.BLUE ) private val drawable = GradientDrawable( GradientDrawable.Orientation.BR_TL, colors.take(2).toIntArray() ) private val animator = createGradientAnimator() private fun createGradientAnimator(): Animator { val max = colors.size return ValueAnimator.ofFloat(0f, max.toFloat()).apply { interpolator = LinearInterpolator() duration = 3_000L repeatCount = ValueAnimator.INFINITE GradientDrawable -
animation 10% ) private val drawable = GradientDrawable( GradientDrawable.Orientation.BR_TL, colors.take(2).toIntArray()
) private val animator = createGradientAnimator() private fun createGradientAnimator(): Animator { val max = colors.size return ValueAnimator.ofFloat(0f, max.toFloat()).apply { interpolator = LinearInterpolator() duration = 3_000L repeatCount = ValueAnimator.INFINITE repeatMode = ValueAnimator.RESTART val evaluator = ArgbEvaluator() addUpdateListener { val index = (it.animatedValue as Float).toInt() val start = colors[index % max] val center = colors[(index + 1) % max] val end = colors[(index + 2) % max] GradientDrawable -
animation 10% private fun createGradientAnimator(): Animator { val max =
colors.size return ValueAnimator.ofFloat(0f, max.toFloat()).apply { interpolator = LinearInterpolator() duration = 3_000L repeatCount = ValueAnimator.INFINITE repeatMode = ValueAnimator.RESTART val evaluator = ArgbEvaluator() addUpdateListener { val index = (it.animatedValue as Float).toInt() val start = colors[index % max] val center = colors[(index + 1) % max] val end = colors[(index + 2) % max] val fraction = it.animatedValue as Float - index drawable.colors = intArrayOf( evaluator.evaluate(fraction, start, center) as Int, evaluator.evaluate(fraction, center, end) as Int ) } } } GradientDrawable -
animation 10% private fun createGradientAnimator(): Animator { val max =
colors.size return ValueAnimator.ofFloat(0f, max.toFloat()).apply { interpolator = LinearInterpolator() duration = 3_000L repeatCount = ValueAnimator.INFINITE repeatMode = ValueAnimator.RESTART val evaluator = ArgbEvaluator() addUpdateListener { val index = (it.animatedValue as Float).toInt() val start = colors[index % max] val center = colors[(index + 1) % max] val end = colors[(index + 2) % max] val fraction = it.animatedValue as Float - index drawable.colors = intArrayOf( evaluator.evaluate(fraction, start, center) as Int, evaluator.evaluate(fraction, center, end) as Int ) } } } GradientDrawable API 16 -
animation 10% drawable.colors = intArrayOf( evaluator.evaluate(fraction, start, center) as Int,
evaluator.evaluate(fraction, center, end) as Int ) } } } init { background = drawable // Animation द animator.start() // Animation animator.cancel() } } GradientDrawable API 16 -
animation 10% view.clipToOutline = true view.outlineProvider = OvalOutlineProvider object OvalOutlineProvider
: ViewOutlineProvider() { override fun getOutline(view: View, outline: Outline) { outline.setOval( view.paddingLeft, view.paddingTop, view.width - view.paddingRight, view.height - view.paddingBottom ) } } GradientDrawable API 21 -
animation 10% view.clipToOutline = true view.outlineProvider = RoundRectOutlineProvider(clipRadius) class RoundRectOutlineProvider(
private val radius: Float ) : ViewOutlineProvider() { override fun getOutline(view: View, outline: Outline) { outline.setRoundRect( view.paddingLeft, view.paddingTop, view.width - view.paddingRight, view.height - view.paddingBottom, radius ) } GradientDrawable API 21 -
animation 10% class AnimatedBorderView : View() { private var borderWidth:
Int = ... override fun draw(canvas: Canvas) { canvas.clipBorder(borderWidth) super.draw(canvas) } private fun Canvas.clipBorder(gap: Int) { val rect = Rect(gap, gap, width - gap, height - gap) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { clipOutRect(rect) } else { clipRect(rect, Region.Op.DIFFERENCE) GradientDrawable -
animation 10% private var borderWidth: Int = ... override fun
draw(canvas: Canvas) { canvas.clipBorder(borderWidth) super.draw(canvas) } private fun Canvas.clipBorder(gap: Int) { val rect = Rect(gap, gap, width - gap, height - gap) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { clipOutRect(rect) } else { clipRect(rect, Region.Op.DIFFERENCE) } } } GradientDrawable -
animation 10% override fun draw(canvas: Canvas) { canvas.clipOuter(clipPath) super.draw(canvas) }
private fun Canvas.clipOuter(path: Path) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { clipOutPath(path) } else { clipPath(path, Region.Op.DIFFERENCE) } } GradientDrawable -
animation 10% override fun draw(canvas: Canvas) { canvas.clipInner(clipPath) super.draw(canvas) }
private fun Canvas.clipOuter(path: Path) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { clipPath(path) } else { clipPath(path, Region.Op.INTERSECT) } } GradientDrawable -
animation 10% • ٣ցо ખ ؊ ਬ܂ѱ Ѿҗޛਸ সೡ ࣻ
. • Android, iOS, Web ݽف زੌೞѱ ਗೠ. • ױ, Vector ӝ߈۽ Gradationਸ ਊೡ ࣻ হ. Render Adobe After Effects animations! Lottie Drawable Link: https://github.com/airbnb/lottie-android animation 10% 3rd Party -
animation 10% • PNGܳ ഛೠ ੌ ನݘ (.png, .apng)
• ৈ۞ PNG ܳ ೞա۽ ೠ ഋక. • Gradation ৈࠗী ࢚ҙহ Animation оמೞ. • ױ, AnimationDrawableۢ ݫݽܻ৬ FPS ޙઁо . Animated Portable Network Graphics APNG Link: https://ko.wikipedia.org/wiki/APNG 3rd Party -
animation 10% • ImageView ١ Drawable APIী ࢎਊೡ ࣻ .
• ইऔѱب View ҳഅח ઁҕغ ঋח. A lightweight and fast APNG image decoder for Android ApngDrawable Link: https://github.com/line/apng-drawable 3rd Party -
animation 10% ApngDrawable // WorkerThread val apngDrawable: ApngDrawable = ApngDrawable.decode(context.resources,
resId).apply { loopCount = ApngDrawable.LOOP_FOREVER setTargetDensity(resources.displayMetrics) } // MainThread setImageDrawable(apngDrawable) 3rd Party -
animation 10% • res/layout/fragment_third_party.xml ApngImageView <soup.animation.sample.third_party.ApngImageView ... android:scaleType="centerCrop" app:apng_autoPlay="true" app:apng_loop="true"
app:apng_rawRes="@raw/apng_example" /> Custom Link: https://github.com/fornewid/android-animation-10p-more/commit/b2657ce225 -
animation 10% ProgressBar + RotateDrawable <layer-list> <item android:gravity="center"> <rotate android:drawable="@drawable/loading_custom_progress"
android:pivotX="50%" android:pivotY="50%" android:fromDegrees="0" android:toDegrees="360" /> </item> <item android:drawable="@drawable/ic_android" android:gravity="center" /> </layer-list> -
animation 10% ProgressBar + RotateDrawable <layer-list> <item android:gravity="center"> <rotate android:drawable="@drawable/loading_custom_progress"
android:pivotX="50%" android:pivotY="50%" android:fromDegrees="0" android:toDegrees="360" /> </item> <item android:drawable="@drawable/ic_android" android:gravity="center" /> </layer-list> -
animation 10% ProgressBar + RotateDrawable <layer-list> <item android:gravity="center"> <rotate android:drawable="@drawable/loading_custom_progress"
android:pivotX="50%" android:pivotY="50%" android:fromDegrees="0" android:toDegrees="360" /> </item> <item android:drawable="@drawable/ic_android" android:gravity="center" /> </layer-list> -
animation 10% ProgressBar + RotateDrawable <layer-list> <item android:gravity="center"> <rotate android:drawable="@drawable/loading_custom_progress"
android:pivotX="50%" android:pivotY="50%" android:fromDegrees="0" android:toDegrees="360" /> </item> <item android:drawable="@drawable/ic_android" android:gravity="center" /> </layer-list> -
animation 10% ProgressBar + ClipDrawable <layer-list> <item android:gravity="center_vertical" /> <item
android:gravity="center_vertical"> <clip /> </item> </layer-list> -
animation 10% ProgressBar + ClipDrawable <layer-list> <item android:gravity="center_vertical"> <shape android:shape="rectangle">
<corners android:radius="2dp" /> <size android:height="4dp" /> <solid android:color="#ebebeb" /> </shape> </item> <item android:gravity="center_vertical"> <clip /> </item> </layer-list> -
animation 10% ProgressBar + ClipDrawable <layer-list> <item android:gravity="center_vertical" /> <item
android:gravity="center_vertical"> <clip android:clipOrientation="horizontal" android:gravity="start"> <shape android:shape="rectangle"> <corners android:radius="2dp" /> <size android:height="4dp" /> <gradient android:angle="180" android:endColor="@color/android4" android:startColor="@color/android1" /> </shape> </clip> </item> -
animation 10% • Drawable Level ӝמ . (ߧਤ: 0 ~
10000) • ClipDrawable, RotateDrawable, ScaleDrawable, LevelListDrawable ١ • Drawable#setLevel(int) API • ImageView#setImageLevel(int) API Drawableҗ Ѿೞৈ ࣚऔѱ Animation ҳഅ оמ Animator + Drawable Level -
animation 10% Animator + Drawable Level private val animator =
ValueAnimator.ofFloat(0f, 1f).apply { repeatMode = ValueAnimator.REVERSE repeatCount = ValueAnimator.INFINITE duration = 1_000L interpolator = Interpolators.LINEAR addUpdateListener { batteryClip.setImageLevel( (it.animatedFraction * 10000).toInt()) } } -
animation 10% Animator A N D R O I D
animation +10% -
animation 10% • XML ੌ۽ ࢶೡ ࣻ . • VSYNC৬
োزೞৈ 60fps۽ زೠ. package android.animation Animator -
animation 10% AnimatorInflater Link: http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/ animation/AnimatorInflater.java#666 • res/animator/{file_name}.xml <set />
<animator /> <objectAnimator /> <propertyValuesHolder /> -> AnimatorSet -> ValueAnimator -> ObjectAnimator -> PropertyValuesHolder[] -
animation 10% Animator abstract class Animator class AnimatorSet extends Animator
class ValueAnimator extends Animator class ObjectAnimator extends ValueAnimator class TimeAnimator extends ValueAnimator -
animation 10% The VSYNC signal synchronizes the display pipeline. (60fps)
VSYNC (Vertical Synchronization) -
animation 10% • ValueAnimatorо Choreographerܳ ਊೠ. VSYNCܳ ਊೞৈ, ٣झۨ ۨ
۪؊݂ झாેਸ ҙܻ Choreographer Link: https://developer.android.com/reference/android/view/Choreographer API 16 -
animation 10% Choreographer val choreographer = Choreographer.getInstance() choreographer.postFrameCallback { //
draw next frame } // Not this view.post() // But this view.postOnAnimation { ... } API 16 -
animation 10% Choreographer val choreographer = Choreographer.getInstance() choreographer.postFrameCallback { //
draw next frame } // Not this view.invalidate() // But this view.postInvalidateOnAnimation() API 16 -
animation 10% public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {
private void start(boolean playBackwards) { ... AnimationHandler.getInstance() .addAnimationFrameCallback(this, delay); ... } public final boolean doAnimationFrame(long frameTime) {} } public class AnimationHandler { Choreographer: Animator -
animation 10% private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override public void doFrame(long frameTimeNanos) { doAnimationFrame(getProvider().getFrameTime()); Choreographer.getInstance() .postFrameCallback(mFrameCallback); } }; public void addAnimationFrameCallback(...) { Choreographer.getInstance() .postFrameCallback(mFrameCallback); } } Choreographer: Animator -
animation 10% ... } public final boolean doAnimationFrame(long frameTime) {}
} public class AnimationHandler { private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { doAnimationFrame(getProvider().getFrameTime()); Choreographer.getInstance() .postFrameCallback(mFrameCallback); } }; public void addAnimationFrameCallback(...) { Choreographer.getInstance() .postFrameCallback(mFrameCallback); Choreographer: Animator -
animation 10% public final boolean doAnimationFrame(long frameTime) {} } public
class AnimationHandler { private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { doAnimationFrame(getProvider().getFrameTime()); Choreographer.getInstance() .postFrameCallback(mFrameCallback); } }; public void addAnimationFrameCallback(...) { Choreographer.getInstance() .postFrameCallback(mFrameCallback); } } Choreographer: Animator -
animation 10% public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {
private void start(boolean playBackwards) { ... AnimationHandler.getInstance() .addAnimationFrameCallback(this, delay); ... } public final boolean doAnimationFrame(long frameTime) {} } public class AnimationHandler { private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { doAnimationFrame(getProvider().getFrameTime()); Choreographer: Animator -
animation 10% Animator ValueAnimator.ofFloat(0f, 1f).apply { repeatMode = ValueAnimator.REVERSE repeatCount
= ValueAnimator.INFINITE duration = 1_000L interpolator = Interpolators.ACCELERATE_DECELERATE } -
animation 10% <animator android:valueType="floatType" android:valueFrom="0" android:valueTo="1" android:repeatMode="reverse" android:repeatCount="infinite" android:duration="1000" android:interpolator=
"@android:interpolator/accelerate_decelerate" /> Animator -
animation 10% <animator android:valueType="floatType" android:valueFrom="0" android:valueTo="1" android:repeatMode="reverse" android:repeatCount="infinite" android:duration="1000" android:interpolator=
"@android:interpolator/accelerate_decelerate" /> Animator floatType intType pathType colorType -
animation 10% <animator android:valueType="floatType" android:valueFrom="0" android:valueTo="1" android:repeatMode="reverse" android:repeatCount="infinite" android:duration="1000" android:interpolator=
"@android:interpolator/accelerate_decelerate" /> Animator ValueAnimator.ofFloat() ValueAnimator.ofInt() ValueAnimator.ofPropertyValuesHolder() ValueAnimator.ofArgb() -
animation 10% <animator android:valueType="floatType" android:valueFrom="0" android:valueTo="1" android:repeatMode="reverse" android:repeatCount="infinite" android:duration="1000" android:interpolator=
"@android:interpolator/accelerate_decelerate" /> Animator FloatEvaluator() IntEvaluator() PathDataEvaluator() ArgbEvaluator() -
animation 10% <animator android:valueType="floatType" android:valueFrom="0" android:valueTo="1" android:repeatMode="reverse" android:repeatCount="infinite" android:duration="1000" android:interpolator=
"@android:interpolator/accelerate_decelerate" /> Animator -
animation 10% Animator ValueAnimator.ofFloat(0f, 1f).apply { repeatMode = ValueAnimator.REVERSE repeatCount
= ValueAnimator.INFINITE duration = 1_000L interpolator = Interpolators.ACCELERATE_DECELERATE } -
animation 10% val animator = ValueAnimator.ofFloat(0f, 1f).apply { repeatMode =
ValueAnimator.REVERSE repeatCount = ValueAnimator.INFINITE duration = 1_000L interpolator = Interpolators.ACCELERATE_DECELERATE } Animator -
animation 10% val animator = ValueAnimator.ofFloat(0f, 1f).apply { repeatMode =
ValueAnimator.REVERSE repeatCount = ValueAnimator.INFINITE duration = 1_000L interpolator = Interpolators.ACCELERATE_DECELERATE } animator.addUpdateListener { updateUi(it.animatedFraction) // or updateUi(it.animatedValue as Float) } animator.removeAllUpdateListeners() animator.start() animator.cancel() Animator -
animation 10% Animator private fun updateUi(fraction: Float) { iconView.run {
rotation = lerp(0f, 360f, fraction) alpha = lerp(1f, .5f, fraction) scaleX = lerp(1f, 2f, fraction) scaleY = lerp(1f, .5f, fraction) translationX = lerp(0f, maxTranslationX, fraction) translationY = lerp(0f, maxTranslationY, fraction) } bugView.translationX = lerp(0f, maxTranslationX, fraction) } -
animation 10% Lerp fun lerp(from: Float, to: Float, progress: Float):
Float { return from + (to - from) * progress } -
animation 10% <CoordinatorLayout> <AppBarLayout> <CollapsingConstraintLayout> <ImageView ... android:id="@+id/image" android:background="@drawable/user_icon" android:scaleType="centerCrop"
/> <Alpha ... app:constraint_referenced_ids="image" /> </CollapsingConstraintLayout> </AppBarLayout> ... Lerp + ConstraintLayout -
animation 10% class CollapsingConstraintLayout : ConstraintLayout(), AppBarLayout.OnOffsetChangedListener { override fun
onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) { setProgress(-verticalOffset / appBarLayout.totalScrollRange.toFloat()) } private fun setProgress(progress: Float) { val fraction = progress.coerceIn(0f, 1f) children.forEach { if (it is ProgressHelper) { it.setProgress(fraction) } } } ... Lerp + ConstraintLayout -
animation 10% class Alpha : ConstraintHelper(), ProgressHelper { override fun
setProgress(progress: Float) { ... views.forEach { it.alpha = lerp(0f, 1f, 1f - progress) } } ... Lerp + ConstraintLayout -
animation 10% LayoutTransition <LinearLayout android:id="@+id/container" android:animateLayoutChanges="true" ... /> container.addView(...) container.removeView(...)
container.childAt(...).setVisibility(...) API 11 -
animation 10% LayoutTransition // ViewGroup.java case R.styleable.ViewGroup_animateLayoutChanges: boolean animateLayoutChanges =
a.getBoolean(attr, false); if (animateLayoutChanges) { setLayoutTransition(new LayoutTransition()); } break; API 11 -
animation 10% PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 1); PropertyValuesHolder pvhTop
= PropertyValuesHolder.ofInt("top", 0, 1); PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0, 1); PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1); PropertyValuesHolder pvhScrollX = PropertyValuesHolder.ofInt("scrollX", 0, 1); PropertyValuesHolder pvhScrollY = PropertyValuesHolder.ofInt("scrollY", 0, 1); defaultChange = ObjectAnimator.ofPropertyValuesHolder((Object)null, pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScrollX, pvhScrollY); defaultFadeIn = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f); defaultFadeOut = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f); LayoutTransition API 11 -
animation 10% container.layoutTransition.apply { setInterpolator(LayoutTransition.APPEARING, Interpolators.ACCELERATE_DECELERATE) setDuration(LayoutTransition.APPEARING, 300) } //
LayoutTransition.APPEARING // LayoutTransition.DISAPPEARING // LayoutTransition.CHANGE_APPEARING // LayoutTransition.CHANGE_DISAPPEARING // LayoutTransition.CHANGING LayoutTransition API 11 -
animation 10% // Show revealView.showContents() private fun View.showContents() { visibility
= View.VISIBLE createCircularRevealOf(fab, 0f, radius) { duration = 300 interpolator = Interpolators.DECELERATE }.start() } // Hide revealView.hideContents() private fun View.hideContents() { Circular Reveal API 21 -
animation 10% createCircularRevealOf(fab, 0f, radius) { duration = 300 interpolator
= Interpolators.DECELERATE }.start() } // Hide revealView.hideContents() private fun View.hideContents() { createCircularRevealOf(fab, radius, 0f) { duration = 300 interpolator = Interpolators.ACCELERATE doOnEnd { visibility = View.GONE } }.start() } private inline fun View.createCircularRevealOf( target: View, Circular Reveal API 21 -
animation 10% doOnEnd { visibility = View.GONE } }.start() }
private inline fun View.createCircularRevealOf( target: View, startRadius: Float, endRadius: Float, block: Animator.() -> Unit ): Animator { return ViewAnimationUtils.createCircularReveal( this, target.centerX(), target.centerY(), startRadius, endRadius ).apply(block) } Circular Reveal API 21 -
animation 10% interface CircularRevealWidget class CircularRevealLinearLayout extends LinearLayout class CircularRevealRelativeLayout
extends RelativeLayout class CircularRevealGridLayout extends GridLayout class CircularRevealFrameLayout extends FrameLayout class CircularRevealCoordinatorLayout extends CoordinatorLayout class CircularRevealCardView extends MaterialCardView MDC Circular Reveal API 14 -
animation 10% private inline fun View.createCircularRevealOf( target: View, startRadius: Float,
endRadius: Float, block: Animator.() -> Unit ): Animator { return ViewAnimationUtils.createCircularReveal( this, target.centerX(), target.centerY(), startRadius, endRadius ).apply(block) } Circular Reveal API 21 -
animation 10% private inline fun CircularRevealWidget.createCircularRevealCompatOf( target: View, startRadius: Float,
endRadius: Float, block: Animator.() -> Unit ): Animator { return CircularRevealCompat.createCircularReveal( this, target.centerX().toFloat(), target.centerY().toFloat(), startRadius, endRadius ).apply(block) } MDC Circular Reveal API 14 -
animation 10% FAB Transform <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/transformFab" style="@style/Widget.MaterialComponents.FloatingActionButton" ... /> //
Show transformFab.isExpanded = true // Hide transformFab.isExpanded = false -
animation 10% <View android:id="@+id/scrim" ... android:background="#66333333" android:visibility="invisible" app:layout_behavior= "@string/fab_transformation_scrim_behavior" />
<com.google.android.material.circularreveal.cardview.CircularRevealCardView android:id="@+id/sheet" android:visibility="invisible" app:layout_behavior= "@string/fab_transformation_sheet_behavior"> ... </com.google.android.material.circularreveal.cardview.CircularRevealCardView> FAB Transform -
animation 10% <View android:id="@+id/scrim" ... android:background="#66333333" android:visibility="invisible" app:layout_behavior= "@string/fab_transformation_scrim_behavior" />
<com.google.android.material.circularreveal.cardview.CircularRevealCardView android:id="@+id/sheet" android:visibility="invisible" app:layout_behavior= "@string/fab_transformation_sheet_behavior"> ... </com.google.android.material.circularreveal.cardview.CircularRevealCardView> FAB Transform -
animation 10% <!-- res/animator/sla_fab.xml --> <selector> <item android:state_pressed="true"> <objectAnimator />
</item> <item> <objectAnimator /> </item> </selector> StateListAnimator -
animation 10% <!-- res/animator/sla_fab.xml --> <selector> <item android:state_pressed="true"> <objectAnimator android:duration="100"
android:propertyName="translationZ" android:valueTo="6dp" android:valueType="floatType" /> </item> <item> <objectAnimator android:duration="100" android:propertyName="translationZ" android:valueTo="0dp" android:valueType="floatType" /> StateListAnimator -
animation 10% <!-- res/animator/sla_fab.xml --> <selector> <item android:state_pressed="true"> <objectAnimator android:duration="100"
android:propertyName="translationZ" android:valueTo="6dp" android:valueType="floatType" /> </item> <item> <objectAnimator android:duration="100" android:propertyName="translationZ" android:valueTo="0dp" android:valueType="floatType" /> </item> </selector> StateListAnimator -
animation 10% <!-- res/animator/sla_fab.xml --> <selector> <item android:state_pressed="true"> <set> <objectAnimator
/> <objectAnimator android:duration="100" android:propertyName="scaleX" android:valueTo="1.1" android:valueType="floatType" /> <objectAnimator android:duration="100" android:propertyName="scaleY" android:valueTo="1.1" android:valueType="floatType" /> StateListAnimator -
animation 10% <!-- res/animator/sla_card.xml --> <selector> <item android:state_activated="true"> <set android:ordering="together">
<objectAnimator android:propertyName="backgroundColor" /> <objectAnimator android:propertyName="translationZ"/> </set> </item> <item> <set ... /> </item> </selector> StateListAnimator + Transition -
animation 10% <!-- res/animator/sla_card.xml --> <selector> <item android:state_activated="true"> <set android:ordering="together">
<objectAnimator android:duration="200" android:propertyName="backgroundColor" android:valueFrom="@color/android1" android:valueTo="@color/android4" android:valueType="colorType" /> <objectAnimator /> </set> </item> <item> <set android:ordering="together"> StateListAnimator + Transition -
animation 10% android:valueFrom="@color/android1" android:valueTo="@color/android4" android:valueType="colorType" /> <objectAnimator /> </set> </item>
<item> <set android:ordering="together"> <objectAnimator android:duration="200" android:propertyName="backgroundColor" android:valueFrom="@color/android4" android:valueTo="@color/android1" android:valueType="colorType" /> <objectAnimator /> </set> </item> </selector> StateListAnimator + Transition -
animation 10% <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/itemView" ... android:stateListAnimator="@animator/sla_card"> ... <androidx.constraintlayout.widget.Group android:id="@+id/actionGroup" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:visibility="gone" app:constraint_referenced_ids="add,close,touchApp" /> </androidx.constraintlayout.widget.ConstraintLayout> itemView.isActivated = expand StateListAnimator + Transition -
animation 10% android:id="@+id/actionGroup" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="gone" app:constraint_referenced_ids="add,close,touchApp" /> </androidx.constraintlayout.widget.ConstraintLayout> TransitionManager.beginDelayedTransition(container)
itemView.isActivated = expand actionGroup.isVisible = expand StateListAnimator + Transition -
animation 10% Interpolator A N D R O I D
animation +10% -
animation 10% • 0f, 1f ࢎ чਸ ೞѱ ࠁрػ чਵ۽
߈ജೠ. • р ч 0f ~ 1f ߧਤܳ ߩযզ ࣻ ݅, दҗ 0f, 1fܳ ࠁೠ. • ױࣽ Inputী Outputਸ ߈ജೞח Ѫ۽, Singletonਵ۽ ࢎਊೡ ࣻ . Animation ߸ചೞח ب Interpolator -
animation 10% Interpolator // package android.animation; public interface TimeInterpolator {
// input : 0 and 1.0 // output: The interpolation value. float getInterpolation(float input); } // package android.view.animation; public interface Interpolator extends TimeInterpolator { } -
animation 10% Interpolator Link: android/view/animation/AnimationUtils.java#408 • res/interpolator/{file_name}.xml <linearInterpolator /> <accelerateInterpolator
/> <decelerateInterpolator /> <accelerateDecelerateInterpolator /> <cycleInterpolator /> <anticipateInterpolator /> <overshootInterpolator /> <anticipateOvershootInterpolator /> <bounceInterpolator /> <pathInterpolator /> -> LinearInterpolator -> AccelerateInterpolator -> DecelerateInterpolator -> AccelerateDecelerateInterpolator -> CycleInterpolator -> AnticipateInterpolator -> OvershootInterpolator -> AnticipateOvershootInterpolator -> BounceInterpolator -> PathInterpolator -
animation 10% Interpolator AnimationUtils.loadInterpolator( context, android.R.interpolator.linear) val animation: Animation =
... animation.setInterpolator( context, android.R.interpolator.linear) <objectAnimator android:interpolator="@android:interpolator/linear" ... /> -
animation 10% PathInterpolator android.view.animation <!-- Quad --> <pathInterpolator android:controlX1="0.33" android:controlY1="0"
/> <!-- Cubic --> <pathInterpolator android:controlX1="0.33" android:controlY1="0" android:controlX2="1" android:controlY2="1" /> <!-- Path --> <pathInterpolator android:pathData="M 0.0,0.0 l 1.0,0.0 l 0.0,1.0" /> API 21 -
animation 10% PathInterpolator android.view.animation // Quad PathInterpolator(0.33f, 0f) // Cubic
PathInterpolator(0.33f, 0f, 1f, 1f) // Path PathInterpolator(Path().apply { moveTo(0f,0f) lineTo(1f, 0f) lineTo(0f, 1f) }) API 21 -
animation 10% PathInterpolatorCompat androidx.core.view.animation // Quad PathInterpolatorCompat.create(0.33f, 0f) // Cubic
PathInterpolatorCompat.create(0.33f, 0f, 1f, 1f) // Path PathInterpolatorCompat.create(Path().apply { moveTo(0f,0f) lineTo(1f, 0f) lineTo(0f, 1f) }) implementation 'androidx.interpolator:interpolator:1.0.0' API 14 -
animation 10% Easing Interpolator val EASE_IN_OUT_CUBIC by cubicBezier(0.645f, 0.045f, 0.355f,
1f) private fun cubicBezier( x1: Float, y1: Float, x2: Float, y2: Float ): Lazy<Interpolator> { return lazy { PathInterpolatorCompat.create(x1, y1, x2, y2) } } -
animation 10% • ਃೠ ҃, Custom Viewী Attributeܳ ୶оೞৈ Layout
XMLী Interpolatorܳ ࢶೞח ߑߨ оמೞ. Interpolator Attribute -
animation 10% Interpolator Attribute <declare-styleable name="CustomView"> <attr name="interpolator" format="reference" />
</declare-styleable> init { val a: TypedArray = ... val resId = a.getResourceId( R.styleable.CustomView_interpolator, 0) if (resId > 0) { ... = AnimationUtils.loadInterpolator(context, resId) } ... } -
animation 10% ViewPropertyAnimator A N D R O I D
animation +10% -
animation 10% ViewPropertyAnimator view.animate() .translationX(100f) .setStartDelay(300L) // ms .setDuration(1000L) //
ms .setInterpolator(Interpolators.ACCELERATE_DECELERATE) -
animation 10% Render into off-screen buffers. View Layer (Hardware vs
Software) Link: Android Graphics Performance (Google I/O '13) API 11 -
animation 10% Hardware Layer API 11 view.setLayerType(View.LAYER_TYPE_HARDWARE, null) // do
animate! Link: https://developer.android.com/guide/topics/graphics/hardware-accel#layers -
animation 10% Hardware Layer API 11 view.setLayerType(View.LAYER_TYPE_HARDWARE, null) // do
animate! view.setLayerType(View.LAYER_TYPE_NONE, null) Link: https://developer.android.com/guide/topics/graphics/hardware-accel#layers -
animation 10% • Viewী Alphaܳ ࢎਊೞח ҃, Viewܳ ࢚ࣘ೧ࢲ falseܳ
߈ജೞח Ѫ ࢿמী ਬܻೞ. • ױ, View ղࠗ ਃٜࣗ Ҁ ঋইঠ ৢ߄ܰѱ Ӓ۰. An optimization when alpha is set on a view. View#hasOverlappingRendering() Link: Hidden Cost of Transparency (100 Days of Google Dev) API 16 -
animation 10% View#hasOverlappingRendering() <ConstraintLayout> <ImageView ... android:layout_marginEnd="40dp" android:layout_marginBottom="40dp" android:src="@drawable/user_icon" />
<ImageView ... android:layout_marginStart="40dp" android:layout_marginTop="40dp" android:src="@drawable/user_icon" /> </ConstraintLayout> API 16 -
animation 10% View#hasOverlappingRendering() <ConstraintLayout> <ImageView ... android:layout_marginEnd="40dp" android:layout_marginBottom="40dp" android:src="@drawable/user_icon" />
<ImageView ... android:layout_marginStart="40dp" android:layout_marginTop="40dp" android:src="@drawable/user_icon" /> </ConstraintLayout> API 16 -
animation 10% View#hasOverlappingRendering() <ConstraintLayout android:alpha="0.5"> <ImageView ... android:layout_marginEnd="40dp" android:layout_marginBottom="40dp" android:src="@drawable/user_icon"
/> <ImageView ... android:layout_marginStart="40dp" android:layout_marginTop="40dp" android:src="@drawable/user_icon" /> </ConstraintLayout> API 16 -
animation 10% View#hasOverlappingRendering() <ConstraintLayout android:alpha="0.5"> <ImageView ... android:layout_marginEnd="40dp" android:layout_marginBottom="40dp" android:src="@drawable/user_icon"
/> <ImageView ... android:layout_marginStart="40dp" android:layout_marginTop="40dp" android:src="@drawable/user_icon" /> </ConstraintLayout> API 16 -
animation 10% View#hasOverlappingRendering() <AlphaOptimizedConstraintLayout android:alpha="0.5"> <ImageView ... android:layout_marginEnd="40dp" android:layout_marginBottom="40dp" android:src="@drawable/user_icon"
/> <ImageView ... android:layout_marginStart="40dp" android:layout_marginTop="40dp" android:src="@drawable/user_icon" /> </AlphaOptimizedConstraintLayout> API 16 -
animation 10% View#hasOverlappingRendering() <AlphaOptimizedConstraintLayout android:alpha="0.5"> <ImageView ... android:layout_marginEnd="40dp" android:layout_marginBottom="40dp" android:src="@drawable/user_icon"
/> <ImageView ... android:layout_marginStart="40dp" android:layout_marginTop="40dp" android:src="@drawable/user_icon" /> </AlphaOptimizedConstraintLayout> API 16 -
animation 10% View#hasOverlappingRendering() class AlphaOptimizedConstraintLayout @JvmOverloads constructor( context: Context, attrs:
AttributeSet? = null, defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr) { override fun hasOverlappingRendering(): Boolean { return false } } Custom Link: AlphaOptimizedFrameLayout.java -
animation 10% RecyclerView.ItemAnimator class SlideInUpAnimator : DefaultItemAnimator() { override fun
animateAddImpl(holder: RecyclerView.ViewHolder) { holder.itemView.animate() .translationY(0f) .alpha(1f) .setDuration(addDuration) .start() } } -
animation 10% RecyclerView.ItemAnimator class RecyclerViewAdapter() : ListAdapter<Item, ViewHolder>( IdBasedDiffCallback {
id.toString() } ) { ... } class IdBasedDiffCallback<T>( private val id: T.() -> String ) : DiffUtil.ItemCallback<T>() { override fun areItemsTheSame(oldItem: T, newItem: T): Boolean { return oldItem.id() == newItem.id() } ... } -
animation 10% View Animation A N D R O I
D animation +10% -
animation 10% • ViewPropertyAnimator৬ ਬࢎೞ. • Windowр Animationী ࢎਊೡ ࣻ
. • Activity <—> Activity • Fragment <—> Fragment package android.view.animation View Animation -
animation 10% View Animation <set> <alpha android:fromAlpha="1.0" android:toAlpha="0.5" /> <translate
android:fromXDelta="0" android:toXDelta="100" android:fromYDelta="0" android:toYDelta="100" /> </set> -
animation 10% View Animation <set android:shareInterpolator="true" android:duration="1000" android:interpolator="@android:anim/accelerate_interpolator"> <alpha android:fromAlpha="1.0"
android:toAlpha="0.5" /> <translate android:fromXDelta="0" android:toXDelta="100" android:fromYDelta="0" android:toYDelta="100" /> </set> -
animation 10% View Animation <set> <alpha /> <translate /> <scale
/> <rotate /> </set> -
animation 10% View Animation <set> <alpha /> <translate /> <scale
/> <rotate /> <set /> </set> -
animation 10% Window Animation: Activity activity.overridePendingTransition( R.anim.slide_in_right, // enterAnim R.anim.slide_out_left
// exitAnim ) activity.overridePendingTransition( R.anim.slide_in_left, // popEnterAnim R.anim.slide_out_right // popExitAnim ) -
animation 10% Window Animation: Fragment fragmentManager.beginTransaction() .setCustomAnimations( R.anim.slide_in_right, // enterAnim
R.anim.slide_out_left, // exitAnim R.anim.slide_in_left, // popEnterAnim R.anim.slide_out_right // popExitAnim ) .replace(...) .commit() -
animation 10% Window Animation: Navigation <action ... app:destination="@id/home" app:enterAnim="@anim/slide_in_right" app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left" app:popExitAnim="@anim/slide_out_right" /> -
animation 10% DynamicAnimation A N D R O I D
animation +10% -
animation 10% FlingAnimation FlingAnimation(view, DynamicAnimation.TRANSLATION_X) .setStartVelocity(5000f) .setFriction(1f) .setMinValue(0f) .setMaxValue(1920f) .start()
Link: https://github.com/simonnorberg/fling-animation-demo -
animation 10% DynamicAnimation.ViewProperty ViewProperty TRANSLATION_X = new ViewProperty("translationX") ViewProperty TRANSLATION_Y
= new ViewProperty("translationY") ViewProperty TRANSLATION_Z = new ViewProperty("translationZ") ViewProperty SCALE_X = new ViewProperty("scaleX") ViewProperty SCALE_Y = new ViewProperty("scaleY") ViewProperty ROTATION = new ViewProperty("rotation") ViewProperty ROTATION_X = new ViewProperty("rotationX") ViewProperty ROTATION_Y = new ViewProperty("rotationY") ViewProperty X = new ViewProperty("x") ViewProperty Y = new ViewProperty("y") ViewProperty Z = new ViewProperty("z") ViewProperty ALPHA = new ViewProperty("alpha") ViewProperty SCROLL_X = new ViewProperty("scrollX") ViewProperty SCROLL_Y = new ViewProperty("scrollY") -
animation 10% SpringAnimation val spring = SpringAnimation( view, DynamicAnimation.TRANSLATION_X, 300f)
.withSpringForceProperties { dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY stiffness = SpringForce.STIFFNESS_LOW } spring.start() -
animation 10% SpringAnimation val spring = SpringAnimation( view, DynamicAnimation.TRANSLATION_X, 300f)
.withSpringForceProperties { dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY stiffness = SpringForce.STIFFNESS_LOW } spring.start() -
animation 10% SpringAnimation val spring = SpringAnimation( view, DynamicAnimation.TRANSLATION_X) .withSpringForceProperties
{ dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY stiffness = SpringForce.STIFFNESS_LOW } spring.animateToFinalPosition(300f) -
animation 10% SpringAnimation val spring = SpringAnimation( view, DynamicAnimation.TRANSLATION_X) .withSpringForceProperties
{ dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY stiffness = SpringForce.STIFFNESS_LOW } spring.animateToFinalPosition(300f) spring.animateToFinalPosition(0f) -
animation 10% SpringAnimation val spring = SpringAnimation( view, DynamicAnimation.TRANSLATION_X) .withSpringForceProperties
{ dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY stiffness = SpringForce.STIFFNESS_LOW } spring.animateToFinalPosition(300f) spring.animateToFinalPosition(0f) spring.animateToFinalPosition(500f) -
animation 10% fun View.spring( property: ViewProperty, stiffness: Float = 500f,
damping: Float = SpringForce.DAMPING_RATIO_NO_BOUNCY, startVelocity: Float? = null ): SpringAnimation { val key = getKey(property) var springAnim = getTag(key) as? SpringAnimation? if (springAnim == null) { springAnim = SpringAnimation(this, property) setTag(key, springAnim) } springAnim.spring = (springAnim.spring ?: SpringForce()).apply { this.dampingRatio = damping this.stiffness = stiffness SpringAnimation -
animation 10% fun View.spring( property: ViewProperty, stiffness: Float = 500f,
damping: Float = SpringForce.DAMPING_RATIO_NO_BOUNCY, startVelocity: Float? = null ): SpringAnimation { val key = getKey(property) var springAnim = getTag(key) as? SpringAnimation? if (springAnim == null) { springAnim = SpringAnimation(this, property) setTag(key, springAnim) } springAnim.spring = (springAnim.spring ?: SpringForce()).apply { this.dampingRatio = damping this.stiffness = stiffness } startVelocity?.let { springAnim.setStartVelocity(it) } return springAnim } SpringAnimation -
animation 10% stiffness: Float = 500f, damping: Float = SpringForce.DAMPING_RATIO_NO_BOUNCY,
startVelocity: Float? = null ): SpringAnimation { val key = getKey(property) var springAnim = getTag(key) as? SpringAnimation? if (springAnim == null) { springAnim = SpringAnimation(this, property) setTag(key, springAnim) } springAnim.spring = (springAnim.spring ?: SpringForce()).apply { this.dampingRatio = damping this.stiffness = stiffness } startVelocity?.let { springAnim.setStartVelocity(it) } return springAnim } SpringAnimation Link: https://github.com/android/plaid/.../SpringUtils.kt#L31 -
animation 10% class SpringItemAnimator : DefaultItemAnimator() { override fun animateMove(...):
Boolean { val view = holder.itemView val fromX = fromViewX + holder.itemView.translationX.toInt() val fromY = fromViewY + holder.itemView.translationY.toInt() endAnimation(holder) val deltaX = toViewX - fromX val deltaY = toViewY - fromY ... if (deltaX != 0) { view.translationX = (-deltaX).toFloat() } if (deltaY != 0) { view.translationY = (-deltaY).toFloat() } SpringItemAnimator -
animation 10% override fun animateMove(...): Boolean { val view =
holder.itemView val fromX = fromViewX + holder.itemView.translationX.toInt() val fromY = fromViewY + holder.itemView.translationY.toInt() endAnimation(holder) val deltaX = toViewX - fromX val deltaY = toViewY - fromY ... if (deltaX != 0) { view.translationX = (-deltaX).toFloat() } if (deltaY != 0) { view.translationY = (-deltaY).toFloat() } pendingMoves.add(holder) return true } override fun runPendingAnimations() { super.runPendingAnimations() SpringItemAnimator -
animation 10% if (deltaY != 0) { view.translationY = (-deltaY).toFloat()
} pendingMoves.add(holder) return true } override fun runPendingAnimations() { super.runPendingAnimations() ... if (pendingMoves.isNotEmpty()) { for (i in pendingMoves.indices.reversed()) { moveItem(pendingMoves.removeAt(i)) } } } private fun moveItem(holder: RecyclerView.ViewHolder) { val springX = holder.itemView.spring(TRANSLATION_X) val springY = holder.itemView.spring(TRANSLATION_Y) ... SpringItemAnimator -
animation 10% if (pendingMoves.isNotEmpty()) { for (i in pendingMoves.indices.reversed()) {
moveItem(pendingMoves.removeAt(i)) } } } private fun moveItem(holder: RecyclerView.ViewHolder) { val springX = holder.itemView.spring(TRANSLATION_X) val springY = holder.itemView.spring(TRANSLATION_Y) ... springX.animateToFinalPosition(0f) springY.animateToFinalPosition(0f) ... } } SpringItemAnimator Link: https://github.com/android/plaid/.../SlideInItemAnimator.kt -
animation 10% Transition Animation A N D R O I
D animation +10% -
animation 10% Transition <!-- res/transition/auto_transition.xml --> <autoTransition /> val transition
= TransitionInflater.from(it.context) .inflateTransition(R.transition.auto_transition) TransitionManager.beginDelayedTransition(container, transition) outerBackground.visibility = View.VISIBLE innerBackground.visibility = View.VISIBLE -
animation 10% Transition <autoTransition /> = <transitionSet android:transitionOrdering="sequential"> <fade android:fadingMode="fade_out"
/> <changeBounds /> <fade android:fadingMode="fade_in" /> </transitionSet> -
animation 10% TransitionInflater Link: http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/ transition/TransitionInflater.java#148 • res/transition/{file_name}.xml <autoTransition />
<fade /> <changeBounds /> <slide /> <explode /> <changeImageTransform /> <changeTransform /> <changeClipBounds /> <transitionSet /> ... -> AutoTransition -> Fade -> ChangeBounds -> Slide -> Explode -> ChangeImageTransform -> ChangeTransform -> ChangeClipBounds -> TransitionSet ... -
animation 10% • TransitionValuesח View ё৬ View IDী ӝ߈ೞৈ ػ.
Transition public abstract class Transition { public abstract void captureStartValues( TransitionValues transitionValues); public abstract void captureEndValues( TransitionValues transitionValues); protected void animate(Animator animator) { ... } } -
animation 10% Shared Elements // Between activities val activityOptions =
ActivityOptionsCompat.makeSceneTransitionAnimation( activity, Pair.create(view, view.transitionName) ) ActivityCompat.startActivity(context, intent, activityOptions.toBundle()) // Between fragments fragmentManager.beginTransaction() .addSharedElement(view, view.transitionName) .replace(...) .setReorderingAllowed(true) .commit() -
animation 10% Shared Elements // Between activities val activityOptions =
ActivityOptionsCompat.makeSceneTransitionAnimation( activity, Pair.create(view, view.transitionName) ) ActivityCompat.startActivity(context, intent, activityOptions.toBundle()) // Between fragments fragmentManager.beginTransaction() .addSharedElement(view, view.transitionName) .replace(...) .setReorderingAllowed(true) .commit() -
animation 10% Shared Elements: Navigation // Navigation: Between activities val
extras = ActivityNavigatorExtras( ActivityOptionsCompat.makeSceneTransitionAnimation( activity, Pair.create(view, view.transitionName) ) ) findNavController().navigate(actionToDetail(), extras) // Navigation: Between fragments val extras = FragmentNavigatorExtras( view to view.transitionName ) findNavController().navigate(actionToDetail(), extras) -
animation 10% Shared Elements // In Kotlin view.transitionName = context.getString(R.string.transition_name)
<!-- In XML --> <View android:id="@+id/view" ... android:transitionName="@string/transition_name" /> -
animation 10% Shared Elements <!-- res/transition/move_shared_element.xml --> <transitionSet> <changeBounds />
<targets> <target android:targetId="@id/view" /> </targets> </transitionSet> -
animation 10% Shared Elements <!-- res/transition/move_shared_element.xml --> <transitionSet> <changeBounds />
<arcMotion /> <targets> <target android:targetId="@id/view" /> </targets> </transitionSet> -
animation 10% Shared Elements class MasterFragment : Fragment() class DetailFragment
: Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sharedElementEnterTransition = ...(R.transition.move_shared_element) sharedElementReturnTransition = ...(R.transition.move_shared_element) } } -
animation 10% Shared Elements class MasterFragment : Fragment() class DetailFragment
: Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sharedElementEnterTransition = ...(R.transition.move_shared_element) sharedElementReturnTransition = ...(R.transition.move_shared_element) } } -
animation 10% Shared Elements class MasterFragment : Fragment() { override
fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) exitTransition = ...(R.transition.move) reenterTransition = ...(R.transition.move) } } class DetailFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enterTransition = ...(R.transition.move) returnTransition = ...(R.transition.move) sharedElementEnterTransition = ...(R.transition.move_shared_element) sharedElementReturnTransition = ...(R.transition.move_shared_element) } } -
animation 10% Shared Elements <!-- res/transition/move.xml --> <transitionSet> <explode />
<changeBounds /> </transitionSet> -
animation 10% Shared Elements <!-- res/transition/move.xml --> <transitionSet> <explode />
<changeBounds> <targets> <target android:excludeId="@id/view" /> </targets> </changeBounds> </transitionSet> -
animation 10% Shared Elements <!-- res/transition/move.xml --> <transitionSet> <explode> <targets>
<target android:excludeId="@id/appBar" /> </targets> </explode> <changeBounds> <targets> <target android:excludeId="@id/view" /> </targets> </changeBounds> </transitionSet> -
animation 10% Shared Elements <!-- res/transition/move.xml --> <transitionSet android:transitionOrdering="sequential"> <explode>
<targets> <target android:excludeId="@id/appBar" /> </targets> </explode> <changeBounds> <targets> <target android:excludeId="@id/view" /> </targets> </changeBounds> </transitionSet> -
animation 10% • ߸ചೞח • Drawable Animation • CustomView
+ Animator • ח ױੌ • ViewPropertyAnimator • DynamicAnimation Summary Link: https://github.com/fornewid/android-animation-10p-more • ೣԋ ח ٜ • Animator (+ lerp) • Transition • ചݶ ജ • View Animation • Transition (+ SharedElements) -
animation 10% • https://developer.android.com/training/animation • ⚽ https://github.com/android/animation • https://github.com/android/plaid •
https://github.com/google/iosched • Android Performance Patterns References -
animation 10% Q & A A N D R O
I D animation +10%