Scaling UI

Scaling UI

Dccb5651578c54429ec81782a0bf881a?s=128

jbarr21

July 27, 2018
Tweet

Transcript

  1. Scaling UI James Barr Uber

  2. Platform UI

  3. UI Issues & Considerations

  4. Design System

  5. Before & After <color name="ub__black">#000000</color> <color name="ub__uber_black_100">#09091A</color> <color name="ub__uber_black_95">#151525</color> <color

    name="ub__uber_black_90">#222231</color> <color name="ub__uber_black_80">#3A3A48</color> <color name="ub__uber_black_60">#6B6B76</color> <color name="ub__uber_black_40">#9D9DA3</color> <color name="ub__white">#FFFFFF</color> <color name="ub__uber_white_120">#B2B2BA</color> <color name="ub__uber_white_100">#C0C0C8</color> <color name="ub__uber_white_80">#CDCDD3</color> <color name="ub__uber_white_60">#D9D9DE</color> <color name="ub__uber_white_40">#E6E6E9</color> <color name="ub__uber_white_20">#F2F2F4</color> <color name="ub__uber_white_10">#F9F9F9</color> <color name="ub__uber_blue_120">#1EACC7</color> <color name="ub__uber_blue_100">#1FBAD6</color> <color name="ub__uber_blue_100_semi_transparent">#661FBAD6</color> <color name="ub__uber_blue_80">#4CC8DE</color> <color name="ub__uber_blue_60">#79D6E6</color> <color name="ub__uber_blue_40">#A5E3EF</color> <color name="ub__uber_blue_20">#D2F1F7</color> ...
  6. None
  7. Design System Guardrails

  8. Lint

  9. Lint class ColorResourceUsageDetector : ResourceXmlDetector() { override fun visitAttribute(context: XmlContext,

    attr: Attr) { if (attr.name in COLOR_ATTRIBUTES { if (attr.value.startsWith(PREFIX_COLOR_RES) && attr.value !in WHITELISTED_COLORS) { when { isInResFolder(LAYOUT) && !isColorSelector(attr.value) -> reportError() isInVectorDrawable(attribute) -> reportError() isInResFolder(context, DRAWABLE) && !hasQualifiedVersion(QUALIFIER_V21) && !isColorSelector(context, attributeVal) -> reportError() } } else if (attr.value.startsWith(PREFIX_THEME_ATTR) && isInResFolder(DRAWABLE) && !isInVectorDrawable(attribute) && !isQualifiedVersion(context)) { reportError() } } } }
  10. Custom Fonts

  11. Font Updates 1. Add new font asset 2. Update font

    name -- <string name="font_path">fonts/Roboto.ttf</string> ++ <string name="font_path">fonts/GoogleSans.ttf</string> 3. ✅ ? src/main/res/value/strings-fonts.xml
  12. Calligraphy

  13. Calligraphy Wrapped

  14. ViewPump View inflation with a pre/post- processing API github.com/InflationX/ViewPump

  15. ViewPump override fun onCreateView( name: String, context: Context, attrs: AttributeSet):

    View { return ViewPump.get().inflate(InflateRequest.builder() .name(name) .context(context) .attrs(attrs) .fallbackViewCreator(mViewCreator) .build()).view() } ViewPumpLayoutInflater.java
  16. ViewPump override fun onCreateView( name: String, context: Context, attrs: AttributeSet):

    View { return ViewPump.get().inflate(InflateRequest.builder() .name(name) .context(context) .attrs(attrs) .fallbackViewCreator(mViewCreator) .build()).view() } ViewPumpLayoutInflater.java
  17. ViewPump Interceptors class TextUpdatingInterceptor() : Interceptor { override fun intercept(chain:

    Chain): InflateResult { val result: InflateResult = chain.proceed(chain.request()) val view: View = result.view() if (view is TextView) { // You also have access to result.context and result.attrs view.text = "[Prefix] ${view.text}" } return result } }
  18. Calligraphy class CalligraphyInterceptor( val calligraphy: Calligraphy) : Interceptor { override

    fun intercept(chain: Chain): InflateResult { val result: InflateResult = chain.proceed(chain.request()) return result.toBuilder() .view( calligraphy.onViewCreated( result.view(), result.context(), result.attrs())) .build() } }
  19. Configuring ViewPump ViewPump.init(ViewPump.builder() .addInterceptor(CalligraphyInterceptor(CalligraphyConfig.Builder().build())) .addInterceptor(AccessibilityEncouragingInterceptor()) .addInterceptor(TextUpdatingInterceptor()) .addInterceptor(AppCompatInterceptor()) .build())

  20. Reimplement AppCompat class AppCompatInterceptor : Interceptor { override fun intercept(chain:

    Interceptor.Chain): InflateResult { val request = chain.request() val viewNameToInflate = viewNameToInflate(request.name()) return chain.proceed(request.toBuilder().name(viewNameToInflate).build()) } fun viewNameToInflate(requestedViewName: String) = when (requestedViewName) { "Button" -> AppCompatButton::class.java.name "EditText" -> AppCompatEditText::class.java.name "ImageView" -> AppCompatImageView::class.java.name "TextView" -> AppCompatTextView::class.java.name else -> requestedViewName } }
  21. Reimplement AppCompat class AppCompatInterceptor : Interceptor { override fun intercept(chain:

    Interceptor.Chain): InflateResult { val request = chain.request() val viewNameToInflate = viewNameToInflate(request.name()) return chain.proceed(request.toBuilder().name(viewNameToInflate).build()) } fun viewNameToInflate(requestedViewName: String) = when (requestedViewName) { "Button" -> AppCompatButton::class.java.name "EditText" -> AppCompatEditText::class.java.name "ImageView" -> AppCompatImageView::class.java.name "TextView" -> AppCompatTextView::class.java.name else -> requestedViewName } }
  22. Hide Unaccessible Views class AccessibilityEncouragingInterceptor : Interceptor { override fun

    intercept(chain: Interceptor.Chain): InflateResult { val result = chain.proceed(chain.request()) result.view()?.let { if (it.isClickable && it.contentDescription.isNullOrEmpty()) { it.visibility = View.INVISIBLE } } return result } }
  23. Other Example Usages • Geocities-ify an app • Draw over

    views under some conditions • Backport functionality for new attrs • Dynamic string reloading • Track recently inflated views & add data to crash reports
  24. Platform UI Widgets

  25. Artist A build tool that codegens a base set of

    Android Views github.com/uber/artist
  26. Artist Traits (functionality) + Stencils (definitions) ↓ Views

  27. Artist Trait @AutoService(Trait::class) class VisibilityTrait : Trait { override fun

    generateFor( type: TypeSpec.Builder, initMethod: MethodSpec.Builder, rClass: ClassName, baseType: String) { arrayOf("visible", "invisible", "gone").forEach { type.addMethod(createVisibilityConvenienceMethod(it)) } } ... }
  28. Artist Trait @AutoService(Trait::class) class VisibilityTrait : Trait { ... fun

    createVisibilityConvenienceMethod(type: String): MethodSpec { return MethodSpec.methodBuilder("is${type.capitalize()}") .addModifiers(Modifier.PUBLIC) .returns(TypeName.BOOLEAN) .addStatement( "return getVisibility() == \$T.${type.toUpperCase()}", TypeNames.Android.View) .build() } }
  29. Artist Stencil class TextViewStencil : ViewStencil( extendedType = "android.support.v7.widget.AppCompatTextView", constructorCount

    = 3, defaultAttrRes = "textViewStyle", addedTraits = VisibilityTrait::class.java )
  30. Artist Generated View public class MyTextView extends AppCompatTextView { public

    MyTextView(Context context) { this(context, null); } public MyTextView(Context context, AttributeSet attrs) { this(context, attrs, R.attr.textViewStyle); } public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } // protected init method - provided in every stencil public boolean isVisible() { return getVisibility() == View.VISIBLE; } public boolean isGone() { return getVisibility() == View.GONE; } public boolean isInvisible() { return getVisibility() == View.INVISIBLE; } }
  31. @AutoService(ViewStencilProvider.class) class SampleViewStencilProvider : ViewStencilProvider { override fun stencils() =

    setOf( ViewStencil("android.support.v7.widget.AppCompatButton", 3, "buttonStyle"), ViewStencil("android.support.v7.widget.AppCompatEditText", 3, "android.R.attr.editTextStyle", TextInputTrait.class), ViewStencil("android.widget.LinearLayout", 3), ViewStencil("android.support.v7.widget.AppCompatImageView", 3), ViewStencil("android.support.v4.widget.NestedScrollView", 3, ScrollableTrait.class), ViewStencil("android.support.v7.widget.AppCompatTextView", 3, "android.R.attr.textViewStyle") ) override fun globalTraits() = setOf( VisibilityTrait.class::java, ForegroundTrait.class::java, ViewTrait.class::java ) }
  32. Artist Use Cases • Make Rx APIs 1st class citizens:

    myView.clicks() • Add built-in analytics to taps and impressions • Make a bugfix to MyTextView and all other usages will receive it • Easily change all of your parent classes from AppCompatWidget to MaterialWidget
  33. Wrapping Up • Collaborate with Design to create a system

    that works • Use semantic names for colors & styles flexibility across apps • Enforce your design system with Lint & ErrorProne • Use ViewPump to post-process inflated views • Use Artist to easily maintain your base set of views
  34. Scaling UI James Barr • uber.github.io • We're Hiring @jbarr21