package org.lab99.mdt.drawable; import android.content.Context; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.v8.renderscript.RenderScript; import org.lab99.mdt.utils.Utils; class ShadowDrawable extends Drawable { // Max Depth private final static float DEPTH_MAX = 5; // variables private ShadowState mState; public ShadowDrawable() { this(null); mState.mOnStateChangedListener = new ShadowStateChanger(this); } ShadowDrawable(ShadowState state) { mState = new ShadowState(state); } public void destroy() { mState.destroy(); } @Override public void draw(Canvas canvas) { mState.mRender.draw(canvas, mState.mShadow); } @Override public int getAlpha() { return mState.mShadow.mAlpha; } @Override public void setAlpha(int alpha) { mState.mShadow.mAlpha = alpha; invalidateSelf(); } @Override public void setColorFilter(ColorFilter cf) { } @Override public int getOpacity() { return 0; } // Getters & Setters public void setRenderScript(RenderScript renderScript) { mState.setRenderScript(renderScript); invalidateSelf(); } public void setMaskDrawer(Drawer drawer) { mState.mShadow.mMaskDrawer = drawer; invalidateSelf(); } public float getDepth() { return mState.mShadow.mDepth; } public void setDepth(float depth) { float d; if (depth < 0) { d = 0; } else if (depth > DEPTH_MAX) { d = DEPTH_MAX; } else { d = depth; } if (d != getDepth()) { mState.mShadow.mDepth = d; } invalidateSelf(); } public float getRotation() { return mState.mShadow.mRotation; } public void setRotation(float rotation) { mState.mShadow.mRotation = rotation; invalidateSelf(); } public OnStateChangedListener getOnStateChangedListener() { return mState.mOnStateChangedListener; } public void setOnStateChangedListener(OnStateChangedListener listener) { mState.mOnStateChangedListener = listener; } @Override public boolean setState(int[] stateSet) { boolean changed = false; if (getOnStateChangedListener() != null) { changed = getOnStateChangedListener().onStateChange(getState(), stateSet); } return super.setState(stateSet) || changed; } @Override protected boolean onStateChange(int[] stateSet) { return getOnStateChangedListener() != null && getOnStateChangedListener().onStateChange(getState(), stateSet); } @Override protected void onBoundsChange(Rect bounds) { mState.mShadow.mBounds = bounds; invalidateSelf(); } public void setContext(Context context) { ((ShadowState) getConstantState()).mRender.setContext(context); } @Override public ConstantState getConstantState() { return mState; } static class Shadow { // Offset function calculate from offset set {1.5, 3, 10, 14, 19} // Material Design Guideline > Layout > Dimensionality > Shadows // offset(depth) = 0.57 * depth^2 + 0.91 * depth; private final static float SHADOW_OFFSET_A = 0.57f; private final static float SHADOW_OFFSET_B = 0.91f; // blur_radius(offset) = 1.5 * offset; private final static float SHADOW_BLUR_RADIUS_OFFSET_FACTOR = 1.5f; // properties float mDepth; Rect mBounds; float mRotation; int mAlpha; // handler Drawer mMaskDrawer; public Shadow() { mBounds = new Rect(); mAlpha = 255; } public Shadow(Shadow o) { mDepth = o.mDepth; mBounds = new Rect(o.mBounds); mRotation = o.mRotation; mAlpha = o.mAlpha; mMaskDrawer = o.mMaskDrawer; } public float getShadowBlurRadius() { float offset = SHADOW_OFFSET_A * (mDepth * mDepth) + SHADOW_OFFSET_B * mDepth; return offset * SHADOW_BLUR_RADIUS_OFFSET_FACTOR; } public float getShadowOffset(Context context) { // offset = (a * depth * depth) + (b * depth) float offset = SHADOW_OFFSET_A * (mDepth * mDepth) + SHADOW_OFFSET_B * mDepth; return Utils.getPixelFromDip(context, offset); } } // State private static class ShadowState extends ConstantState { // Shadow Shadow mShadow; // Render ShadowRender mRender; // Handler OnStateChangedListener mOnStateChangedListener; ShadowState(ShadowState orig) { if (orig != null) initWithState(orig); else initWithoutState(); } private void initWithState(ShadowState orig) { // shadow mShadow = new Shadow(orig.mShadow); // render mRender = new ShadowRender(orig.mRender); // Handler mOnStateChangedListener = orig.mOnStateChangedListener; } private void initWithoutState() { mShadow = new Shadow(); mRender = new ShadowRender((RenderScript) null); // everything else is either 0 or null } public void destroy() { if (mRender != null) { mRender.destroy(); } } // Getters & Setters public void setRenderScript(RenderScript renderScript) { if (renderScript != null) { if (mRender == null) { mRender = new ShadowRender(renderScript); } else { mRender.setRenderScript(renderScript); } } } @Override public Drawable newDrawable() { return new ShadowDrawable(this); } @Override public int getChangingConfigurations() { return 0; } } }