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

NeiL saitug

April 10, 2015
Tweet

More Decks by NeiL saitug

Other Decks in Programming

Transcript

  1. 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 ...
  2. getOpacity() public int getOpacity() { switch (mState.mUseColor >>> 24) {

    case 255: return PixelFormat.OPAQUE; case 0: return PixelFormat.TRANSPARENT; } return PixelFormat.TRANSLUCENT; }
  3. 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); }
  4. public void invalidateDrawable(Drawable drawable) { ... final Rect dirty =

    drawable.getBounds(); invalidate(dirty.left, dirty.top, dirty.right, dirty.bottom); ...
  5. 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); }
  6. 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); ... }
  7. Lollipop&'&Touch&Events public class Drawable { ... public void setHotspot(float x,

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

    event) { switch(event.getAction()) { case MotionEvent.ACTION_MOVE: drawableHotspotChanged(x, y); ... break; } } }
  9. 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) ... }
  10. Lollipop&'&Tint public class Drawable { ... public void setTint(int) public

    void setTintList(ColorStateList list) public void setTintMode(Mode) ... }
  11. 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); } } }
  12. 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); }
  13. 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; }