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

Øredev 2017: Measure. Layout. Draw. Repeat.

Øredev 2017: Measure. Layout. Draw. Repeat.

Huyen's presentation at Øredev 2017 on custom Views and ViewGroups in Android.

Huyen Tue Dao

November 09, 2017
Tweet

More Decks by Huyen Tue Dao

Other Decks in Programming

Transcript

  1. 3 MEASURE. LAYOUT. DRAW. REPEAT. CUSTOM VIEWS WHY NOT? DIFFICULT

    TIME-CONSUMING DRAWING/INTERACTIONS/A11Y → ON YOUR OWN
  2. 4 MEASURE. LAYOUT. DRAW. REPEAT. HOW ANDROID DRAWS VIEWS HOW

    TO GO CUSTOM DRAW MEASURE VIEW GROUPS LAYOUT
  3. R PARENT PASSES CONSTRAINTS CHILD CALCULATES MEASURED WIDTH/HEIGHT MEASURE HOW

    ANDROID DRAWS VIEWS onMeasure() LAYOUT onLayout() DRAW onDraw() MEASURE OWN CHILDREN (IF ANY)
  4. R PARENT SIZES AND POSITIONS CHILD MEASURE LAYOUT DRAW HOW

    ANDROID DRAWS VIEWS onMeasure() onLayout() onDraw()
  5. PARENT DRAWS PARENT TELLS CHILDREN TO DRAW MEASURE LAYOUT DRAW

    0 1 Z parent children HOW ANDROID DRAWS VIEWS onMeasure() onLayout() onDraw()
  6. DIFFERENT WAYS TO GO CUSTOM VIEW VIEWGROUP EXTEND EXISTING CUSTOM

    LAYOUTS ON ANDROID, @LUCASRATMUNDO EXTEND BASE CUSTOM EXTENDED* TWEAK BEHAVIOR ARGUABLY SIMPLEST COMPOSITE MODULAR UI LAYOUT REUSE FLAT CUSTOM CUSTOM DRAWING/BEHAVIOR PERFORMANCE CUSTOM COMPOSITE CUSTOM LAYOUT LOGIC PERFORMANCE
  7. WHAT DO YOU NEED TO IMPLEMENT? VIEW MEASURE LAYOUT DRAW

    onMeasure() onLayout() onDraw() NO NO YES BUT YOU SHOULD REUSABILITY/FLEXIBILITY VIEW HAS NO CHILDREN REQUIRED FOR VIEW TO APPEAR
  8. 12 /** * An interface for a view implementing a

    tally counter. !*/ public interface TallyCounter { /** * Reset the counter. !*/ void reset(); /** * Increment the counter. !*/ void increment(); /** * @return The current count of the counter. !*/ int getCount(); /** * Set the counter value. !*/ void setCount(int count); }
  9. VIEW CONSTRUCTORS CREATE NEW FROM CODE View(ContextAcontext) CREATE FROM XML

    View(ContextBcontext, AttributeSetBattrs) CREATE FROM XML WITH A STYLE FROM A THEME ATTRIBUTE View(ContextCcontext, AttributeSetCattrs, intCdefStyleAttr) CREATE FROM XML WITH A STYLE FROM A THEME ATTRIBUTE OR STYLE RESOURCE View(ContextDcontext, AttributeSetDattrs, intDdefStyleAttr, intDdefStyleRes) A DEEP DIVE INTO ANDROID VIEW CONSTRUCTORS @DANLEW42
  10. 16 public TallyCounterView(Context context, AttributeSet attrs) { super(context, attrs); !//

    Set up paints for canvas drawing. backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); backgroundPaint.setColor(ContextCompat.getColor(context, R.color.colorPrimary)); linePaint = new Paint(Paint.ANTI_ALIAS_FLAG); linePaint.setColor(ContextCompat.getColor(context, R.color.colorAccent)); linePaint.setStrokeWidth(1f); numberPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); numberPaint.setColor(ContextCompat.getColor(context, android.R.color.white)); }A
  11. 17 public TallyCounterView(Context context, AttributeSet attrs) { super(context, attrs); !//

    Set up paints for canvas drawing. backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); backgroundPaint.setColor(ContextCompat.getColor(context, R.color.colorPrimary)); linePaint = new Paint(Paint.ANTI_ALIAS_FLAG); linePaint.setColor(ContextCompat.getColor(context, R.color.colorAccent)); linePaint.setStrokeWidth(1f); numberPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); numberPaint.setColor(ContextCompat.getColor(context, android.R.color.white)); !// Set the number text size to be 64sp. !// Translate 64sp… numberPaint.setTextSize( Math.round(64f * getResources().getDisplayMetrics().scaledDensity)); }A
  12. 18 public TallyCounterView(Context context, AttributeSet attrs) { super(context, attrs); !//

    Set up paints for canvas drawing. backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); backgroundPaint.setColor(ContextCompat.getColor(context, R.color.colorPrimary)); linePaint = new Paint(Paint.ANTI_ALIAS_FLAG); linePaint.setColor(ContextCompat.getColor(context, R.color.colorAccent)); linePaint.setStrokeWidth(1f); numberPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); numberPaint.setColor(ContextCompat.getColor(context, android.R.color.white)); !// Set the number text size to be 64sp. !// Translate 64sp… numberPaint.setTextSize( Math.round(64f * getResources().getDisplayMetrics().scaledDensity)); !// Allocate objects needed for canvas drawing here. backgroundRect = new RectF(); !// Initialize drawing measurements. cornerRadius = Math.round(2f * getResources().getDisplayMetrics().density); }A
  13. 19 public TallyCounterView(Context context, AttributeSet attrs) { super(context, attrs); !//

    Set up paints for canvas drawing. backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); backgroundPaint.setColor(ContextCompat.getColor(context, R.color.colorPrimary)); linePaint = new Paint(Paint.ANTI_ALIAS_FLAG); linePaint.setColor(ContextCompat.getColor(context, R.color.colorAccent)); linePaint.setStrokeWidth(1f); numberPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); numberPaint.setColor(ContextCompat.getColor(context, android.R.color.white)); !// Set the number text size to be 64sp. !// Translate 64sp… numberPaint.setTextSize( Math.round(64f * getResources().getDisplayMetrics().scaledDensity)); !// Allocate objects needed for canvas drawing here. backgroundRect = new RectF(); !// Initialize drawing measurements. cornerRadius = Math.round(2f * getResources().getDisplayMetrics().density); !// Do initial count setup. setCount(0); }A
  14. 21 @Override protected void onDraw(Canvas canvas) { !// Grab canvas

    dimensions. final int canvasWidth = canvas.getWidth(); final int canvasHeight = canvas.getHeight(); !// Calculate horizontal center. final float centerX = canvasWidth * 0.5f; }A WHAT WE WANT 0000
  15. 22 @Override protected void onDraw(Canvas canvas) { !// Grab canvas

    dimensions. final int canvasWidth = canvas.getWidth(); final int canvasHeight = canvas.getHeight(); !// Calculate horizontal center. final float centerX = canvasWidth * 0.5f; !// Draw the background. backgroundRect.set(0f, 0f, canvasWidth, canvasHeight); canvas.drawRoundRect(backgroundRect, cornerRadius, cornerRadius, backgroundPaint); }A WHAT WE WANT 0000
  16. 23 @Override protected void onDraw(Canvas canvas) { !// Grab canvas

    dimensions. final int canvasWidth = canvas.getWidth(); final int canvasHeight = canvas.getHeight(); !// Calculate horizontal center. final float centerX = canvasWidth * 0.5f; !// Draw the background. backgroundRect.set(0f, 0f, canvasWidth, canvasHeight); canvas.drawRoundRect(backgroundRect, cornerRadius, cornerRadius, backgroundPaint); !// Draw baseline. final float baselineY = Math.round(canvasHeight * 0.6f); canvas.drawLine(0, baselineY, canvasWidth, baselineY, linePaint); }A WHAT WE WANT 0000
  17. 24 @Override protected void onDraw(Canvas canvas) { !// Grab canvas

    dimensions. final int canvasWidth = canvas.getWidth(); final int canvasHeight = canvas.getHeight(); !// Calculate horizontal center. final float centerX = canvasWidth * 0.5f; !// Draw the background. backgroundRect.set(0f, 0f, canvasWidth, canvasHeight); canvas.drawRoundRect(backgroundRect, cornerRadius, cornerRadius, backgroundPaint); !// Draw baseline. final float baselineY = Math.round(canvasHeight * 0.6f); canvas.drawLine(0, baselineY, canvasWidth, baselineY, linePaint); !// Draw text. }A WHAT WE WANT 0000
  18. 25 @Override protected void onDraw(Canvas canvas) { !// Grab canvas

    dimensions. final int canvasWidth = canvas.getWidth(); final int canvasHeight = canvas.getHeight(); !// Calculate horizontal center. final float centerX = canvasWidth * 0.5f; !// Draw the background. backgroundRect.set(0f, 0f, canvasWidth, canvasHeight); canvas.drawRoundRect(backgroundRect, cornerRadius, cornerRadius, backgroundPaint); !// Draw baseline. final float baselineY = Math.round(canvasHeight * 0.6f); canvas.drawLine(0, baselineY, canvasWidth, baselineY, linePaint); !// Draw text. !// Measure the width of text to display. final float textWidth = numberPaint.measureText(displayedCount); !// Figure out an x-coordinate that will center the text in the canvas. final float textX = Math.round(centerX - textWidth * 0.5f); !// Draw. canvas.drawText(displayedCount, textX, baselineY, numberPaint); }A WHAT WE WANT 0000
  19. 26

  20. REFLECTING STATE CHANGES MARK A VIEW AS DIRTY (NEEDS A

    RE-DRAW) invalidate() RE-DRAWN “AT SOME POINT IN THE FUTURE” BE MORE PRECISE: invalidate(int l, int t, int r, int b)
  21. 28 !// !// TallyCounter interface !// @Override public void reset()

    { setCount(0); } @Override public void increment() { setCount(count + 1); } @OverrideA public void setCount(int count) { count = Math.min(count, MAX_COUNT); this.count = count; !// Create the string here. this.displayedCount = String.format(Locale.getDefault(), "%04d", count); }A @Override public int getCount() { return count; }
  22. 29 @OverrideA public void setCount(int count) { count = Math.min(count,

    MAX_COUNT); this.count = count; !// Create the string here. this.displayedCount = String.format(Locale.getDefault(), "%04d", count); }A
  23. 30 @OverrideA public void setCount(int count) { count = Math.min(count,

    MAX_COUNT); this.count = count; !// Create the string here. this.displayedCount = String.format(Locale.getDefault(), "%04d", count); invalidate(); }A
  24. 31

  25. THINGS TO REMEMBER WHEN DRAWING NO ALLOCATIONS IN ONDRAW. SERIOUSLY.

    INVALIDATE ONLY WHEN YOU NEED. INVALIDATE ONLY WHAT YOU NEED. TEXT IS POSITIONED AT THE BASELINE. DRAW IN PX. THINK IN DP AND SP.
  26. WHERE’S THAT OTHER BUTTON? <Button android:id="@+id/increment" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:layout_marginStart="16dp"

    android:layout_marginTop="48dp" android:text="@string/click" !/> <randomlytyping.widget.TallyCounterView android:id="@+id/tally_counter" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:layout_marginTop="40dp" !/> <Button android:id="@+id/reset" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:layout_marginTop=“40dp" android:text="@string/reset" !/>
  27. HOW MEASUREMENTS ARE MADE CHILD PARENT android:layout_width="match_parent" android:layout_height="wrap_content" setLayoutParams() How

    a child tells its parent how it wants to be laid out. MarginLayoutParams LinearLayout.LayoutParams 1Child defines LayoutParams in XML or Java getLayoutParams()
  28. HOW MEASUREMENTS ARE MADE CHILD PARENT How a parent communicates

    constraints to children child.measure() int = { mode | size } AT_MOST X → Be any size up to X EXACTLY X → Be exactly X UNSPECIFIED → No constraints 1 2 Child defines LayoutParams in XML or Java Parent calculates MeasureSpecs and passes to child.measure()
  29. HOW MEASUREMENTS ARE MADE Must call; otherwise → runtime exception.

    CHILD PARENT setMeasuredDimension() 1 2 3 Child defines LayoutParams in XML or Java Parent calculates MeasureSpecs and passes to child.measure() Child calculates width/height; setMeasuredDimension() getMeasuredWidth() getMeasuredHeight()
  30. HOW MEASUREMENTS ARE MADE Child defines LayoutParams in XML or

    Java 1 Parent calculates MeasureSpecs and passes to child.measure() 2 Child calculates width/height; setMeasuredDimension() 3 Parent calls child.layout(); final child size/position. 4 CHILD PARENT child.layout() getWidth() getHeight()
  31. 39 private int reconcileSize(int contentSize, int measureSpec) { final int

    mode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); switch (mode) {Z case MeasureSpec.EXACTLY: return specSize; case MeasureSpec.AT_MOST: if (contentSize < specSize) {B return contentSize; }Belse {C return specSize; }C case MeasureSpec.UNSPECIFIED: default: return contentSize; }Z }Z
  32. 40 private int reconcileSize(int contentSize, int measureSpec) { final int

    mode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); switch (mode) {A case MeasureSpec.EXACTLY: return specSize; case MeasureSpec.AT_MOST: if (contentSize < specSize) {B return contentSize; }Belse {C return specSize;B }C case MeasureSpec.UNSPECIFIED: default: return contentSize;B }A }Z
  33. 41 private int reconcileSize(int contentSize, int measureSpec) { final int

    mode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); switch (mode) {A case MeasureSpec.EXACTLY: return specSize;A case MeasureSpec.AT_MOST: if (contentSize < specSize) {B return contentSize;Q }Belse {C return specSize;B }C case MeasureSpec.UNSPECIFIED: default: return contentSize;B }A }Z
  34. 42 private int reconcileSize(int contentSize, int measureSpec) { final int

    mode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); switch (mode) {A case MeasureSpec.EXACTLY: return specSize;A case MeasureSpec.AT_MOST: if (contentSize < specSize) {B return contentSize;Q }Belse {C return specSize;B }C case MeasureSpec.UNSPECIFIED: default: return contentSize; }A }Z
  35. 45 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {A final

    Paint.FontMetrics fontMetrics = numberPaint.getFontMetrics(); }A
  36. 46 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { A

    final Paint.FontMetrics fontMetrics = numberPaint.getFontMetrics(); !// Measure maximum possible width of text. final float maxTextWidth = numberPaint.measureText(MAX_COUNT_STRING); }A
  37. 47 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { A

    final Paint.FontMetrics fontMetrics = numberPaint.getFontMetrics(); !// Measure maximum possible width of text. final float maxTextWidth = numberPaint.measureText(MAX_COUNT_STRING); !// Estimate maximum possible height of text. final float maxTextHeight = -fontMetrics.top + fontMetrics.bottom; }A
  38. 48 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { A

    final Paint.FontMetrics fontMetrics = numberPaint.getFontMetrics(); !// Measure maximum possible width of text. final float maxTextWidth = numberPaint.measureText(MAX_COUNT_STRING); !// Estimate maximum possible height of text. final float maxTextHeight = -fontMetrics.top + fontMetrics.bottom; !// Add padding to maximum width calculation. final int desiredWidth = Math.round(maxTextWidth + getPaddingLeft() + getPaddingRight()); }A
  39. 49 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { A

    final Paint.FontMetrics fontMetrics = numberPaint.getFontMetrics(); !// Measure maximum possible width of text. final float maxTextWidth = numberPaint.measureText(MAX_COUNT_STRING); !// Estimate maximum possible height of text. final float maxTextHeight = -fontMetrics.top + fontMetrics.bottom; !// Add padding to maximum width calculation. final int desiredWidth = Math.round(maxTextWidth + getPaddingLeft() + getPaddingRight()); !// Add padding to maximum height calculation. final int desiredHeight = Math.round(maxTextHeight * 2f + getPaddingTop() + getPaddingBottom()); }A
  40. 50 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { A

    final Paint.FontMetrics fontMetrics = numberPaint.getFontMetrics(); !// Measure maximum possible width of text. final float maxTextWidth = numberPaint.measureText(MAX_COUNT_STRING); !// Estimate maximum possible height of text. final float maxTextHeight = -fontMetrics.top + fontMetrics.bottom; !// Add padding to maximum width calculation. final int desiredWidth = Math.round(maxTextWidth + getPaddingLeft() + getPaddingRight()); !// Add padding to maximum height calculation. final int desiredHeight = Math.round(maxTextHeight * 2f + getPaddingTop() + getPaddingBottom()); !// Reconcile size that this view wants to be with the size !// that the parent will let it be. final int measuredWidth = reconcileSize(desiredWidth, widthMeasureSpec); final int measuredHeight = reconcileSize(desiredHeight, heightMeasureSpec); }A
  41. 51 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { A

    final Paint.FontMetrics fontMetrics = numberPaint.getFontMetrics(); !// Measure maximum possible width of text. final float maxTextWidth = numberPaint.measureText(MAX_COUNT_STRING); !// Estimate maximum possible height of text. final float maxTextHeight = -fontMetrics.top + fontMetrics.bottom; !// Add padding to maximum width calculation. final int desiredWidth = Math.round(maxTextWidth + getPaddingLeft() + getPaddingRight()); !// Add padding to maximum height calculation. final int desiredHeight = Math.round(maxTextHeight * 2f + getPaddingTop() + getPaddingBottom()); !// Reconcile size that this view wants to be with the size !// that the parent will let it be. final int measuredWidth = reconcileSize(desiredWidth, widthMeasureSpec); final int measuredHeight = reconcileSize(desiredHeight, heightMeasureSpec); !// Store the final measured dimensions. setMeasuredDimension(measuredWidth, measuredHeight); }A
  42. 52

  43. WHAT DO YOU NEED TO IMPLEMENT? VIEWGROUP MEASURE LAYOUT DRAW

    YES YES NO HAVE TO MEASURE EACH CHILD: child.measure() (ABSTRACT) HAVE TO LAYOUT EACH CHILD: child.layout() DOES NOT DRAW BY DEFAULT setWillNotDraw onMeasure() onLayout() onDraw()
  44. 54

  45. 55 <rt.widget.SimpleListItem xmlns:android="http:"//schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="@dimen/list_item_padding_vertical" android:paddingBottom="@dimen/list_item_padding_vertical" android:paddingStart="0dp" android:paddingEnd="@dimen/activity_horizontal_margin" …>

    <ImageView android:id="@+id/icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginEnd="12dp" android:contentDescription="@null" !/> <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height=“wrap_content" !/> <TextView android:id="@+id/subtitle" android:layout_width="wrap_content" android:layout_height=“wrap_content" !/> !</rt.widget.SimpleListItem>
  46. 56 public class SimpleListItem extends ViewGroup { @BindView(R.id.icon) ImageView icon;

    @BindView(R.id.title) TextView titleView; @BindView(R.id.subtitle) TextView subtitleView; /** * Constructor. * * @param context The current context. * @param attrs The attributes of the XML tag that is inflating the view. !*/ public SimpleListItem(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onFinishInflate() { super.onFinishInflate(); ButterKnife.bind(this); }
  47. VALIDATE checkLayoutParams() VIEW WITHOUT LAYOUT PARAMETERS generateDefaultLayoutParams() VIEW WITH INVALID

    LAYOUT PARAMETERS generateLayoutParams(ViewGroup.LayoutParams p) VIEW WITH LAYOUT XML ATTRIBUTES generateLayoutParams(AttributeSet attrs) CUSTOM LAYOUT PARAMETERS ViewGroup.LayoutParams (width, height) MarginLayoutParams (width, height) (left, top, right, bottom)
  48. USEFUL MEASUREMENT METHODS MEASURE + PADDING measureChild(…) measureChildren(…) MEASURE SPEC

    + CHILD LAYOUT PARAMETERS getChildMeasureSpec(…) MEASURE + PADDING + MARGINS + SPACE USED measureChildWithMargins(…) “The heavy lifting is done in getChildMeasureSpec.”
  49. 59 @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof

    MarginLayoutParams; } @Override protected LayoutParams generateDefaultLayoutParams() { return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); } @Override protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return generateDefaultLayoutParams(); }
  50. 61 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {A !//

    Measure icon. measureChildWithMargins(icon, widthMeasureSpec, 0, heightMeasureSpec, 0); }A WHAT WE WANT
  51. 62 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {A !//

    Measure icon. measureChildWithMargins(icon, widthMeasureSpec, 0, heightMeasureSpec, 0); !// Figure out how much width and height the icon used. MarginLayoutParams lp = (MarginLayoutParams) icon.getLayoutParams(); int widthUsed = icon.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; int heightUsed = 0; }A WHAT WE WANT
  52. 63 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {A !//

    Measure icon. measureChildWithMargins(icon, widthMeasureSpec, 0, heightMeasureSpec, 0); !// Figure out how much width and height the icon used. MarginLayoutParams lp = (MarginLayoutParams) icon.getLayoutParams(); int widthUsed = icon.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; int heightUsed = 0; !// Measure title measureChildWithMargins( titleView, widthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed ); }A WHAT WE WANT
  53. 64 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {A !//

    Measure icon. measureChildWithMargins(icon, widthMeasureSpec, 0, heightMeasureSpec, 0); !// Figure out how much width and height the icon used. MarginLayoutParams lp = (MarginLayoutParams) icon.getLayoutParams(); int widthUsed = icon.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; int heightUsed = 0; !// Measure title measureChildWithMargins( titleView, widthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed ); !// Measure the Subtitle. measureChildWithMargins(A subtitleView, widthMeasureSpec, widthUsed,A heightMeasureSpec, titleView.getMeasuredHeight()); }A WHAT WE WANT
  54. 66 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {A …A

    !// Calculate this view's measured width and height. }A WHAT WE WANT
  55. 67 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {A …A

    !// Calculate this view's measured width and height. !// Figure out how much total space the icon used. int iconWidth = icon.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; int iconHeight = icon.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; lp = (MarginLayoutParams) titleView.getLayoutParams(); !// Figure out how much total space the title used. int titleWidth = titleView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; int titleHeight = titleView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; lp = (MarginLayoutParams) subtitleView.getLayoutParams(); !// Figure out how much total space the subtitle used. int subtitleWidth = subtitleView.getMeasuredWidth() + lp.leftMargin +lp.rightMargin; int subtitleHeight = subtitleView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; }A WHAT WE WANT
  56. 68 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {A …A

    !// The width taken by the children + padding. int width = getPaddingTop() + getPaddingBottom() + iconWidth + Math.max(titleWidth, subtitleWidth); !// The height taken by the children + padding. int height = getPaddingTop() + getPaddingBottom() + Math.max(iconHeight, titleHeight + subtitleHeight); }A WHAT WE WANT
  57. 69 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {A …A

    !// The width taken by the children + padding. int width = getPaddingTop() + getPaddingBottom() + iconWidth + Math.max(titleWidth, subtitleWidth); !// The height taken by the children + padding. int height = getPaddingTop() + getPaddingBottom() + Math.max(iconHeight, titleHeight + subtitleHeight); !// Reconcile the measured dimensions with the this view's constraints and !// set the final measured width and height. setMeasuredDimension( resolveSize(width, widthMeasureSpec), resolveSize(height, heightMeasureSpec) ); }A WHAT WE WANT
  58. 71 @Override protected void onLayout(boolean changed, int left, int top,

    int right, int bottom) MarginLayoutParams layoutParams = (MarginLayoutParams) icon.getLayoutParams(); !// Figure out the x-coordinate and y-coordinate of the icon. int x = getPaddingLeft() + layoutParams.leftMargin; int y = getPaddingTop() + layoutParams.topMargin; }A WHAT WE WANT
  59. 72 @Override protected void onLayout(boolean changed, int left, int top,

    int right, int bottom) {A MarginLayoutParams layoutParams = (MarginLayoutParams) icon.getLayoutParams(); !// Figure out the x-coordinate and y-coordinate of the icon. int x = getPaddingLeft() + layoutParams.leftMargin; int y = getPaddingTop() + layoutParams.topMargin; !// Layout the icon. icon.layout(x, y, x + icon.getMeasuredWidth(), y + icon.getMeasuredHeight()); }A WHAT WE WANT
  60. 73 @Override protected void onLayout(boolean changed, int left, int top,

    int right, int bottom) {A … !// Calculate the x-coordinate of the title: icon's right coordinate + !// the icon's right margin. x += icon.getMeasuredWidth() + layoutParams.rightMargin; !// Add in the title's left margin. layoutParams = (MarginLayoutParams) titleView.getLayoutParams(); x += layoutParams.leftMargin; !// Calculate the y-coordinate of the title: this ViewGroup's top padding + !// the title's top margin y = getPaddingTop() + layoutParams.topMargin; !// Layout the title. titleView.layout(x, y, x + titleView.getMeasuredWidth(), y + titleView.getMeasuredHeight()); }A WHAT WE WANT
  61. 74 @Override protected void onLayout(boolean changed, int left, int top,

    int right, int bottom) {A … !// The subtitle has the same x-coordinate as the title. !// So no more calculating there. !// Calculate the y-coordinate of the subtitle: the title's bottom !// coordinate + the title's bottom margin. y += titleView.getMeasuredHeight() + layoutParams.bottomMargin; layoutParams = (MarginLayoutParams) subtitleView.getLayoutParams(); !// Add in the subtitle's top margin. y += layoutParams.topMargin; !// Layout the subtitle. subtitleView.layout(x, y, x + subtitleView.getMeasuredWidth(), y + subtitleView.getMeasuredHeight()); }A WHAT WE WANT
  62. 75 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { !//

    Measure icon. measureChildWithMargins(icon, widthMeasureSpec, 0, heightMeasureSpec, 0); !// Figure out how much width the icon used. MarginLayoutParams lp = (MarginLayoutParams) icon.getLayoutParams(); int widthUsed = icon.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; int heightUsed = 0; !// Measure title measureChildWithMargins( titleView, !// Pass width constraints and width already used. widthMeasureSpec, widthUsed, !// Pass height constraints and height already used. heightMeasureSpec, heightUsed ); !// Measure the Subtitle. measureChildWithMargins( subtitleView, !// Pass width constraints and width already used. widthMeasureSpec, widthUsed, !// Pass height constraints and height already used. heightMeasureSpec, titleView.getMeasuredHeight()); !// Calculate this view's measured width and height. !// Figure out how much total space the icon used. int iconWidth = icon.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; int iconHeight = icon.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; lp = (MarginLayoutParams) titleView.getLayoutParams(); !// Figure out how much total space the title used. int titleWidth = titleView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; int titleHeight = titleView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; lp = (MarginLayoutParams) subtitleView.getLayoutParams(); !// Figure out how much total space the subtitle used. int subtitleWidth = subtitleView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; int subtitleHeight = subtitleView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; !// The width taken by the children + padding. int width = getPaddingTop() + getPaddingBottom() + iconWidth + Math.max(titleWidth, subtitleWidth); !// The height taken by the children + padding. int height = getPaddingTop() + getPaddingBottom() + Math.max(iconHeight, titleHeight + subtitleHeight); !// Reconcile the measured dimensions with the this view's constraints and !// set the final measured width and height. setMeasuredDimension( resolveSize(width, widthMeasureSpec), resolveSize(height, heightMeasureSpec) ); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { MarginLayoutParams layoutParams = (MarginLayoutParams) icon.getLayoutParams(); !// Figure out the x-coordinate and y-coordinate of the icon. int x = getPaddingLeft() + layoutParams.leftMargin; int y = getPaddingTop() + layoutParams.topMargin; !// Layout the icon. icon.layout(x, y, x + icon.getMeasuredWidth(), y + icon.getMeasuredHeight()); !// Calculate the x-coordinate of the title: icon's right coordinate + !// the icon's right margin. x += icon.getMeasuredWidth() + layoutParams.rightMargin; !// Add in the title's left margin. layoutParams = (MarginLayoutParams) titleView.getLayoutParams(); x += layoutParams.leftMargin; !// Calculate the y-coordinate of the title: this ViewGroup's top padding + !// the title's top margin y = getPaddingTop() + layoutParams.topMargin; !// Layout the title. titleView.layout(x, y, x + titleView.getMeasuredWidth(), y + titleView.getMeasuredHeight()); !// The subtitle has the same x-coordinate as the title. So no more calculating there. !// Calculate the y-coordinate of the subtitle: the title's bottom coordinate + !// the title's bottom margin. y += titleView.getMeasuredHeight() + layoutParams.bottomMargin; layoutParams = (MarginLayoutParams) subtitleView.getLayoutParams(); !// Add in the subtitle's top margin. y += layoutParams.topMargin; !// Layout the subtitle. subtitleView.layout(x, y, x + subtitleView.getMeasuredWidth(), y + subtitleView.getMeasuredHeight()); }
  63. 76

  64. WHERE TO GO FROM HERE… ACCESSIBILITY. A11Y FTW. ADD IT.

    @KELLYSHUSTER CUSTOM DRAWABLES. CUSTOM DRAWABLE STATES. @RHARTER @MARCOSPAULOSD ALL THE DRAWING OPERATIONS. PATHS. FILTERS. SHADERS. OH MY! @ROMAINGUY @CHIUKI HARDWARE ACCELERATION. LEARN ABOUT LAYERS + DISPLAY LISTS. @WORKINGKILLS GO CUSTOM. TOUCH. GESTURES. @PBREAULT @CALREN24 PERFORMANCE. MEASUREMENT. TOOLS. @QUEENCODEMONKEY @FRANCOISBLAVOET
  65. CUSTOM VIEWS/VIEWGROUPS Custom Views and ViewGroups (Part 1) https://caster.io/episodes/ custom-views-and-viewgroups-part-1/

    Creating Custom Views https://developer.android.com/training/custom- views/index.html A deep dive into Android View constructors http://blog.danlew.net/ 2016/07/19/a-deep-dive-into-android-view-constructors/ Google I/O 2013 - Writing Custom Views for Android https://youtu.be/ NYtB6mlu7vA Enhancing Android UI with Custom Views https://newcircle.com/s/post/ 1663/ tutorial_enhancing_android_ui_with_custom_views_dave_smith_video 79
  66. CUSTOM VIEWS/VIEWGROUPS Custom Android Components http://chiuki.github.io/android-custom- components Custom Views and

    Performance https://youtu.be/zK2i7ivzK7M Taming Android UIs with Eric Burke of Square https://youtu.be/ jF6Ad4GYjRU Custom Layouts on Android http://lucasr.org/2014/05/12/custom- layouts-on-android/ Layout traversals on Android https://youtu.be/sdkcuvZCh1U Custom View Groups https://sriramramani.wordpress.com/ 2015/05/06/custom-viewgroups/ 80
  67. TOUCH/GESTURES Understanding Android Views and Gestures https:// academy.realm.io/posts/understanding-android-views-and- gestures-android-meetup-chang-2017/ Making

    Sense of the Touch System https://speakerdeck.com/ pbreault/making-sense-of-the-touch-system | https:// youtu.be/usBaTHZdXSI Making the View Interactive https://developer.android.com/ training/custom-views/making-interactive.html 81
  68. TEXT + DRAWING Typography on Android https://youtu.be/OgiZa22Zpus Android Textual Layout

    https://youtu.be/GZ0eKqvzJa8 Hinting Around: Text Demystified https:// www.youtube.com/watch?v=VS7co3TrgKE | https:// speakerdeck.com/rock3r/hinting-around-android-text- demystified Calligraphy. Custom fonts in Android the easy way… https://github.com/chrisjenx/Calligraphy 82
  69. DRAWING + DRAWABLES Fun with Android Shaders and Filters https://chiuki.github.io/

    android-shaders-filters Don’t Fear the Canvas https://youtu.be/KH8Ldp39TUk Custom drawable states in Android http://charlesharley.com/2012/ programming/custom-drawable-states-in-android Custom Drawables http://ryanharter.com/blog/2015/04/03/ custom-drawables/ The Magic World of Drawables https://youtu.be/1YjB1uUfxgE Hardware Acceleration https://developer.android.com/guide/ topics/graphics/hardware-accel.html 83
  70. PERFORMANCE Loving Lean layouts https://youtu.be/gwqQT5NrhUg Let’s Sprinkle some #PerfMatters on

    your ViewGroups https:// youtu.be/fEtyhj74JhU Eugenio Marletti: Clue + Highly Custom/Performant Components https://youtu.be/e-xWYiHAhjI 84