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

XML details for Android UI

XML details for Android UI

2020 NAVER TECH CONCERT에서 발표한 내용입니다.
http://techcon.naver.com/
https://github.com/fornewid/TECHCON-2020-Sample

Sungyong An

August 19, 2020
Tweet

More Decks by Sungyong An

Other Decks in Programming

Transcript

  1. NAVER WEBTOON | 안성용 놓치기쉬운 안드로이드UI디테일살펴보기

  2. Java ?

  3. Java/Kotlin/XML/C++ . 요즘에는대부분Kotlin으로개발한다. 오늘은이부분을중점으로살펴봅니다.

  4. 안드로이드에서 XML은 어디에 사용할까? - 애니메이션 - 이미지 - UI

    레이아웃 - 메뉴 - 앱 탐색 그래프 - 리소스 (문자열, 크기, 색상 등) … 다양한 곳에 사용하고 있다! 어떻게 사용될까? 개발할때,조금더신경쓰면좋을내용을다뤄봅시다.
  5. 가끔버튼이잘안눌러지는부분이있다. 1

  6. 버튼이 잘 안 눌러지는 이유 주로 이미지 버튼에서 발생하는데, View

    영역이 손가락으로 누를 수 있을만큼 적절하게 확보되지 않는 것이 원인이다. <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="24dp" android:src="@drawable/ic_search" ... 아이콘은24x24dp크기
  7. Link: https://material.io/design/usability/accessibility.html#layout-and-typography Material Design에서는 최소 48 x 48 dp 만큼의

    터치영역을 확보하는 것을 권장한다.
  8. 버튼이 잘 안 눌러지는 이유 Margin은 View 터치 영역으로 간주되지

    않는다. 디자인 가이드를 맹목적으로 따르지 말고 사용성을 고려하여 터치영역을 적절히 확보하는 것이 좋다. <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="12dp" android:padding="12dp" android:src="@drawable/ic_search" ... XML코드한줄에도큰차이가발생할수있다!
  9. 목표 놓치기쉬운 안드로이드UI디테일살펴보기

  10. 목표 놓치기쉬운 안드로이드UI관련 XML디테일살펴보기

  11. 클릭 효과가 이미지 아래에 그려져서 보이지 않는 경우가 있다. 2

  12. 버튼이 안 눌러지는 것처럼 보이는 이유 이럴 때는 foreground와 같은

    속성을 사용하여 해결할 수 있다.
  13. 버튼이 안 눌러지는 것처럼 보이는 이유 <androidx.constraintlayout.widget.ConstraintLayout android:background="?selectableItemBackground"> <androidx.constraintlayout.widget.ConstraintLayout android:foreground="?selectableItemBackground">

    안드로이드 앱은 OS 버전에 따라 API가 지원되지 않을 수 있다. 이 때는 적절한 대안을 만들면 된다.
  14. 버튼이 안 눌러지는 것처럼 보이는 이유 <FrameLayout android:foreground="?selectableItemBackground"> 그런데 FrameLayout에서는

    API 21에서도 foreground 속성이 잘 동작한다. 도대체 왜?
  15. Link: http://androidxref.com/5.0.0_r2/.../android/widget/FrameLayout.java L 까지는 FrameLayout의 속성이었다.

  16. Link: http://androidxref.com/6.0.0_r1/.../android/view/View.java M 부터는 View의 속성으로 변경되었다.

  17. 버튼이 안 눌러지는 것처럼 보이는 이유 <FrameLayout android:foreground="?selectableItemBackground"> 사용하는 View(=FrameLayout)에

    있는 속성만 아니라 상속하는 상위 View 속성도 사용할 수 있다는 것을 알 수 있다.
  18. <LinearLayout> <View /> <View /> <LinearLayout> <ImageView /> <TextView />

    </LinearLayout> </LinearLayout> 레이아웃 XML이 화면에 어떻게 그려지게 되는지 과정이 궁금하다면? 3
  19. LayoutInflater override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } @Override

    public void setContentView(int resId) { ... LayoutInflater.from(mContext).inflate(resId, ...); ... }
  20. Link: http://androidxref.com/9.0.0_r3/.../android/view/LayoutInflater.java 단순히 레이아웃 XML을 파싱해서 View 구현체로 변환해주는 방식이다.

  21. LayoutInflater 따라서 XML 코드는 실제 구현체로 변환해주는 단계가 반드시 필요하다.

  22. LayoutInflater <View /> <View /> <View /> View는 선언된 순서대로

    그려진다. 레이아웃이 겹쳐지는 경우에는 고민이 필요하다. (ex. RecyclerView + Button 등)
  23. LayoutInflater Z축을 변경하는 방법을 사용할 수도 있다. 단, BG가 있으면

    그림자 효과가 나타나니 주의할 것. <View /> <View android:elevation="12dp" android:translationZ="12dp" /> <View />
  24. 4 android:tint 속성을 이용하여, 상태에 따라 아이콘 색상을 다르게 보여줄

    수 있다. <ImageView android:src="@drawable/ic_heart" android:tint="@color/selector_heart" ... /> <selector> <item android:color="@color/alert" /> </selector>
  25. AppCompat 하지만 API 21 미만에서는 런타임에 오류가 발생한다. <ImageView android:src="@drawable/ic_heart"

    android:tint="@color/selector_heart" ... /> 위 속성에서 발생한 문제다.
  26. Link: http://androidxref.com/5.0.0_r2/.../android/widget/ImageView.java L 부터는 ColorStateList를 허용한다.

  27. Link: http://androidxref.com/4.4_r1/.../android/widget/ImageView.java KK 까지는 Color 만 허용한다.

  28. AppCompat 그렇다면 어떻게 해결할 수 있을까? <ImageView android:src="@drawable/ic_heart" android:tint="@color/selector_heart" ...

    />
  29. AppCompat <ImageView android:src="@drawable/ic_heart" app:tint="@color/selector_heart" ... /> 플랫폼 속성 대신 커스텀

    속성으로 변경하면 API 21 미만에서도 ColorStateList가 동작한다. 무슨 차이가 있는 걸까?
  30. <declare-styleable name="ImageView"> <!-- The tinting color for the image. By

    default, the tint will blend using SRC_ATOP mode. Please note that for compatibility reasons, this is NOT consistent with the default SRC_IN tint mode used by {@link android.widget.ImageView#setImageTintList} and by similar tint attributes on other views. --> <attr name="tint" format="color" /> </declare-styleable> <declare-styleable name="AppCompatImageView"> <!-- Tint to apply to the image source. --> <attr format="color" name="tint"/> </declare-styleable> android:tint 는 ImageView의 속성이고, app:tint 는 AppCompatImageView의 속성이다.
  31. Link: https://github.com/androidx/.../androidx/appcompat/widget/AppCompatImageHelper.java AppCompatImageView는 ColorStateList를 허용한다. 따라서 API level에 관계없이 잘

    동작한다.
  32. AppCompat <ImageView android:src="@drawable/ic_heart" app:tint="@color/selector_heart" ... /> 잠깐! ImageView가 어떻게 AppCompatImageView가

    된걸까요? class MainActivity : AppCompatActivity() 비밀(?)은 상속에 숨어 있습니다.
  33. AppCompatActivity를 상속하면 내부적으로 LayoutInflater에 Factory를 설정해준다.

  34. LayoutInflater를 통해 레이아웃 XML을 View 구현체로 변환하려고 할 때, 우선적으로

    Factory를 이용한다.
  35. Link: http://androidxref.com/.../android/view/LayoutInflater.java (이 부분은 MDC에서 사용됩니다.) AppCompatViewInflater 내부에서 "ImageView" 태그는

    AppCompatImageView로 생성해준다.
  36. AppCompat과 MDC <style name="AppTheme" parent="Theme.AppCompat.Light" /> 이번에는 Button을 살펴봅시다.

  37. AppCompat과 MDC <style name="AppTheme" parent="Theme.MaterialComponents.Light" /> 상속하는 테마에 따라 Button

    모양이 바뀝니다?
  38. MDC 테마에서 ViewInflater를 교체하여 "Button" 태그가 MaterialButton으로 생성된다.

  39. AppCompat과 MDC 상속 테마에 따라 View 구현체가 달라진다. AS 4.0

    까지는 기본 프로젝트를 생성하면 AppCompat을 상속하지만, AS 4.1 부터는 MDC를 상속하는 것으로 변경되었으니 주의할 것!
  40. 5 Style과 Theme에 대해서 조금 더 알아보자.

  41. Style과 Theme <LinearLayout android:theme="@style/AppTheme"> <Button style="@style/Widget.AppCompat.Button.Colored" android:text=" TOP " />

    <androidx.constraintlayout.widget.ConstraintLayout android:theme="@style/AppTheme.Grayscale"> <Button style="@style/Widget.AppCompat.Button.Colored" android:text=" LEFT " /> <Button android:text=" RIGHT "/> </androidx.constraintlayout.widget.ConstraintLayout> <Button android:text=" BOTTOM " /> </LinearLayout> <style name="AppTheme" parent="Theme.AppCompat.Light"> <item name="colorAccent">#FF2C2C</item> </style> <style name="AppTheme.Grayscale"> <item name="colorAccent">#000000</item> </style>
  42. 버튼 Style 내부 리소스에서 현재 Theme의 colorAccent 속성을 참조한다.

  43. Style과 Theme <LinearLayout android:theme="@style/AppTheme"> ... <Button android:text=" BOTTOM " />

    </LinearLayout> 아무것도설정하지않은버튼의기본색상은뭘까? 즉, Theme는 자신을 포함하여 하위 View에도 영향을 준다.
  44. 현재 Theme의 buttonStyle 속성에서 배경 이미지를 참조한다.

  45. Theme에서 기본 Style을 정의해두고 있고, Style은 단일 View에 적용된다는 것을

    알 수 있다.
  46. Style과 Theme <manifest> <application android:theme="@style/AppTheme"> <activity android:theme="@style/AppTheme.Specific" /> </application> </manifest>

    상황에 따라 화면 단위로 Theme를 적절히 설정해주고, 세세하게 변경이 필요하면 해당 View에 Style을 설정하는 것을 추천한다.
  47. 6 <LinearLayout android:background="#ffffff"> ... </LinearLayout> 혹시 배경이 흰색이 아니라서 root에

    background를 설정한 적이 있다면?
  48. WindowBackground 이 경우, 개발자 옵션으로 overdraw를 확인해보면 화면의 모든 영역이

    불필요하게 한번씩 더 그려진다. background 속성을 사용하지 않고, 기본 배경색을 변경할 수는 없을까?
  49. WindowBackground <style name="AppTheme" parent="..."> <item name="android:windowBackground">#ffffff</item> </style> <LinearLayout> ... </LinearLayout>

    windowBackground 속성을 이용하면 이 부분을 개선할 수 있다. 어디에 그려지는지는 직접 찾아보면 재미있겠죠?
  50. 7 <shape> drawable 많이 쓰시나요? <shape android:shape="rectangle"> <corners android:radius="24dp" />

    <solid android:color="#DDDDDD" /> <stroke android:width="4dp" android:color="#CCCCCC" /> </shape>
  51. 생각보다 강력한 Drawable <ripple android:color="#FF2C2C"> <item android:drawable="@drawable/shape" /> </ripple> shape

    drawable에 Ripple 효과를 쉽게 추가할 수 있다.
  52. 생각보다 강력한 Drawable <ripple android:color="#FF2C2C"> <item android:drawable="@drawable/shape" /> <item android:id="@android:id/mask"

    android:drawable="@drawable/ic_bug" /> </ripple> Ripple 효과의 영역을 다르게 설정할 수도 있다. (= mask)
  53. 생각보다 강력한 Drawable 조금 복잡한 조건의 배터리 UI를 만들어보자! A.

    0 ~ 100% 단계가 1% 단위로 보인다. B. 1 ~ 20% 단계에서는 빨간색으로 보인다. C. 0 ~ 1% 단계에서는 경고 아이콘(❗)이 보인다. D. 충전중에는 0 ~ 20% 단계에서도 검정색으로 보이고, 충전 아이콘(⚡)이 함께 보인다. Java 또는 Kotlin 코드 없이도 이미지 5장만으로 구현할 수 있다면? empty low full alert charge
  54. 생각보다 강력한 Drawable <selector> <item android:state_activated="true"> <layer-list> <item android:drawable="@drawable/empty" />

    <item> <clip android:drawable="@drawable/full" android:clipOrientation="vertical" android:gravity="bottom" /> </item> <item android:drawable="@drawable/charge" /> ⚡ </layer-list> </item> <item /> <!-- Normal --> </selector> selector, layer-list, level-list, clip 등을 혼합하면 가능하다. 먼저 충전중 상태부터 구현해보자. 임시로 Activated 상태를 충전중이라 가정했다. 이미지를 겹쳐 보여줘야 할 때 사용한다. ProgressBar 처럼 이미지를 단계 별로 자를 때 사용한다.
  55. 생각보다 강력한 Drawable <level-list> <item android:minLevel="0" android:maxLevel="100"> <layer-list /> ❗

    </item> <item android:minLevel="101" android:maxLevel="2000"> <layer-list /> </item> <item android:minLevel="2001" android:maxLevel="10000"> <layer-list /> </item> </level-list> 다음으로 기본 상태를 살펴보자. level-list를 사용하여 각 level에 맞는 이미지 조합을 보여주자. 참고로 level은 0 ~ 10000 단계를 가진다.
  56. 생각보다 강력한 Drawable <ImageView android:src="@drawable/ic_battery" /> val level: Int =

    ... // 0 ~ 100 val batteryView: ImageView = ... batteryView.setImageLevel(level * 100) batteryView.isActivated = isCharging setActivated, setImageLevel API를 호출하면 구현 완료! Drawable 구현체가 궁금하다면?
  57. 재밌게도 "shape"는 GradientDrawable이 구현체다.

  58. ImageView의 Level API는 Drawable의 Level API를 호출해주는 형태다.

  59. 생각보다 강력한 Drawable 이외에도 다양한 Drawable 구현체가 있으므로, 직접 살펴보는

    것을 추천한다!
  60. 8 <resources> <color name="background">#F2F2F2</color> <color name="icon">#000000</color> </resources> res/values/colors.xml 다크모드를 지원하는

    방법은?
  61. Resource Qualifier 색상 외에 이미지 등 다른 리소스도 -night qualifier를

    사용할 수 있다. <resources> <color name="background">#202020</color> <color name="icon">#FFFFFF</color> </resources> res/values-night/colors.xml res/drawable/ic_drink.xml res/drawable-night/ic_drink.xml
  62. 자세한 내용은 공식 문서를 참고하자.

  63. Resource Qualifier 이외에도 다양한 Qualifier를 이용하여, 안드로이드의 파편화된 기기 생태계에

    적응할 수 있다. res/layout-land/activity_main.xml res/values-v23/styles.xml res/values-ko/string.xml res/values-sw600dp/dimens.xml
  64. 이런 목록형 UI를 구현할 때의 과정을 생각해본다면 레이아웃 변경사항을 확인하려고

    매번 빌드하지는 않았나? 9
  65. tools 이용하기 레이아웃 XML을 작성하면 왼쪽 화면처럼 보인다. <androidx.constraintlayout.widget.ConstraintLayout> <androidx.appcompat.widget.Toolbar

    /> <androidx.recyclerview.widget.RecyclerView /> </androidx.constraintlayout.widget.ConstraintLayout> 빌드하기 전에 미리 볼 수 있는 방법이 있다면?
  66. tools 이용하기 <RecyclerView android:orientation="vertical" app:layoutManager="...LinearLayoutManager" tools:itemCount="3" tools:listitem="@layout/item" /> <ConstraintLayout> <CircleImageView

    /> <TextView /> <ImageView /> </ConstraintLayout> tools를 이용하여, 목록의 개수/방향 등을 설정할 수 있다.
  67. tools 이용하기 <ConstraintLayout> <CircleImageView tools:src="@tools:sample/avatars" /> <TextView tools:text="@tools:sample/full_names" /> <ImageView

    /> </ConstraintLayout> 미리 정의된 tools 샘플 데이터를 이용하면 좀 더 실제처럼 보인다.
  68. tools 이용하기 <ConstraintLayout> <CircleImageView /> <TextView tools:text="@sample/labels" /> <ImageView />

    </ConstraintLayout> {project}/{module}/sampledata/labels 샘플 데이터를 직접 정의할 수도 있다. tools를 이용하여 빌드 횟수를 줄이고, 생산성을 높여보자. I am debugging. My foldable phone. NAVER TECHCON 2020 ⷎ
  69. 정리 margin, padding tools Attribute LayoutInflater AppCompat, MDC WindowBackground Drawable

    Qualifier XML 오늘은 안드로이드를 구성하는 XML 코드에 대해서 살펴봤습니다. 어떤가요. 안드로이드 앱 개발 재미있어 보이지 않나요? Style, Theme
  70. 감사합니다 Link: https://github.com/fornewid/TECHCON-2020-Sample