Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Android UI Animation 들이붓기

Sungyong An
September 14, 2020

Android UI Animation 들이붓기

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

Sungyong An

September 14, 2020
Tweet

More Decks by Sungyong An

Other Decks in Programming

Transcript

  1. 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>
  2. 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>
  3. 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>
  4. Loading... 하지만 컨텐츠가 표시되기 전에 항상 로딩 UI가 보이면 조금

    과한 느낌을 줄 수 있다. ContentLoadingProgressBar는 컨텐츠를 불러오는 속도가 느릴 때만 로딩 UI를 보여준다. (예. API 응답이 느린 경우)
  5. 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); } } }
  6. Progress ProgressBar와 Drawable을 이용하여, Progress Animation를 손쉽게 만들 수 있다.

    재생, 녹음/녹화, 다운로드 등에 사용할 수 있다. +
  7. 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>
  8. 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>
  9. Frame Animation <ImageView /> val drawable: AnimationDrawable = ... imageView.setImageDrawable(drawable)

    drawable.start() drawable.stop() Activity나 Fragment 등의 Lifecycle에 따라 Animation 시작과 종료를 관리해줘야 한다.
  10. AnimatedStateListDrawable View 상태에 따라 Drawable을 보여줄 때, 각 상태 사이에

    Animation을 보여줄 수 있다. 예를 들어 BottomNavigationView, TabLayout 처럼 selected 상태를 지원하거나, 좋아요, 즐겨찾기 등 checked 상태를 지원하는 상황에 사용하기 적합하다.
  11. 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>
  12. 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>
  13. 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
  14. + 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>
  15. + 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>
  16. + 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>
  17. + 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>
  18. <!-- 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
  19. <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
  20. 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
  21. 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
  22. 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>
  23. 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>
  24. 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>
  25. StateListAnimator <TextView android:id="@+id/button" ... android:stateListAnimator="@animator/sla" /> 여기서는 배경색 Animation에 사용했지만,

    Translation / Scale 처리에 사용하는 경우가 일반적이다. 예를 들어, Button, FAB 클릭 효과를 구현할 때 주로 쓰인다.
  26. SharedElements Activity 혹은 Fragment 간에 화면을 전환할 때, 콘텐츠가 이어지는

    것처럼 보여줄 수 있다. 프로필 사진을 확대해서 보거나, 이미지 목록형 화면에서 사용하기에 적합하다.
  27. 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() )
  28. 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) }
  29. 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()
  30. TransitionManager 상태에 따라 Layout이 변경되어야 하는 경우, TransitionManager를 이용할 수

    있다. ConstraintLayout을 사용하고 있다면 이 때, 레이아웃 전체를 변경하는 것보다 Guideline 등을 이용하는 것이 편리하다. 사진 편집 모드나 폴더블 상태 등에서 사용하면 좋을 듯 하다.
  31. 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)
  32. 이외에도… 아래 Google I/O 세션 영상을 보는 것을 추천한다. Link:

    Motional Intelligence: Build Smarter Animations (Google I/O'19) Animated Slide