package com.s16.drawing;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.TypedValue;
public class HomeUpDrawable extends Drawable implements Animatable {
private static final int DEFAULT_INTRINSIC_SIZE = 32; // 50
private static final float ARROW_HEAD_ANGLE = (float)Math.toRadians(45.0D);
private static final long FRAME_DURATION = 1000 / 60;
public static final int DEFAULT_ARROW_COLOR = 0xA0FFFFFF;
public static final int DEFAULT_ARROW_COLOR_LIGHT = 0x60000000;
private static final boolean MATERIAL_SDK_INT = android.os.Build.VERSION.SDK_INT > 20;
public static final int MODE_NORMAL = 0;
public static final int MODE_HOME_UP = 1;
public static final int MODE_DRAWER = 2;
private Context mContext;
private final int mMode;
private final Drawable mIcon;
private final Path mArrowPath;
private final Paint mArrowPaint;
private final Paint mPaint;
private float mProgress;
private float mRotate;
private final int mIntrinsicWidth;
private final int mIntrinsicHeight;
protected float mBarGap;
protected float mBarSize;
protected float mBarThickness;
protected float mMiddleArrowSize;
protected float mTopBottomArrowSize;
private final Runnable mUpdater = new Runnable() {
@Override
public void run() {
if(mRunning) {
if (mProgress >= 0.0f) {
mProgress += 0.1f;
if (mProgress >= 1.0f) {
mProgress = 1.0f;
unscheduleSelf(this);
mRunning = false;
} else {
scheduleSelf(this, SystemClock.uptimeMillis() + FRAME_DURATION);
}
} else {
mProgress -= 0.1f;
if (mProgress <= 0.0f) {
mProgress = 0.0f;
unscheduleSelf(this);
mRunning = false;
} else {
scheduleSelf(this, SystemClock.uptimeMillis() + FRAME_DURATION);
}
}
updateBound();
invalidateSelf();
}
}
};
private boolean mRunning;
public HomeUpDrawable(Context context) {
this(context, null, DEFAULT_ARROW_COLOR, MODE_NORMAL);
}
public HomeUpDrawable(Context context, int mode) {
this(context, null, DEFAULT_ARROW_COLOR, mode);
}
public HomeUpDrawable(Context context, int arrowColor, int mode) {
this(context, null, arrowColor, mode);
}
public HomeUpDrawable(Context context, Drawable icon, int mode) {
this(context, icon, DEFAULT_ARROW_COLOR, mode);
}
public HomeUpDrawable(Context context, Drawable icon, int arrowColor, int mode) {
mContext = context;
if (icon == null) {
ApplicationInfo applicationInfo = context.getApplicationInfo();
mIcon = context.getResources().getDrawable(applicationInfo.icon);
} else {
mIcon = icon;
}
mMode = mode;
DisplayMetrics dm = context.getResources().getDisplayMetrics();
mBarSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 18, dm);
mTopBottomArrowSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 11, dm);
mBarThickness = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, dm);
mBarGap = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, dm);
mMiddleArrowSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, dm);
mArrowPath = new Path();
mArrowPaint = new Paint();
mArrowPaint.setStyle(Paint.Style.FILL);
mArrowPaint.setColor(arrowColor);
mPaint = new Paint();
mPaint.setColor(arrowColor);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.SQUARE);
mPaint.setStrokeWidth(mBarThickness);
final int defaultIntrinsicSize = getDefaultIntrinsicSize(context);
int baseSize = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, defaultIntrinsicSize, dm);
if (MATERIAL_SDK_INT) {
mIntrinsicHeight = baseSize;
mIntrinsicWidth = baseSize;
} else {
mIntrinsicHeight = (baseSize/4) * 3;
mIntrinsicWidth = baseSize;
}
}
public static int getDefaultIntrinsicSize(Context context) {
int screenLayout = (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK);
switch (screenLayout) {
case 1: // mdpi
return DEFAULT_INTRINSIC_SIZE;
case 2: // hdpi
return (int)(DEFAULT_INTRINSIC_SIZE * 1.5f);
case 3: // xhdpi
case 4: // xxdpi
return DEFAULT_INTRINSIC_SIZE * 2;
//case 4: // xxdpi
// return DEFAULT_INTRINSIC_SIZE * 3;
default:
break;
}
if (screenLayout > 4) return DEFAULT_INTRINSIC_SIZE * 3;
return DEFAULT_INTRINSIC_SIZE;
}
protected float lerp(float paramFloat1, float paramFloat2, float paramFloat3) {
return paramFloat1 + paramFloat3 * (paramFloat2 - paramFloat1);
}
private void createArrowPath(RectF bounds) {
if (mMode == MODE_NORMAL) return;
float progress = (mMode == MODE_HOME_UP) ? 1.0f : mProgress;
if (MATERIAL_SDK_INT) {
float f1 = lerp(mBarSize, mTopBottomArrowSize, progress);
float f2 = lerp(mBarSize, mMiddleArrowSize, progress);
float f3 = lerp(0.0F, mBarThickness / 2.0F, progress);
float f4 = lerp(0.0F, ARROW_HEAD_ANGLE, progress);
float f5 = 0.0F;
float f6 = 180.0F;
mRotate = lerp(f5, f6, progress);
float f8 = lerp(mBarGap + mBarThickness, 0.0F, progress);
mArrowPath.rewind();
float f9 = -f2 / 2.0F;
mArrowPath.moveTo(f9 + f3, 0.0F);
mArrowPath.rLineTo(f2 - f3, 0.0F);
float f10 = (float) Math.round(f1 * Math.cos(f4));
float f11 = (float) Math.round(f1 * Math.sin(f4));
mArrowPath.moveTo(f9, f8);
mArrowPath.rLineTo(f10, f11);
mArrowPath.moveTo(f9, -f8);
mArrowPath.rLineTo(f10, -f11);
mArrowPath.moveTo(0.0F, 0.0F);
} else {
float width = bounds.width();
float height = bounds.height();
float s = Math.min(width, height) / 48;
if (mMode == MODE_HOME_UP) {
mArrowPath.rewind();
mArrowPath.moveTo(s * 26, s * 2);
mArrowPath.lineTo(s * 35, s * 2);
mArrowPath.lineTo(s * 21, s * 23);
mArrowPath.lineTo(s * 34, s * 44);
mArrowPath.lineTo(s * 25, s * 44);
mArrowPath.lineTo(s * 12, s * 23);
mArrowPath.lineTo(s * 26, s * 2);
} else if (mMode == MODE_DRAWER) {
float dx = 21.0f + (progress * 10.0f);
mArrowPath.rewind();
mArrowPath.moveTo(s * 1, s * 4);
mArrowPath.lineTo(s * dx, s * 4);
mArrowPath.lineTo(s * dx, s * 12);
mArrowPath.lineTo(s * 1, s * 12);
mArrowPath.lineTo(s * 1, s * 4);
mArrowPath.moveTo(s * 1, s * 22);
mArrowPath.lineTo(s * dx, s * 22);
mArrowPath.lineTo(s * dx, s * 30);
mArrowPath.lineTo(s * 1, s * 30);
mArrowPath.lineTo(s * 1, s * 22);
mArrowPath.moveTo(s * 1, s * 40);
mArrowPath.lineTo(s * dx, s * 40);
mArrowPath.lineTo(s * dx, s * 48);
mArrowPath.lineTo(s * 1, s * 48);
mArrowPath.lineTo(s * 1, s * 40);
}
}
mArrowPath.close();
mArrowPath.computeBounds(bounds, false);
}
private Rect getCalculateBounds(Rect bounds) {
if (MATERIAL_SDK_INT) {
int baseSize = Math.min(bounds.width(), bounds.height());
int x= (bounds.width() - baseSize) / 2;
int y= (bounds.height() - baseSize) / 2;
return new Rect(x, y, x + baseSize, y + baseSize);
} else {
int dx = bounds.width() / 4;
int dy = bounds.height() / 3;
int baseSize = Math.min(dx, dy);
int width = baseSize * 4;
int height = baseSize * 3;
int x= (bounds.width() - width) / 2;
int y= (bounds.height() - height) / 2;
return new Rect(x, y, x + width, y + height);
}
}
private RectF getArrowBounds(Rect bounds) {
if (MATERIAL_SDK_INT) {
int dx = bounds.width() / 4;
int dy = bounds.height() / 4;
int baseSize = Math.min(dx, dy);
RectF arrowBounds = new RectF();
arrowBounds.top = bounds.top + baseSize;
arrowBounds.left = bounds.left + baseSize;
arrowBounds.bottom = bounds.left + (baseSize * 3);
arrowBounds.right = bounds.left + (baseSize * 3);
return arrowBounds;
} else {
int dx = bounds.width() / 4;
int dy = bounds.height() / 3;
int baseSize = Math.min(dx, dy);
RectF arrowBounds = new RectF();
arrowBounds.top = bounds.top + baseSize;
arrowBounds.bottom = bounds.top + (baseSize * 2);
arrowBounds.left = bounds.left;
arrowBounds.right = bounds.left + baseSize;
return arrowBounds;
}
}
private Rect getIconBounds(Rect bounds) {
int dx = bounds.width() / 4;
int dy = bounds.height() / 3;
int baseSize = Math.min(dx, dy);
Rect iconBounds = new Rect(bounds);
iconBounds.left += baseSize;
return iconBounds;
}
private void updateBound() {
Rect bounds = getBounds();
createArrowPath(getArrowBounds(bounds));
}
@Override
public void draw(Canvas canvas) {
Rect bounds = getBounds();
Rect calcBounds = getCalculateBounds(bounds);
if (mMode == MODE_HOME_UP || mMode == MODE_DRAWER) {
int saveCount = canvas.save();
RectF arrowBounds = getArrowBounds(calcBounds);
if (MATERIAL_SDK_INT) {
canvas.rotate(180.0F, arrowBounds.centerX(), arrowBounds.centerY());
canvas.rotate(mRotate, arrowBounds.centerX(), arrowBounds.centerY());
canvas.translate(arrowBounds.centerX(), arrowBounds.centerY());
canvas.drawPath(mArrowPath, mPaint);
} else {
canvas.translate(arrowBounds.left, arrowBounds.top);
canvas.drawPath(mArrowPath, mArrowPaint);
}
canvas.restoreToCount(saveCount);
}
if (!MATERIAL_SDK_INT && mIcon != null) {
Rect iconRect = getIconBounds(calcBounds);
mIcon.setBounds(iconRect);
mIcon.draw(canvas);
}
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
createArrowPath(getArrowBounds(bounds));
}
@Override
public void setAlpha(int alpha) {
mArrowPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
mArrowPaint.setColorFilter(cf);
}
@Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
@Override
public int getIntrinsicWidth() {
return mIntrinsicWidth;
}
@Override
public int getIntrinsicHeight() {
return mIntrinsicHeight;
}
public void setProgress(float paramFloat) {
mProgress = paramFloat;
updateBound();
invalidateSelf();
}
public void setColor(int resourceId) {
mArrowPaint.setColor(mContext.getResources().getColor(resourceId));
}
@Override
public void start() {
if (isRunning()) return;
scheduleSelf(mUpdater, SystemClock.uptimeMillis() + FRAME_DURATION);
invalidateSelf();
}
@Override
public void stop() {
if (!isRunning()) return;
mRunning = false;
unscheduleSelf(mUpdater);
}
@Override
public void scheduleSelf(Runnable what, long when) {
mRunning = true;
super.scheduleSelf(what, when);
}
@Override
public boolean isRunning() {
return mRunning;
}
}