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

getBounds() : The Drawables Story

getBounds() : The Drawables Story

At Devoxx 2013, Cyril Mottier gave a great talk on Mastering Android Drawables. This talk goes in-depth on how to write your own custom drawables, and digs into the changes that were introduced into the Drawable API for Lollipop, the first major update to the Drawables class in a while.

We'll answer such questions as What is a Drawable? How do you make one? How do they interact with their Views?

We’ll talk about the native Drawable types, how custom Drawables can be included in XML, and the nuts and bolts of actually implementing one.

Finally, we’ll overview the changes that have been introduced to the Drawable API in Lollipop, and some of the challenges of backporting these to pre-Lollipop OS's.

Presented with Jamie Huson, Etsy at DroidCon Montreal 2015. http://www.droidcon.ca/speakers/46

67867d2677e7dd983000441ff0b1c089?s=128

NeiL saitug

April 10, 2015
Tweet

More Decks by NeiL saitug

Other Decks in Programming

Transcript

  1. getBounds() The$Drawables$Story Jamie&Huson!+!Lisa&Neigut!|!10#Avril#2015

  2. Drawables!

  3. "something+that+can+be+drawn." !Drawable.java,JavaDocs / "a#posi(oned#en(ty that$is$to$be$drawn$on$a$canvas." !Cyril'Mo*er

  4. What%Makes%Drawables%Awesome: • Drawing)is)fun! • Handling)State)Changes • Separa7on)of)View)Logic)from)Code • XML •

    Versa7lity
  5. None
  6. NinePatchDrawable

  7. ShapeDrawable

  8. BitmapDrawable

  9. LayerDrawable

  10. StateListDrawable

  11. Custom'Drawables

  12. BadgeDrawable

  13. FontDrawable

  14. FontDrawable

  15. None
  16. /res ../drawable +..+layered_drawable.xml

  17. <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item> <shape android:shape="oval"> <solid android:color="@color/purple"/> </shape> </item> ...

  18. layout.xml <TextView ... android:background="@drawable/layered_drawable"/>

  19. TOTAL%JAVA%CODE%WRITTEN:%$0

  20. Drawable(Redux,(vCustom.0

  21. /res ../drawable +..custom_drawable.xml

  22. <com.droidcon.drawables.custom xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" app:icon="@id/ic_etsy_e" app:color="@color/etsy_orange" > </com.droidcon.drawables.custom>

  23. layout.xml <TextView ... android:background="@drawable/custom_drawable"/>

  24. None
  25. Run$me'Error Caused by: org.xmlpull.v1.XmlPullParserException: Binary XML file line #2: __invalid

    drawable tag custom__
  26. Drawable.java public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs)

    throws XmlPullParserException, IOException { Drawable drawable;
  27. final String name = parser.getName(); if (name.equals("selector")) { drawable =

    new StateListDrawable(); } else if (name.equals("layer-list")) { drawable = new LayerDrawable(); } else if (name.equals("shape")) { drawable = new GradientDrawable(); } else if ...
  28. ... } else { throw new XmlPullParserException(parser.getPositionDescription() + ": invalid

    drawable tag " + name); }
  29. Seriously?

  30. None
  31. CustomDrawable drawable = new CustomDrawable(); drawable.setColorFilter(Color.BLACK, PorterDuff.Mode.DST); mView.setBackground(drawable);

  32. The$Drawable$API

  33. public class NinjaDrawable extends ColorDrawable {

  34. Methods(you(definitely(want(to(@Override • draw(Canvas+canvas)

  35. @Override draw(Canvas canvas)!(onDraw) • Just&like&a&View's&drawing. • Use&getBounds()&to&determine&drawing&area

  36. Methods,)good)to)know • getOpacity() • getIntrinsicWidth() • getMinimumWidth()

  37. getOpacity() public int getOpacity() { switch (mState.mUseColor >>> 24) {

    case 255: return PixelFormat.OPAQUE; case 0: return PixelFormat.TRANSPARENT; } return PixelFormat.TRANSLUCENT; }
  38. getIntrinsicWidth()!(onMeasure) public int getIntrinsicWidth() { return -1; } . public

    int getIntrinsicWidth() { return mBitmap.getWidth(); }
  39. getMinimumHeight\Width()!(getSuggestedMinimumHeight())

  40. getMinimumHeight\Width()!(getSuggestedMinimumHeight()) Bo#om%line:%implement%getIntrinsic,%not%getMinimum.

  41. Methods,)ninja)level • getConstantState()-and-mutate()

  42. getConstantState()

  43. mutate()

  44. Drawables)+)Views

  45. Drawable.Callback

  46. public static interface Callback { public void invalidateDrawable(Drawable who); public

    void scheduleDrawable(Drawable who, Runnable what, long when); public void unscheduleDrawable(Drawable who, Runnable what); }
  47. public class View implements Drawable.Callback {

  48. public void setBackground(Drawable d) { mBackground = d; d.setCallback(this); }

  49. public void invalidateDrawable(Drawable drawable) { ... final Rect dirty =

    drawable.getBounds(); invalidate(dirty.left, dirty.top, dirty.right, dirty.bottom); ...
  50. Drawable(Bounds

  51. Drawable.java setBounds(Rect rect) setBounds(int left, int top, int right, int

    bottom)
  52. None
  53. None
  54. Binding&it&All&Together

  55. • Measurement • Layout • Draw

  56. • Measurement • Layout • Draw

  57. During'Measurement • View&'>&mDrawable.getIntrinsic* • Calculates&dimens,&calls&children,&etc. • View&Measurements&'>&ParentView

  58. Some%me&before&Draw • Calculates*bounds*for*drawable • mDrawable.setBounds(...)

  59. During'Draw • mDrawable.draw(canvas)

  60. Caveat&Emptor

  61. ColorDrawable.java getIntrinsicHeight() { returns -1; }

  62. CompoundBu)on.java public void onDraw(Canvas canvas) { final int verticalGravity =

    getGravity() & Gravity.VERTICAL_GRAVITY_MASK; final int drawableHeight = buttonDrawable.getIntrinsicHeight(); final int drawableWidth = buttonDrawable.getIntrinsicWidth(); int top = 0; switch (verticalGravity) { case Gravity.BOTTOM: top = getHeight() - drawableHeight; break; case Gravity.CENTER_VERTICAL: top = (getHeight() - drawableHeight) / 2; break; } int bottom = top + drawableHeight; int left = isLayoutRtl() ? getWidth() - drawableWidth : 0; int right = isLayoutRtl() ? getWidth() : drawableWidth; buttonDrawable.setBounds(left, top, right, bottom); buttonDrawable.draw(canvas); }
  63. ImageView.java if (dwidth <= 0 || dheight <= 0 ||

    ScaleType.FIT_XY == mScaleType) { /* If the drawable has no intrinsic size, or we're told to scaletofit, then we just fill our entire view. */ mDrawable.setBounds(0, 0, vwidth, vheight); mDrawMatrix = null; } else { // We need to do the scaling ourself, so have the drawable // use its native size. mDrawable.setBounds(0, 0, dwidth, dheight); ... }
  64. Lollipop

  65. Ripple

  66. None
  67. None
  68. Lollipop&'&Touch&Events public class Drawable { ... public void setHotspot(float x,

    float y) {} public void setHotspotBounds(int, int, int, int) {} ... }
  69. Lollipop&'&Touch&Events public class View { ... @Override public void onTouchEvent(MotionEvent

    event) { switch(event.getAction()) { case MotionEvent.ACTION_MOVE: drawableHotspotChanged(x, y); ... break; } } }
  70. Lollipop&'&Outline public class Drawable { ... public void getOutline(Outline outline)

    {} ... }
  71. Lollipop&'&Dirty&Region public class Drawable { ... public Rect getDirtyBounds() {

    return getBounds(); } ... }
  72. None
  73. Lollipop&'&Theme public class Drawable { ... public void applyTheme(Theme) {

    } public boolean canApplyTheme() { } public Drawable createFromXml(Resources, XmlPullParser, Theme) public Drawable createFromXmlInner(Resources, XmlPullParser, AttributeSet, Theme) public void inflate(Resources, XmlPullParser, AttributeSet, Theme) ... }
  74. Lollipop&'&Tint public class Drawable { ... public void setTint(int) public

    void setTintList(ColorStateList list) public void setTintMode(Mode) ... }
  75. Lollipop&'&ConstantState public class ConstantState { public boolean canApplyTheme() public Drawable

    newDrawable(Resources, Theme) }
  76. VectorDrawable

  77. Backwards)Compa.bility MrVector!h#ps:/ /github.com/telly/MrVector

  78. Ripple

  79. None
  80. Ripple public class RippledImageView extends ImageView { ... @Override public

    boolean onTouchEvent(MotionEvent event) { updateHotspot(event.getX(), event.getY()); return super.onTouchEvent(event); } private void updateHotspot(float x, float y) { Drawable background = getBackground(); if (background != null && background instanceof RippleDrawableCompat) { ((RippleDrawableCompat) background).setHotspot(x, y); } Drawable src = getDrawable(); if (src != null && src instanceof RippleDrawableCompat) { ((RippleDrawableCompat) src).setHotspot(x, y); } } }
  81. Ripple @Override public void draw(@NonNull Canvas canvas) { // Clip

    to the dirty bounds, which will be the drawable bounds if we // have a mask or content and the ripple bounds if we're projecting. final Rect bounds = getDirtyBounds(); final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); canvas.clipRect(bounds); drawContent(canvas); drawBackgroundAndRipples(canvas); canvas.restoreToCount(saveCount); }
  82. Ripple public boolean draw(Canvas c, Paint p) { final boolean

    canUseHardware = c.isHardwareAccelerated(); if (mCanUseHardware != canUseHardware && mCanUseHardware) { // We've switched from hardware to non-hardware mode. Panic. cancelHardwareAnimations(true); } mCanUseHardware = canUseHardware; final boolean hasContent; if (canUseHardware && (mHardwareAnimating || mHasPendingHardwareExit)) { hasContent = drawHardware((HardwareCanvas) c, p); } else { hasContent = drawSoftware(c, p); } return hasContent; }
  83. Ripple Search: Android'Graphics'Pipeline'Bu3on'to'FrameBuffer" AOSP: h"ps:/ /source.android.com/devices/graphics/index.html

  84. None
  85. Thank&You! @JamieHuson,^.^,@ni/ynei