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

Custom Layouts

1f461eee0b22011d0bccf4e882d9149f?s=47 Lucas Rocha
October 31, 2014

Custom Layouts

Presented at Droidcon London 2014

1f461eee0b22011d0bccf4e882d9149f?s=128

Lucas Rocha

October 31, 2014
Tweet

Transcript

  1. CUSTOM LAYOUTS Droidcon UK, 2014

  2. LUCAS ROCHA +LucasRocha | @lucasratmundo

  3. None
  4. LAYOUT is at the heart of every UI toolkit.

  5. SIZE + POSITION

  6. MEASURE LAYOUT DRAW

  7. THE TRAVERSAL

  8. THE TRAVERSAL http://devfest.gdg-london.com

  9. PERFORMANCE 1. Number of views 2. Computational cost

  10. # OF VIEWS Compound drawables, spannables, drawables, etc.

  11. LAYOUT COST Avoid multi-pass layout features. Depth matters.

  12. PROBE IT! Intercept view methods. Dissect layout traversals. https://github.com/lucasr/probe

  13. WHY CUSTOM? Simplify view hierarchy, performance, missing features.

  14. MANY SHAPES Composite, custom, flat, async flat. https://lucasr.org/?p=3920

  15. BUILDING BLOCKS Methods LayoutParams MeasureSpec MATCH_PARENT WRAP_CONTENT DIMENSION EXACTLY AT_MOST

    UNSPECIFIED onMeasure() onLayout() measureChild*() ...
  16. MINDSET Cheat & simplify. You're not creating a general-purpose layout.

  17. THE ULTIMATE GUIDE FOR CUSTOM LAYOUT DEVELOPMENT

  18. LET'S DO THIS https://github.com/lucasr/android-layout-samples

  19. STRUCTURE public class TweetLayout extends ViewGroup { public TweetLayoutView(Context context,

    AttributeSet attrs) { super(context, attrs, defStyleAttr); LayoutInflater.from(context) .inflate(R.layout.tweet_layout, this, true); mProfileImage = (ImageView) findViewById(R.id.profile_image); ... } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { ... } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { ... } }
  20. LAYOUT FILE <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/profile_image" android:layout_width="@dimen/tweet_profile_image_size" android:layout_height="@dimen/tweet_profile_image_size"

    android:scaleType="centerCrop"/> <TextView android:id="@+id/author_text" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textSize="@dimen/tweet_author_text_size" android:singleLine="true"/> ... </merge>
  21. onMeasure() 1. Measure one child 2. Update consumed width/height

  22. MEASURE

  23. MEASURE

  24. MEASURE

  25. MEASURE

  26. MEASURE

  27. MEASURE

  28. MEASURE

  29. MEASURE @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { ...

    int widthUsed = 0; int heightUsed = 0; measureChildWithMargins(mProfileImage, widthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed); widthUsed += getMeasuredWidthWithMargins(mProfileImage); measureChildWithMargins(mAuthorText, widthMeasureSpec, WidthUsed, heightMeasureSpec, heightUsed); heightUsed += getMeasuredHeightWithMargins(mAuthorText); measureChildWithMargins(mMessageText, widthMeasureSpec, WidthUsed, heightMeasureSpec, heightUsed); heightUsed += getMeasuredHeightWithMargins(mMessageText); ... setMeasuredDimension(widthSize, heightSize); }
  30. onLayout() 1. Layout one child 2. Update current vert/hor offset

  31. LAYOUT @Override protected void onLayout(boolean changed, int l, int t,

    int r, int b) { int currentTop = paddingTop; layoutView(mProfileImage, paddingLeft, currentTop, mProfileImage.getMeasuredWidth(), mProfileImage.getMeasuredHeight()); final int contentLeft = getWidthWithMargins(mProfileImage) + paddingLeft; final int contentWidth = r - l - contentLeft - getPaddingRight(); layoutView(mAuthorText, contentLeft, currentTop, contentWidth, mAuthorText.getMeasuredHeight()); currentTop += getHeightWithMargins(mAuthorText); layoutView(mMessageText, contentLeft, currentTop, contentWidth, mMessageText.getMeasuredHeight()); currentTop += getHeightWithMargins(mMessageText); ... }
  32. PROFIT. Before After

  33. RECYCLING Hard to implement. No extensible layout API—until recently.

  34. RECYCLER VIEW Framework for view recycling layouts.

  35. LOW-LEVEL 1. Track visible viewport 2. Recycle views out of

    bounds 3. Appearing/disappearing views 4. Scroll to position 5. State restoration ...
  36. TWOWAY VIEW RecyclerView made simple. https://github.com/lucasr/twoway-view

  37. SIMPLE BASE TwoWayLayoutManager to the rescue!

  38. HOW IT WORKS measureChild() START END layoutChild() canAddMoreViews() Direction

  39. LET'S DO THIS http://bit.ly/twowayview-simplelistlayout

  40. STRUCTURE public class SimpleListLayout extends TwoWayLayoutManager { public SimpleListLayout(Context context,

    Orientation orientation) { super(context, orientation); } @Override protected void measureChild(View child, Direction direction) { ... } @Override protected void layoutChild(View child, Direction direction) { ... } @Override protected boolean canAddMoreViews(Direction direction, int limit) { ... } }
  41. MEASURE @Override protected void measureChild(View child, Direction direction) { measureChildWithMargins(child,

    0, 0); }
  42. LAYOUT @Override protected void layoutChild(View child, Direction direction) { final

    int width = getDecoratedMeasuredWidth(child); final int height = getDecoratedMeasuredHeight(child); final int l, t, r, b; if (getOrientation() == Orientation.VERTICAL) { l = getPaddingLeft(); t = direction == Direction.END ? getLayoutEnd() : getLayoutStart() - height; } else { l = direction == Direction.END ? getLayoutEnd() : getLayoutStart() - width; t = getPaddingTop(); } r = l + width; b = t + height; layoutDecorated(child, l, t, r, b); }
  43. VIEWPORT CONTROL @Override protected boolean canAddMoreViews(Direction direction, int limit) {

    if (direction == Direction.END) { return getLayoutEnd() < getEndWithPadding(); } else { return getLayoutStart() > getStartWithPadding(); } }
  44. NOT JUST THAT Scroll to position, state restoration, accessibility, keyboard

    nav, etc.
  45. QUESTIONS? +LucasRocha | @lucasratmundo