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

Don’t Fear the Canvas (Android KW June 2016)

Don’t Fear the Canvas (Android KW June 2016)

Android provides a wide variety of built-in views and layouts. Sometimes designs can’t be created with these views. Other times implementing a design with these views requires a complex view hierarchy. This talk will cover how to create custom views targeted to your needs. I’ll explain when and when not to go custom, and provide tips and tricks for getting the most out of your views.

https://www.youtube.com/watch?v=KH8Ldp39TUk

Eefb68011178f8d4e7ae59d1d8f0b0b5?s=128

Matthew Precious

June 28, 2016
Tweet

Transcript

  1. Matt Precious Don’t Fear the Canvas

  2. Custom view?

  3. public class CustomView extends View { //  }x

  4. public class CustomView extends TextView {y //  }x

  5. public class CustomView extends LinearLayout {y //  }x TextView

  6. public class CustomView extends LinearLayout { public void bind(Data data)

    { //  } }x
  7. public class CustomView extends LinearLayout { public void show() {

    //  } }x b i n d ( D a t a d a t a ) }x
  8. M

  9. A

  10. J

  11. S

  12. S

  13. S

  14. S

  15. S

  16. S

  17. M

  18. M <FrameLayout>
 </FrameLayout>

  19. M <FrameLayout>
 <TextView/>
 </FrameLayout>

  20. M <FrameLayout>
 <TextView/>
 <ImageView/>
 </FrameLayout>

  21. M <FrameLayout>
 <TextView/>
 <ImageView/>
 <View/>
 </FrameLayout>

  22. M <FrameLayout>
 <TextView/>
 <ImageView/>
 <View/> <View/>
 </FrameLayout>

  23. M <FrameLayout android:foreground="…">
 <TextView/>
 <ImageView/>
 <View/>
 </FrameLayout>

  24. M

  25. M

  26. M 70px

  27. M 70px 68px

  28. M 70px 68px ≠

  29. @Override protected void onDraw(Canvas canvas) { }x

  30. Canvas

  31. Canvas • drawLine()

  32. Canvas • drawLine() • drawRect()

  33. Canvas • drawLine() • drawRect() • drawOval()

  34. Canvas • drawLine() • drawRect() • drawOval() • drawText()

  35. Canvas • drawLine() • drawRect() • drawOval() • drawText() •

    drawColor()
  36. Canvas • drawLine() • drawRect() • drawOval() • drawText() •

    drawColor() • drawPath()
  37. Canvas • drawLine() • drawRect() • drawOval() • drawText() •

    drawColor() • drawPath() • drawBitmap()
  38. Canvas • drawLine() • drawRect() • drawOval() • drawText() •

    drawColor() • drawPath() • drawBitmap() • …
  39. Canvas • drawLine() • drawRect() • drawOval() • drawText() •

    drawColor() • drawPath() • drawBitmap() • … • translate()
  40. Canvas • drawLine() • drawRect() • drawOval() • drawText() •

    drawColor() • drawPath() • drawBitmap() • … • translate() • scale()
  41. Canvas • drawLine() • drawRect() • drawOval() • drawText() •

    drawColor() • drawPath() • drawBitmap() • … • translate() • scale() • rotate()
  42. Canvas • drawLine() • drawRect() • drawOval() • drawText() •

    drawColor() • drawPath() • drawBitmap() • … • translate() • scale() • rotate() • skew()
  43. Canvas • drawLine() • drawRect() • drawOval() • drawText() •

    drawColor() • drawPath() • drawBitmap() • … • translate() • scale() • rotate() • skew() • clipRect()
  44. Canvas • drawLine() • drawRect() • drawOval() • drawText() •

    drawColor() • drawPath() • drawBitmap() • … • translate() • scale() • rotate() • skew() • clipRect() • …
  45. @Override protected void onDraw(Canvas canvas) { }x

  46. @Override protected void onDraw(Canvas canvas) { }x

  47. @Override protected void onDraw(Canvas canvas) { }x (0, 0)

  48. @Override protected void onDraw(Canvas canvas) { }x (0, 0) (w,

    h)
  49. @Override protected void onDraw(Canvas canvas) { }x

  50. @Override protected void onDraw(Canvas canvas) { canvas.drawRect(5, 5, 10, 10,

    paint); }x
  51. @Override protected void onDraw(Canvas canvas) { canvas.drawRect(5, 5, 10, 10,

    paint); }x
  52. @Override protected void onDraw(Canvas canvas) { canvas.drawRect(5, 5, 10, 10,

    paint); canvas.translate(5, 15); }x
  53. @Override protected void onDraw(Canvas canvas) { canvas.drawRect(5, 5, 10, 10,

    paint); canvas.translate(5, 15); }x (0, 0)
  54. @Override protected void onDraw(Canvas canvas) { canvas.drawRect(5, 5, 10, 10,

    paint); canvas.translate(5, 15); }x (0, 0)
  55. @Override protected void onDraw(Canvas canvas) { canvas.drawRect(5, 5, 10, 10,

    paint); canvas.translate(5, 15); canvas.drawOval(0, 0, 5, 5, paint); }x
  56. @Override protected void onDraw(Canvas canvas) { canvas.drawRect(5, 5, 10, 10,

    paint); canvas.translate(5, 15); canvas.drawOval(0, 0, 5, 5, paint); }x
  57. public class AvatarView extends ImageView { //  }

  58. backgroundPaint = new Paint();
 backgroundPaint.setAntiAlias(true);
 backgroundPaint.setStyle(Paint.Style.FILL); // TODO: Set color

    based on user.
  59. @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); }x

  60. @Override protected void onDraw(Canvas canvas) { canvas.drawOval(0, 0, getMeasuredWidth(), getMeasuredHeight(),

    backgroundPaint); super.onDraw(canvas); }x
  61. @Overrideyprotected void onMeasure(…) {
 super.onMeasure(…);
 bounds.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
 }

    @Override protected void onDraw(Canvas canvas) { canvas.drawOval(bounds, backgroundPaint); super.onDraw(canvas); }x
  62. borderPaint = new Paint();
 borderPaint.setAntiAlias(true);
 borderPaint.setColor(borderColor);
 borderPaint.setStyle(Paint.Style.STROKE);
 borderPaint.setStrokeWidth(borderWidth);

  63. @Override protected void onDraw(Canvas canvas) { canvas.drawOval(bounds, backgroundPaint); super.onDraw(canvas); }x

  64. @Override protected void onDraw(Canvas canvas) { canvas.drawOval(bounds, backgroundPaint); super.onDraw(canvas); canvas.drawOval(bounds,

    borderPaint); }x
  65. pressPaint = new Paint();
 pressPaint.setAntiAlias(true);
 pressPaint.setColor(pressColor);
 pressPaint.setStyle(Paint.Style.FILL);

  66. @Override protected void onDraw(Canvas canvas) { canvas.drawOval(bounds, backgroundPaint); super.onDraw(canvas); canvas.drawOval(bounds,

    borderPaint); }x
  67. @Override protected void onDraw(Canvas canvas) { canvas.drawOval(bounds, backgroundPaint); super.onDraw(canvas); canvas.drawOval(bounds,

    borderPaint); if (isPressed()) {
 canvas.drawOval(bounds, pressPaint);
 } }x
  68. @Override protected void drawableStateChanged() {
 super.drawableStateChanged();
 invalidate();
 }

  69. M

  70. M Baseline

  71. M Baseline Descent

  72. M Baseline Descent g

  73. M Baseline Descent Ascent g

  74. M Baseline Descent Ascent g É

  75. M Baseline Descent Bottom Ascent g É

  76. M Baseline Descent Bottom Ascent Top g É

  77. M Baseline Descent Bottom Ascent Top g É

  78. textPaint = new Paint();
 textPaint.setAntiAlias(true);
 textPaint.setColor(textColor);
 textPaint.setTextSize(textSize);
 textPaint.setTextAlign(Paint.Align.CENTER);

  79. public void setUser(User user) { backgroundPaint.setColor(user.color());
 initial = user.initial(); invalidate();

    }x
  80. public void setUser(User user) { backgroundPaint.setColor(user.color());
 initial = user.initial(); textPaint.getTextBounds(initial,

    0, initial.length(), textBounds);
 invalidate(); }x
  81. @Override protected void onDraw(Canvas canvas) { canvas.drawOval(bounds, backgroundPaint); super.onDraw(canvas); canvas.drawOval(bounds,

    borderPaint); if (isPressed()) {
 canvas.drawOval(bounds, pressPaint);
 }y }x
  82. @Override protected void onDraw(Canvas canvas) { canvas.drawOval(bounds, backgroundPaint); int textBottom

    = round((bounds.height() / 2f) + (textBounds.height() / 2f)); super.onDraw(canvas); canvas.drawOval(bounds, borderPaint); if (isPressed()) {
 canvas.drawOval(bounds, pressPaint);
 }y }x
  83. @Override protected void onDraw(Canvas canvas) { canvas.drawOval(bounds, backgroundPaint); int textBottom

    = round((bounds.height() / 2f) + (textBounds.height() / 2f)); canvas.drawText(initial, width / 2f, textBottom, textPaint); super.onDraw(canvas); canvas.drawOval(bounds, borderPaint); if (isPressed()) {
 canvas.drawOval(bounds, pressPaint);
 }y }x
  84. M

  85. None
  86. None
  87. None
  88. canvas.drawLine(2, 5, 14, 5, linePaint);

  89. canvas.drawLine(2, 5, 14, 5, linePaint);

  90. canvas.drawLine(2, 5, 14, 5, linePaint); D

  91. canvas.drawLine(2, 5, 14, 5, linePaint);

  92. canvas.drawLine(2, 5, 14, 5, linePaint);

  93. @Overrideyprotected void onMeasure(…) {
 super.onMeasure(…);
 bounds.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
 }x

  94. @Overrideyprotected void onMeasure(…) {
 super.onMeasure(…);
 bounds.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); borderBounds.set(bounds);


    }x
  95. @Overrideyprotected void onMeasure(…) {
 super.onMeasure(…);
 bounds.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); borderBounds.set(bounds);

    borderBounds.inset(halfBorderWidth, halfBorderWidth);
 }x
  96. M

  97. S

  98. S M

  99. public class AvatarView extends ImageView {y //  }x

  100. public class AvatarPlaceholder extends Drawable {y //  }x View

    ImageView
  101. @Overrideyprotected void onMeasure(…) {
 //  }x

  102. @Override public void setBounds(…) {
 //  }x p r

    o t e c t e d onMeasure

  103. @Override protected void onDraw(Canvas canvas) { //  }

  104. picasso .load(user.image()) .placeholder(new AvatarPlaceholder(…)) .into(avatarView);

  105. attrs.xml <declare-styleable name="AvatarView">
 <attr name="textColor" format="color"/>
 <attr name="textSize" format="dimension"/>
 <attr

    name="borderColor" format="color"/>
 <attr name="borderWidth" format="dimension"/>
 <attr name="pressColor" format="color"/>
 </declare-styleable>
  106. attrs.xml <declare-styleable name="AvatarView">
 <attr name="android:textColor"/>
 <attr name="android:textSize"/>
 <attr name="borderColor" format="color"/>


    <attr name="borderWidth" format="dimension"/>
 <attr name="pressColor" format="color"/>
 </declare-styleable> 
 format="color"
 format="dimension"

  107. TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AvatarView, 0, 0); int borderColor =

    a.getColor( R.styleable.AvatarView_borderColor, defaultBorderColor);
 int borderWidth = a.getDimensionPixelSize( R.styleable.AvatarView_borderWidth, defaultBorderWidth);
 // 
 a.recycle();
  108. <com.squareup.cash.ui.widget.AvatarView android:layout_width="@dimen/avatar_size" android:layout_height="@dimen/avatar_size" android:textColor="@color/white" android:textSize="@dimen/avatar_text" app:borderColor="@color/white" app:borderWidth="@dimen/avatar_border" app:pressColor="@color/press"
 />

  109. None
  110. None
  111. None
  112. None
  113. path.moveTo(p1);

  114. path.moveTo(p1); path.lineTo(p2);

  115. path.moveTo(p1); path.lineTo(p2); path.arcTo(p3);

  116. path.moveTo(p1); path.lineTo(p2); path.arcTo(p3); path.lineTo(p4);

  117. path.moveTo(p1); path.lineTo(p2); path.arcTo(p3); path.lineTo(p4); path.lineTo(p5);

  118. path.moveTo(p1); path.lineTo(p2); path.arcTo(p3); path.lineTo(p4); path.lineTo(p5); path.lineTo(p6);

  119. path.moveTo(p1); path.lineTo(p2); path.arcTo(p3); path.lineTo(p4); path.lineTo(p5); path.lineTo(p6); path.arcTo(p7);

  120. path.moveTo(p1); path.lineTo(p2); path.arcTo(p3); path.lineTo(p4); path.lineTo(p5); path.lineTo(p6); path.arcTo(p7); path.lineTo(p8);

  121. path.moveTo(p1); path.lineTo(p2); path.arcTo(p3); path.lineTo(p4); path.lineTo(p5); path.lineTo(p6); path.arcTo(p7); path.lineTo(p8); path.arcTo(p1);

  122. path.moveTo(p1); path.lineTo(p2); path.arcTo(p3); path.lineTo(p4); path.lineTo(p5); path.lineTo(p6); path.arcTo(p7); path.lineTo(p8); path.arcTo(p1); path.close();

  123. None
  124. canvas.drawPath(path, bubblePaint);

  125. None
  126. PathEffect pathEffect = new DashPathEffect( new float[] { dashLength, dashGap

    }, 0);
 strokePaint.setPathEffect(pathEffect);
  127. PathEffect pathEffect = new DashPathEffect( new float[] { dashLength, dashGap

    }, 0);
 strokePaint.setPathEffect(pathEffect);
  128. PathEffect pathEffect = new DashPathEffect( new float[] { dashLength, dashGap

    }, 0);
 strokePaint.setPathEffect(pathEffect);
  129. PathEffect pathEffect = new DashPathEffect( new float[] { dashLength, dashGap

    }, 0);
 strokePaint.setPathEffect(pathEffect);
  130. PathEffect pathEffect = new DashPathEffect( new float[] { dashLength, dashGap

    }, phase);
 strokePaint.setPathEffect(pathEffect);
  131. PathEffect pathEffect = new DashPathEffect( new float[] { dashLength, dashGap

    }, phase);
 strokePaint.setPathEffect(pathEffect); Romain Guy PathEffect
  132. None
  133. None
  134. None
  135. None
  136. None
  137. None
  138. None
  139. None
  140. Canvas all the things?

  141. No.

  142. No.*

  143. Accessibility

  144. Accessibility • setContentDescription()

  145. Accessibility • setContentDescription() • ExploreByTouchHelper

  146. Accessibility • setContentDescription() • ExploreByTouchHelper • Google I/O 2013

  147. Tips

  148. Tips • Don’t break Preview

  149. Tips • Don’t break Preview • isInEditMode()

  150. Tips • Don’t break Preview • isInEditMode() • Embrace Preview

  151. Tips • Don’t break Preview • isInEditMode() • Embrace Preview

    • isInEditMode()
  152. Tips • Don’t break Preview • isInEditMode() • Embrace Preview

    • isInEditMode() • tools:
  153. Tips • Don’t break Preview • isInEditMode() • Embrace Preview

    • isInEditMode() • tools: • Translate/scale/rotate
  154. Tips • Don’t break Preview • isInEditMode() • Embrace Preview

    • isInEditMode() • tools: • Translate/scale/rotate • Save/restore
  155. Tips • Don’t break Preview • isInEditMode() • Embrace Preview

    • isInEditMode() • tools: • Translate/scale/rotate • Save/restore • Be aware of pixel boundaries
  156. Tips • Don’t break Preview • isInEditMode() • Embrace Preview

    • isInEditMode() • tools: • Translate/scale/rotate • Save/restore • Be aware of pixel boundaries • Cast/round to int
  157. Tips • Don’t break Preview • isInEditMode() • Embrace Preview

    • isInEditMode() • tools: • Translate/scale/rotate • Save/restore • Be aware of pixel boundaries • Cast/round to int • Inset/translate strokes
  158. Matt Precious Don’t Fear the Canvas +MatthewPrecious @mattprec