Android UI Animation 들이붓기

826c34bb94d4fe786230bd95c5bdc409?s=47 SOUP
September 14, 2020

Android UI Animation 들이붓기

드로이드나이츠2020에서 발표한 자료입니다.
https://droidknights.github.io/2020/
https://github.com/fornewid/DroidKnights-2020-Sample

826c34bb94d4fe786230bd95c5bdc409?s=128

SOUP

September 14, 2020
Tweet

Transcript

  1. Speaker l Android UI Animation 들이붓기 안성용 / 네이버웹툰

  2. 난이도 시간 유지보수 기피하는 이유?

  3. None
  4. Animation UseCase 손쉽게 구현하기 목표

  5. Loading... Animation

  6. Loading... ProgressBar와 Drawable을 이용하여, 브랜드가 들어간 로딩 UI를 손쉽게 만들

    수 있다. +
  7. Loading... <!-- res/drawable/loading.xml —> <layer-list> <item android:gravity="center"> <rotate android:drawable="@drawable/loading_outer" android:pivotX="50%"

    android:pivotY="50%" android:fromDegrees="0" android:toDegrees="360" /> </item> <item android:drawable="@drawable/loading_inner" android:gravity="center" /> </layer-list>
  8. Loading... <!-- res/drawable/loading.xml —> <layer-list> <item android:gravity="center"> <rotate android:drawable="@drawable/loading_outer" android:pivotX="50%"

    android:pivotY="50%" android:fromDegrees="0" android:toDegrees="360" /> </item> <item android:drawable="@drawable/loading_inner" android:gravity="center" /> </layer-list>
  9. Loading... <!-- res/drawable/loading.xml —> <layer-list> <item android:gravity="center"> <rotate android:drawable="@drawable/loading_outer" android:pivotX="50%"

    android:pivotY="50%" android:fromDegrees="0" android:toDegrees="360" /> </item> <item android:drawable="@drawable/loading_inner" android:gravity="center" /> </layer-list>
  10. Loading... <ProgressBar style="@style/Widget.AppCompat.ProgressBar" ... android:indeterminateDrawable="@drawable/loading" android:indeterminateDuration="1000" />

  11. Loading... 하지만 컨텐츠가 표시되기 전에 항상 로딩 UI가 보이면 조금

    과한 느낌을 줄 수 있다. ContentLoadingProgressBar는 컨텐츠를 불러오는 속도가 느릴 때만 로딩 UI를 보여준다. (예. API 응답이 느린 경우)
  12. ContentLoadingProgressBar 동작 방식은 단순하다. 500ms 이내에 불러오면 로딩 UI를 보여주지

    않고, 한번 보여진 UI는 최소 500ms동안 보여준다. 이를 참고하여, 각 서비스에 맞는 Widget을 구현해볼 수 있다. public class ContentLoadingProgressBar extends ProgressBar { private static final int MIN_SHOW_TIME = 500; // ms private static final int MIN_DELAY = 500; // ms public synchronized void show() { postDelayed(mDelayedShow, MIN_DELAY); } public synchronized void hide() { long diff = System.currentTimeMillis() - mStartTime; if (diff >= MIN_SHOW_TIME || mStartTime == -1) { setVisibility(View.GONE); } else { postDelayed(mDelayedHide, MIN_SHOW_TIME - diff); } } }
  13. Progress ProgressBar와 Drawable을 이용하여, Progress Animation를 손쉽게 만들 수 있다.

    재생, 녹음/녹화, 다운로드 등에 사용할 수 있다. +
  14. Progress <!-- res/drawable/progress_background.xml --> <layer-list> <item> <shape android:shape="ring" android:thickness="5dp" android:useLevel="false">

    <solid android:color="@color/white" /> </shape> </item> <item android:drawable="@drawable/ic_android" android:gravity="center" /> </layer-list>
  15. Progress <!-- res/drawable/progress.xml --> <rotate android:fromDegrees="270" android:toDegrees="270"> <shape android:shape="ring" android:thickness="5dp"

    android:useLevel="true"> <solid android:color="@color/colorAccent" /> </shape> </rotate>
  16. Progress <ProgressBar style="@style/Widget.AppCompat.ProgressBar.Horizontal" ... android:indeterminate="false" android:progressDrawable="@drawable/progress" android:background="@drawable/progress_background" android:max="500" tools:progress="200" />

    val progressBar: ProgressBar = ... progressBar.progress = currentProgress
  17. Frame Animation 로딩 UI를 Frame Animation으로 보여주고 싶다면 AnimationDrawable을 이용하여

    손쉽게 만들 수 있다. " " " " "
  18. Frame Animation <!-- res/drawable/frame_loading.xml --> <animation-list android:oneshot="false"> <item android:drawable="@drawable/frame_loading_01" android:duration="500"

    /> <item android:drawable="@drawable/frame_loading_02" android:duration="500" /> <item android:drawable="@drawable/frame_loading_03" android:duration="500" /> <item android:drawable="@drawable/frame_loading_04" android:duration="500" /> <item android:drawable="@drawable/frame_loading_05" android:duration="500" /> </animation-list>
  19. Frame Animation <ImageView /> val drawable: AnimationDrawable = ... imageView.setImageDrawable(drawable)

    drawable.start() drawable.stop() Activity나 Fragment 등의 Lifecycle에 따라 Animation 시작과 종료를 관리해줘야 한다.
  20. Frame Animation <com.example.widget.AnimatedImageView android:src="@drawable/frame_loading" /> Link: AnimatedImageView.java 이 때, AnimatedImageView를

    이용하면 XML에 선언하는 형태로 조금 더 편하게 사용할 수 있다.
  21. Notification Icon AnimationDrawable, AnimatedVectorDrawable 등을 사용하면 알림 아이콘에도 애니메이션을 보여줄

    수 있다.
  22. 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> Notification.Builder(...) .setSmallIcon(R.drawable.stat_sys_download) ... .build()
  23. Click!! Animation

  24. AnimatedStateListDrawable View 상태에 따라 Drawable을 보여줄 때, 각 상태 사이에

    Animation을 보여줄 수 있다. 예를 들어 BottomNavigationView, TabLayout 처럼 selected 상태를 지원하거나, 좋아요, 즐겨찾기 등 checked 상태를 지원하는 상황에 사용하기 적합하다.
  25. AnimatedStateListDrawable <selector> <item android:id="@+id/selected" android:drawable="@drawable/ic_battery_100" android:state_selected="true" /> <item android:id="@+id/unselected" android:drawable="@drawable/ic_battery_0"

    android:state_selected="false" /> </selector>
  26. AnimatedStateListDrawable <animated-selector> <item android:id="@+id/selected" android:drawable="@drawable/ic_battery_100" android:state_selected="true" /> <item android:id="@+id/unselected" android:drawable="@drawable/ic_battery_0"

    android:state_selected="false" /> </animated-selector>
  27. AnimatedStateListDrawable <!-- res/drawable/asld_battery.xml --> <animated-selector> <item android:id="@+id/selected" android:drawable="@drawable/ic_battery_100" android:state_selected="true" />

    <item android:id="@+id/unselected" android:drawable="@drawable/ic_battery_0" android:state_selected="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>
  28. AnimatedStateListDrawable <!-- res/drawable/asld_battery.xml --> <animated-selector> <item android:id="@+id/selected" android:drawable="@drawable/ic_battery_100" android:state_selected="true" />

    <item android:id="@+id/unselected" android:drawable="@drawable/ic_battery_0" android:state_selected="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>
  29. AnimatedStateListDrawable <!-- 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" /> ... <item android:drawable="@drawable/ic_battery_100" android:duration="32" /> </animation-list> 32 32 32 32 32 32 32 32
  30. AnimatedStateListDrawable <ImageView android:src="@drawable/asld_battery" /> imageView.setOnClickListener { it.isSelected = true //

    or false }
  31. + AnimatedVectorDrawable Frame Animation을 사용하는 대신 VectorDrawable에 Animation을 구현할 수도

    있다.
  32. + AnimatedVectorDrawable <!-- res/drawable/asld_settings.xml --> <animated-selector> <item android:id="@+id/selected" android:drawable="@drawable/ic_settings" android:state_selected="true"

    /> <item android:id="@+id/unselected" android:drawable="@drawable/ic_settings" android:state_selected="false" /> <transition android:drawable="@drawable/avd_settings_select" android:fromId="@id/unselected" android:toId="@id/selected" /> </animated-selector>
  33. + AnimatedVectorDrawable <!-- res/drawable/asld_settings.xml --> <animated-selector> <item android:id="@+id/selected" android:drawable="@drawable/ic_settings" android:state_selected="true"

    /> <item android:id="@+id/unselected" android:drawable="@drawable/ic_settings" android:state_selected="false" /> <transition android:drawable="@drawable/avd_settings_select" android:fromId="@id/unselected" android:toId="@id/selected" /> </animated-selector>
  34. + AnimatedVectorDrawable 300 600 900 rotation 0 scale scale

  35. + AnimatedVectorDrawable <!-- res/drawable/avd_settings_select.xml --> <animated-vector> <aapt:attr name="android:drawable"> <vector android:width="24dp"

    android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> <group android:name="gear" android:pivotX="12" android:pivotY="12"> <path android:fillColor="#00F2B2" android:pathData="M19.43,12.98c0.04,..." /> </group> </vector> </aapt:attr> </animated-vector>
  36. + AnimatedVectorDrawable <!-- res/drawable/avd_settings_select.xml --> <animated-vector> <aapt:attr name="android:drawable"> <vector android:width="24dp"

    android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> <group android:name="gear" android:pivotX="12" android:pivotY="12"> <path android:fillColor="#00F2B2" android:pathData="M19.43,12.98c0.04,..." /> </group> </vector> </aapt:attr> </animated-vector>
  37. <!-- res/drawable/avd_settings_select.xml --> <animated-vector> <aapt:attr name="android:drawable" /> <target android:name="gear"> <aapt:attr

    name="android:animation"> <set> <objectAnimator android:duration="300" android:propertyName="scaleX" android:valueFrom="1" android:valueTo="0.8" android:valueType="floatType" /> <objectAnimator android:duration="300" android:propertyName="scaleY" android:valueFrom="1" + AnimatedVectorDrawable
  38. <animated-vector> <aapt:attr name="android:drawable" /> <target android:name="gear"> <aapt:attr name="android:animation"> <set> <objectAnimator

    android:duration="300" android:propertyName="scaleX" android:valueFrom="1" android:valueTo="0.8" android:valueType="floatType" /> <objectAnimator android:duration="300" android:propertyName="scaleY" android:valueFrom="1" android:valueTo="0.8" android:valueType="floatType" /> <objectAnimator android:duration="300" android:propertyName="rotation" android:startOffset="300" + AnimatedVectorDrawable
  39. android:duration="300" android:propertyName="scaleY" android:valueFrom="1" android:valueTo="0.8" android:valueType="floatType" /> <objectAnimator android:duration="300" android:propertyName="rotation" android:startOffset="300"

    android:valueFrom="0" android:valueTo="720" android:valueType="floatType" /> <objectAnimator android:duration="300" android:propertyName="scaleX" android:startOffset="600" android:valueFrom="0.8" android:valueTo="1" android:valueType="floatType" /> <objectAnimator android:duration="300" + AnimatedVectorDrawable
  40. android:valueFrom="0" android:valueTo="720" android:valueType="floatType" /> <objectAnimator android:duration="300" android:propertyName="scaleX" android:startOffset="600" android:valueFrom="0.8" android:valueTo="1"

    android:valueType="floatType" /> <objectAnimator android:duration="300" android:propertyName="scaleY" android:startOffset="600" android:valueFrom="0.8" android:valueTo="1" android:valueType="floatType" /> </set> </aapt:attr> </target> </animated-vector> + AnimatedVectorDrawable
  41. + AnimatedVectorDrawable <ImageView android:src="@drawable/asld_settings" /> imageView.setOnClickListener { it.isSelected = true

    }
  42. Link: https://shapeshifter.design/ PathData 간 morph animation을 쉽게 구현할 수 있다.

    Shape Shifter
  43. Ripple Material Design에서 제공하는 Ripple 효과를 구현해보자.

  44. Ripple <!-- res/drawable/ripple.xml --> <ripple android:color="?colorControlHighlight"> <item android:drawable="@drawable/ripple_mask" /> <item

    android:id="@android:id/mask" android:drawable="@drawable/ripple_mask" /> </ripple> <!-- res/drawable/ripple_mask.xml --> <shape android:shape="rectangle"> <corners android:radius="8dp" /> <solid android:color="@color/colorAccent" /> </shape>
  45. Ripple <TextView android:id="@+id/button" ... android:background="@drawable/ripple" />

  46. Ripple <View android:background="?selectableItemBackground" /> <!— or ?selectableItemBackgroundBorderless —> 배경이 투명한

    항목에는 미리 정의된 속성을 사용하여, Ripple 효과를 지원할 수 있다. <!-- res/drawable/item_background_material.xml --> <ripple android:color="?attr/colorControlHighlight"> <item android:id="@id/mask"> <color android:color="@color/white" /> </item> </ripple>
  47. StateListAnimator StateListDrawable과 다르게 View 상태 변경에 따라 Animation을 보여줄 수

    있다.
  48. StateListAnimator <!-- res/animator/sla.xml --> <selector> <item android:state_pressed="true"> <objectAnimator android:duration="200" android:propertyName="backgroundColor"

    android:valueFrom="#00F2B2" android:valueTo="#9900F2B2" android:valueType="colorType" /> </item> <item> <objectAnimator android:duration="200" android:propertyName="backgroundColor" android:valueFrom="#9900F2B2" android:valueTo="#00F2B2" android:valueType="colorType" /> </item> </selector>
  49. StateListAnimator <TextView android:id="@+id/button" ... android:stateListAnimator="@animator/sla" /> 여기서는 배경색 Animation에 사용했지만,

    Translation / Scale 처리에 사용하는 경우가 일반적이다. 예를 들어, Button, FAB 클릭 효과를 구현할 때 주로 쓰인다.
  50. Transition~ Animation

  51. SharedElements Activity 혹은 Fragment 간에 화면을 전환할 때, 콘텐츠가 이어지는

    것처럼 보여줄 수 있다. 프로필 사진을 확대해서 보거나, 이미지 목록형 화면에서 사용하기에 적합하다.
  52. SharedElements <!-- profile_item_photo.xml --> <ImageView android:transitionName="photo" /> <!-- viewer_activity.xml -->

    <ImageView android:transitionName="photo" /> // ProfileActivity.kt val intent = Intent(this, ViewerActivity::class.java) intent.putExtra("resId", resId) ActivityCompat.startActivity( this, intent, ActivityOptionsCompat .makeSceneTransitionAnimation( this, view, view.transitionName ) .toBundle() )
  53. SharedElements // ViewerActivity.kt override fun onCreate(savedInstanceState: Bundle?) { window.sharedElementEnterTransition =

    TransitionSet().apply { interpolator = OvershootInterpolator(0.7f) ordering = TransitionSet.ORDERING_TOGETHER addTransition(ChangeBounds().apply { pathMotion = ArcMotion() }) addTransition(ChangeTransform()) addTransition(ChangeClipBounds()) addTransition(ChangeImageTransform()) } super.onCreate(savedInstanceState) }
  54. View Animation Activity 혹은 Fragment 간에 화면을 전환할 때, 단순한

    Animation을 보여줄 수도 있다.
  55. View Animation // In Activity activity.overridePendingTransition( R.anim.slide_in_right, // enterAnim R.anim.slide_out_left

    // exitAnim ) // In Fragment fragmentManager.beginTransaction() .setCustomAnimations( R.anim.slide_in_right, // enterAnim R.anim.slide_out_left, // exitAnim ) .replace(...) .commit()
  56. View Animation <alpha android:fromAlpha="1.0" android:toAlpha="0.5" />

  57. View Animation <alpha android:interpolator="@android:anim/accelerate_interpolator" android:fromAlpha="1.0" android:toAlpha="0.5" />

  58. View Animation <alpha /> <translate android:fromXDelta="0" android:toXDelta="100" android:fromYDelta="0" android:toYDelta="100" />

  59. View Animation <set > <alpha /> <translate /> </set>

  60. View Animation <set android:shareInterpolator="true" android:duration="1000" android:interpolator="@android:anim/accelerate_interpolator" > <alpha /> <translate

    /> </set>
  61. View Animation <set > <scale /> <rotate /> <alpha />

    <translate /> </set>
  62. View Animation <set > <scale /> <rotate /> <alpha />

    <translate /> <set /> </set>
  63. TransitionManager 상태에 따라 Layout이 변경되어야 하는 경우, TransitionManager를 이용할 수

    있다. ConstraintLayout을 사용하고 있다면 이 때, 레이아웃 전체를 변경하는 것보다 Guideline 등을 이용하는 것이 편리하다. 사진 편집 모드나 폴더블 상태 등에서 사용하면 좋을 듯 하다.
  64. TransitionManager val layout = binding.root val constraintSet = ConstraintSet().apply {

    clone(layout) if (fold) { setGuidelinePercent(R.id.fold_guideline, 0.5f) } else { setGuidelinePercent(R.id.fold_guideline, 1f) } } TransitionManager.beginDelayedTransition(layout) constraintSet.applyTo(layout)
  65. 이외에도… Animator, ViewPropertyAnimator, DynamicAnimation LayoutTransition, CircularReveal, MotionLayout 등 다양한 API들이

    제공되고 있다. Link: Android Animation 11% 더 활용하기
  66. 이외에도… 아래 Google I/O 세션 영상을 보는 것을 추천한다. Link:

    Motional Intelligence: Build Smarter Animations (Google I/O'19) Animated Slide
  67. 감사합니다!