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

Custom Layouts

Lucas Rocha
October 31, 2014

Custom Layouts

Presented at Droidcon London 2014

Lucas Rocha

October 31, 2014
Tweet

More Decks by Lucas Rocha

Other Decks in Programming

Transcript

  1. CUSTOM
    LAYOUTS
    Droidcon UK, 2014

    View Slide

  2. LUCAS ROCHA
    +LucasRocha | @lucasratmundo

    View Slide

  3. View Slide

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

    View Slide

  5. SIZE + POSITION

    View Slide

  6. MEASURE
    LAYOUT
    DRAW

    View Slide

  7. THE TRAVERSAL

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  15. BUILDING BLOCKS
    Methods
    LayoutParams MeasureSpec
    MATCH_PARENT
    WRAP_CONTENT
    DIMENSION
    EXACTLY
    AT_MOST
    UNSPECIFIED
    onMeasure()
    onLayout()
    measureChild*()
    ...

    View Slide

  16. MINDSET
    Cheat & simplify. You're not creating a
    general-purpose layout.

    View Slide

  17. THE ULTIMATE
    GUIDE FOR
    CUSTOM
    LAYOUT
    DEVELOPMENT

    View Slide

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

    View Slide

  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) {
    ...
    }
    }

    View Slide

  20. LAYOUT FILE
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    android:id="@+id/profile_image"
    android:layout_width="@dimen/tweet_profile_image_size"
    android:layout_height="@dimen/tweet_profile_image_size"
    android:scaleType="centerCrop"/>
    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"/>
    ...

    View Slide

  21. onMeasure()
    1. Measure one child
    2. Update consumed width/height

    View Slide

  22. MEASURE

    View Slide

  23. MEASURE

    View Slide

  24. MEASURE

    View Slide

  25. MEASURE

    View Slide

  26. MEASURE

    View Slide

  27. MEASURE

    View Slide

  28. MEASURE

    View Slide

  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);
    }

    View Slide

  30. onLayout()
    1. Layout one child
    2. Update current vert/hor offset

    View Slide

  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);
    ...
    }

    View Slide

  32. PROFIT.
    Before
    After

    View Slide

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

    View Slide

  34. RECYCLER VIEW
    Framework for view recycling layouts.

    View Slide

  35. LOW-LEVEL
    1. Track visible viewport
    2. Recycle views out of bounds
    3. Appearing/disappearing views
    4. Scroll to position
    5. State restoration
    ...

    View Slide

  36. TWOWAY VIEW
    RecyclerView made simple.
    https://github.com/lucasr/twoway-view

    View Slide

  37. SIMPLE BASE
    TwoWayLayoutManager to the rescue!

    View Slide

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

    View Slide

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

    View Slide

  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) {
    ...
    }
    }

    View Slide

  41. MEASURE
    @Override
    protected void measureChild(View child, Direction direction) {
    measureChildWithMargins(child, 0, 0);
    }

    View Slide

  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);
    }

    View Slide

  43. VIEWPORT CONTROL
    @Override
    protected boolean canAddMoreViews(Direction direction, int limit) {
    if (direction == Direction.END) {
    return getLayoutEnd() < getEndWithPadding();
    } else {
    return getLayoutStart() > getStartWithPadding();
    }
    }

    View Slide

  44. NOT JUST THAT
    Scroll to position, state restoration,
    accessibility, keyboard nav, etc.

    View Slide

  45. QUESTIONS?
    +LucasRocha | @lucasratmundo

    View Slide