package org.lab99.mdt.drawable; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; /** * A wrapper {@link Drawable} to implement the material design concept. * <p/> * The drawable contains several component: * <ul> * <li>An original {@link Drawable} for the normal background.</li> * <li>{@link RippleDrawable} for handling the ripple effect</li> * <li>{@link ShadowDrawable} for handling the shadow beneath the view</li> * </ul> */ public class PaperDrawable extends ProxyDrawable implements Drawable.Callback { Context mContext; public PaperDrawable(Context context) { this(null, null, context); } public PaperDrawable(Drawable original, Context context) { this(original, null, context); } PaperDrawable(Drawable original, PaperState state, Context context) { super(original, state); setContext(context); } PaperDrawable(PaperState state, Resources res) { super(state, res); } @Override protected ProxyState createConstantState(ProxyState orig, Resources res) { return new PaperState((PaperState) orig, res); } /** * Draw on the canvas. * * Draw the shadow first, and then draw the original drawable, then draw the ripple effect finally. * * @param canvas The given canvas. */ @Override public void draw(Canvas canvas) { getShadowSelf().draw(canvas); super.draw(canvas); getRipple().draw(canvas); getShadowChild().draw(canvas); } /** * Set the given state. * * @param stateSet the given states. * @return */ @Override public boolean setState(int[] stateSet) { boolean ret = super.setState(stateSet); getShadowSelf().setState(stateSet); getRipple().setState(stateSet); return ret; } @Override public boolean isStateful() { return true; } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); getShadowSelf().setBounds(bounds); getRipple().setBounds(bounds); } private PaperState getPaperState() { return ((PaperState) getConstantState()); } ShadowDrawable getShadowSelf() { return getPaperState().mShadowSelf; } RippleDrawable getRipple() { return getPaperState().mRipple; } Drawable getShadowChild() { return getPaperState().mShadowChildren; } /** * Get the touch tracker, which is tracking the touch location. * * @return The {@link TouchTracker} object. */ public TouchTracker getTouchTracker() { return getPaperState().mTouchTracker; } /** * Check whether the ripple effect is enabled or not. * @return Return true if the ripple is enabled. */ public boolean isRippleEnabled() { return getRipple().isEnabled(); } /** * Enable the ripple effect. * @param enabled Set to true if want to enable the ripple effect. */ public void setRippleEnabled(boolean enabled) { getRipple().setEnabled(enabled); } /** * Check whether the ripple will be triggered by touch event. * @return Return true if the ripple will be triggered by touch event. */ public boolean isRippleOnTouchEnabled() { return getRipple().getOnStateChangedListener().isEnabled(); } /** * Enable the ability to trigger the ripple effect via touch event. * @param enabled Set to true if want to trigger the ripple effect by touch event. */ public void setRippleOnTouchEnabled(boolean enabled) { getRipple().getOnStateChangedListener().setEnabled(enabled); } /** * Get the shadow depth * @return Return the shadow depth. */ public float getDepth() { return getShadowSelf().getDepth(); } /** * Set the shadow depth for drawing the shadow. * * @param depth The depth of shadow, the effective range is from 0 to 5. */ public void setDepth(float depth) { getShadowSelf().setDepth(depth); } /** * Get the rotation value from the view. * @return the rotation value. */ public float getRotation() { return getShadowSelf().getRotation(); } /** * Set the rotation value of the view. * * The rotation of the attached view should be tracked, as the shadow should always be drawn under * the view, in another word, the shadow should always represent the widget depth as the light is * from the top, as the material design guideline said, no matter how the widget is rotated. * * @param rotation Set the same rotation value of the attached view. */ public void setRotation(float rotation) { getShadowSelf().setRotation(rotation); } /** * Set {@link Context} object. * As {@link Context} object is required in {@link android.support.v8.renderscript.RenderScript} and {@link org.lab99.mdt.utils.Utils#getPixelFromDip(Context, float)}. * @param context The {@link Context} object. */ public void setContext(Context context) { mContext = context; getShadowSelf().setContext(mContext); } /* Ripple */ /** * Set ripple color. * @param color The ripple color. */ public void setRippleColor(int color) { getRipple().setRippleColor(color); } static class PaperState extends ProxyState { // 0 - Self Shadow // 1 - Original Background // 2 - Ripple // 3 - Children Shadow ShadowDrawable mShadowSelf; RippleDrawable mRipple; Drawable mShadowChildren; TouchTracker mTouchTracker; PaperState(PaperState orig, Resources res) { super(orig, res); } @Override public Drawable newDrawable() { return new PaperDrawable(this, null); } @Override protected void initWithState(ProxyState orig, Resources res) { super.initWithState(orig, res); PaperState state = (PaperState) orig; mShadowSelf = (ShadowDrawable) state.mShadowSelf.getConstantState().newDrawable(res); mRipple = (RippleDrawable) state.mRipple.getConstantState().newDrawable(res); mShadowChildren = state.mShadowChildren.getConstantState().newDrawable(res); setTouchTracker(state.mTouchTracker); } @Override protected void initWithoutState(Resources res) { super.initWithoutState(res); mShadowSelf = new ShadowDrawable(); mRipple = new RippleDrawable(); // TODO: to be remove mShadowChildren = new ColorDrawable(Color.TRANSPARENT); setTouchTracker(new TouchTracker()); } @Override protected void setCallback(Callback callback) { super.setCallback(callback); mShadowSelf.setCallback(callback); mRipple.setCallback(callback); mShadowChildren.setCallback(callback); } @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || who == mShadowSelf || who == mRipple || who == mShadowChildren; } @Override public void setOriginal(Drawable original) { super.setOriginal(original); if (original != null) { Drawer masker = new Drawer() { @Override public void draw(Canvas canvas) { if (getOriginal() != null) { getOriginal().draw(canvas); } } }; mShadowSelf.setMaskDrawer(masker); mRipple.setMaskDrawer(masker); } else { // clear the mask as original is null mShadowSelf.setMaskDrawer(null); mRipple.setMaskDrawer(null); } } public void setShadowSelf(ShadowDrawable shadow) { if (shadow != null) { mShadowSelf = (ShadowDrawable) shadow.getConstantState().newDrawable(); } } public void setRipple(RippleDrawable ripple) { if (ripple != null) { mRipple = (RippleDrawable) ripple.getConstantState().newDrawable(); } } public void setShadowChild(Drawable shadow) { if (shadow != null) { mShadowChildren = shadow.getConstantState().newDrawable(); } } public void setTouchTracker(TouchTracker tracker) { mTouchTracker = tracker; mRipple.setTouchTracker(mTouchTracker); } } }