Slide 1

Slide 1 text

CUSTOM LAYOUTS Droidcon UK, 2014

Slide 2

Slide 2 text

LUCAS ROCHA +LucasRocha | @lucasratmundo

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

LAYOUT is at the heart of every UI toolkit.

Slide 5

Slide 5 text

SIZE + POSITION

Slide 6

Slide 6 text

MEASURE LAYOUT DRAW

Slide 7

Slide 7 text

THE TRAVERSAL

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

PERFORMANCE 1. Number of views 2. Computational cost

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

THE ULTIMATE GUIDE FOR CUSTOM LAYOUT DEVELOPMENT

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

LAYOUT FILE ...

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

MEASURE

Slide 23

Slide 23 text

MEASURE

Slide 24

Slide 24 text

MEASURE

Slide 25

Slide 25 text

MEASURE

Slide 26

Slide 26 text

MEASURE

Slide 27

Slide 27 text

MEASURE

Slide 28

Slide 28 text

MEASURE

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

PROFIT. Before After

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

RECYCLER VIEW Framework for view recycling layouts.

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

SIMPLE BASE TwoWayLayoutManager to the rescue!

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

QUESTIONS? +LucasRocha | @lucasratmundo