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

Atomic Design in Android

Atomic Design in Android

Applications grow so fast and with a lot of people working on the same project, you mostly lose track of UI elements that you have in place and you start creating them over and over again with different configurations and different design specifications. In result of this, Application gets a lot of UI inconsistency and Designers are never happy about it.

Within this talk, you will learn how to apply one of the most popular approaches, “Atomic Design” to your application. What are the benefits of having the design system? What constraints you should pay attention? What are the complications of creating a design system and how to approach them? How do you solve communication problems? When you should be involved in the process? What are the different perspectives of Designers and Developers? How to apply Atomic Design into Android Ecosystem?

Sooner or later your Designer or Project Manager will approach to you and say “Hey! I think we should have a Design System.” Get ready when that happens.

- Presented in Droidcon Italy (20.04.2018) https://www.youtube.com/watch?v=x17dO8D5Bps

Yahya Bayramoğlu

April 20, 2018
Tweet

More Decks by Yahya Bayramoğlu

Other Decks in Programming

Transcript

  1. Atomic Design - Brad Frost “The thought is that; all

    matter (whether solid, liquid, gas, simple, complex, etc) is comprised of atoms. Those atomic units bond together to form molecules, which in turn combine into more complex organisms to ultimately create all matter in our universe.”
  2. Atomic Design • Atoms; are the basic building blocks of

    matter. • Molecules; are groups of atoms bonded together and are the smallest fundamental units of a compound. • Habitats; are to define attributes and resources. Such as; Color, Font, Size, Icon, Style, Animation, and so on. are provided by the Android framework and built with habitats.
  3. Text Styles What is a text style? • Font •

    Size • Line Spacing • Letter Spacing • Color • Alignment Gravity
  4. Text Styles Keep Gravity out of styles! <LinearLayout … >

    <TextView android:layout_width="match_parent" android:gravity="end" /> </LinearLayout> Since it depends on; • `layout_width` value <LinearLayout … > <TextView android:layout_width="wrap_content" android:gravity="end" /> </LinearLayout>
  5. Text Styles Keep Gravity out of styles! <LinearLayout … >

    <TextView android:layout_width="wrap_content" android:gravity="end" /> </LinearLayout> Since it depends on; • `layout_width` value • where the `gravity` defined <LinearLayout … android:gravity="end" > <TextView android:layout_width="wrap_content"/> </LinearLayout>
  6. Text Styles Keep Gravity out of styles! <FrameLayout … >

    <TextView android:layout_width="wrap_content" android:gravity="end" /> </FrameLayout> Since it depends on; • `layout_width` value • where the `gravity` defined • `layout_gravity` or `gravity` usage <FrameLayout … > <TextView android:layout_width="wrap_content" android:layout_gravity="end" /> </FrameLayout>
  7. Text Styles Keep Gravity out of styles! Since it depends

    on; • `layout_width` value • where the `gravity` defined • `layout_gravity` or `gravity` usage • in which layout it is defined <FrameLayout … > <TextView android:layout_width="wrap_content" android:layout_gravity="end" /> </FrameLayout> <LinearLayout … > <TextView android:layout_width="wrap_content" android:layout_gravity="end" /> </LinearLayout>
  8. Text Styles Custom Typography in Android by Matt Raufman https://goo.gl/cEchMz

    Typesetter https://goo.gl/H1P7yo Line Spacing & Letter Spacing
  9. Paddings <style name="DDS.Container.Card.Small"> <item name="android:layout_width">@dimen/fourteen_grid_unit</item> <item name="android:layout_height">wrap_content</item> <item name="contentPadding">@dimen/one_grid_unit</item> <item

    name="android:background">@color/palette_white</item> <item name="cardElevation">@dimen/card_elevation</item> </style> Molecules with no padding Containers with paddings
  10. Separators They are not abomination They might break Pixel Perfection

    In Static UI • Include into molecules • Extra padding In RecyclerView • Not in molecule • No padding
  11. Separators • Have basic separator ◦ No Padding ◦ No

    Restriction • Have different styles ◦ Use in Static Layout ◦ Use w/o paddings ◦ Have variations
  12. Molecules • Same elements ◦ Text Styles ◦ Colors ◦

    Padding ◦ Size Same Molecule, Different States
  13. Molecules • Need different layout types • More Different elements

    than common • Complex to Maintain Different Molecule
  14. Molecules • Extra elements ◦ Have a limit? ◦ Have

    it completely separate? ◦ Are they in same category? section? Grey Zone!
  15. DDS • Define molecule via xml • Set different states

    through xml • Do not redraw • Do not make runtime change • Final on the inflation
  16. DDS • Create custom attributes • Read attribute values •

    Inflate layout • Configure • Set values
  17. DDS internal interface DDSContract { fun getStyleableId(): IntArray fun readAttributes(typedArray:

    TypedArray) fun getRelevantLayout(): Int fun configureLayout() fun setViewValues() fun getContext(): Context }
  18. DDS internal fun DDSContract.initialize(attributeSet: AttributeSet?) { attributeSet?.let { val typedArray

    = getContext().theme .obtainStyledAttributes(attributeSet, getStyleableId(), 0, 0) readAttributes(typedArray) typedArray.recycle() } if (this is ViewGroup) { inflate(getContext(), getRelevantLayout(), this) } else { throw IllegalStateException("DDSContract is not a ViewGroup.") } configureLayout() setViewValues() }
  19. DDS internal fun DDSContract.initialize(attributeSet: AttributeSet?) { attributeSet?.let { val typedArray

    = getContext().theme .obtainStyledAttributes(attributeSet, getStyleableId(), 0, 0) readAttributes(typedArray) typedArray.recycle() } if (this is ViewGroup) { inflate(getContext(), getRelevantLayout(), this) } else { throw IllegalStateException("DDSContract is not a ViewGroup.") } configureLayout() setViewValues() }
  20. DDS internal fun DDSContract.initialize(attributeSet: AttributeSet?) { attributeSet?.let { val typedArray

    = getContext().theme .obtainStyledAttributes(attributeSet, getStyleableId(), 0, 0) readAttributes(typedArray) typedArray.recycle() } if (this is ViewGroup) { inflate(getContext(), getRelevantLayout(), this) } else { throw IllegalStateException("DDSContract is not a ViewGroup.") } configureLayout() setViewValues() }
  21. DDS internal fun DDSContract.initialize(attributeSet: AttributeSet?) { attributeSet?.let { val typedArray

    = getContext().theme .obtainStyledAttributes(attributeSet, getStyleableId(), 0, 0) readAttributes(typedArray) typedArray.recycle() } if (this is ViewGroup) { inflate(getContext(), getRelevantLayout(), this) } else { throw IllegalStateException("DDSContract is not a ViewGroup.") } configureLayout() setViewValues() }
  22. DDS internal fun DDSContract.initialize(attributeSet: AttributeSet?) { attributeSet?.let { val typedArray

    = getContext().theme .obtainStyledAttributes(attributeSet, getStyleableId(), 0, 0) readAttributes(typedArray) typedArray.recycle() } if (this is ViewGroup) { inflate(getContext(), getRelevantLayout(), this) } else { throw IllegalStateException("DDSContract is not a ViewGroup.") } configureLayout() setViewValues() }
  23. DDS class AvatarInfo : LinearLayout, DDSContract { } ... constructor(context:

    Context, attrs: AttributeSet) : super(context, attrs) { initialize(attrs) } ...
  24. DDS class AvatarInfo : LinearLayout, DDSContract { } override fun

    getStyleableId(): IntArray = R.styleable.AvatarInfo override fun readAttributes(typedArray: TypedArray) { with(typedArray) { state = getEnum(R.styleable.AvatarInfo_avatarInfoState) // … } } override fun configureLayout() { orientation = HORIZONTAL gravity = Gravity.CENTER }
  25. DDS class AvatarInfo : LinearLayout, DDSContract { } override fun

    getRelevantLayout() : Int = when(state) { AvatarInfoState.STATE_1 -> R.layout.avatar_info_state_1 AvatarInfoState.STATE_2 -> R.layout.avatar_info_state_2 AvatarInfoState.STATE_3 -> R.layout.avatar_info_state_3 AvatarInfoState.STATE_4 -> R.layout.avatar_info_state_4 } enum class AvatarInfoState : DDSState { STATE_1, STATE_2, STATE_3, STATE_4 }
  26. DDS <merge xmlns:android="http://schemas.android.com/apk/res/android"> <ImageView android:id="@+id/avatar_info_image" android:layout_width="@dimen/avatar_size_48" android:layout_height="@dimen/avatar_size_48" /> <TextView android:id="@+id/avatar_info_title"

    style="@style/DDS.TextStyle.010" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/avatar_info_description" style="@style/DDS.TextStyle.018" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </merge>
  27. DDS <merge xmlns:android="http://schemas.android.com/apk/res/android"> <ImageView android:id="@+id/avatar_info_image" android:layout_width="@dimen/avatar_size_48" android:layout_height="@dimen/avatar_size_48" /> <TextView android:id="@+id/avatar_info_title"

    style="@style/DDS.TextStyle.010" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/avatar_info_description" style="@style/DDS.TextStyle.018" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </merge> State1
  28. DDS <merge xmlns:android="http://schemas.android.com/apk/res/android"> <ImageView android:id="@+id/avatar_info_image" android:layout_width="@dimen/avatar_size_36" android:layout_height="@dimen/avatar_size_36" /> <TextView android:id="@+id/avatar_info_title"

    style="@style/DDS.TextStyle.017" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/avatar_info_description" style="@style/DDS.TextStyle.020" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </merge> State2
  29. DDS <merge xmlns:android="http://schemas.android.com/apk/res/android"> <ImageView android:id="@+id/avatar_info_image" android:layout_width="@dimen/avatar_size_36" android:layout_height="@dimen/avatar_size_36" /> <TextView android:id="@+id/avatar_info_title"

    style="@style/DDS.TextStyle.017" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/avatar_info_description" style="@style/DDS.TextStyle.020" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </merge>
  30. DDS class AvatarInfo : LinearLayout, DDSContract { } val title:

    TextView by lazy { findViewById<TextView>(R.id.avatar_info_title) }
  31. DDS class AvatarInfo : LinearLayout, DDSContract { } val title:

    TextView by lazy { state.isRequired(AvatarInfoState.STATE_3) findViewById<TextView>(R.id.avatar_info_title) } internal fun DDSState.isRequired(expected: DDSState) { check(this == expected, { "This element is restricted to $expected state." }) }
  32. DDS

  33. DDS

  34. Thanks! ➢ Have a Design System :) ➢ Decide how

    strict you want it to be ➢ Keep an eye on the performance ➢ Make it easy to find ➢ Keep it simple ➢ Work closely with Designers Happy Developers & Designers & User