package com.example.administrator.cookman.ui.component; import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Region; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.FloatRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.AppCompatImageView; import android.util.AttributeSet; import android.view.animation.DecelerateInterpolator; import com.example.administrator.cookman.R; /** * Created by Administrator on 2017/2/25. */ public class SwitchIconView extends AppCompatImageView { private static final int DEFAULT_ANIMATION_DURATION = 300; private static final float DASH_THICKNESS_PART = 1f / 12f; private static final float DEFAULT_DISABLED_ALPHA = .5f; private static final float SIN_45 = (float) Math.sin(Math.toRadians(45)); private final long animationDuration; @FloatRange(from = 0f, to = 1f) private final float disabledStateAlpha; private final int dashXStart; private final int dashYStart; private final Path clipPath; private final int iconTintColor; private final int disabledStateColor; private final boolean noDash; private int dashThickness; private int dashLengthXProjection; private int dashLengthYProjection; private PorterDuffColorFilter colorFilter; private final ArgbEvaluator colorEvaluator = new ArgbEvaluator(); @FloatRange(from = 0f, to = 1f) private float fraction = 0f; private boolean enabled; @NonNull private final Paint dashPaint; @NonNull private final Point dashStart = new Point(); @NonNull private final Point dashEnd = new Point(); public SwitchIconView(@NonNull Context context) { this(context, null); } public SwitchIconView(@NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public SwitchIconView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray array = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.SwitchIconView, 0, 0); try { iconTintColor = array.getColor(R.styleable.SwitchIconView_si_tint_color, Color.BLACK); animationDuration = array.getInteger(R.styleable.SwitchIconView_si_animation_duration, DEFAULT_ANIMATION_DURATION); disabledStateAlpha = array.getFloat(R.styleable.SwitchIconView_si_disabled_alpha, DEFAULT_DISABLED_ALPHA); disabledStateColor = array.getColor(R.styleable.SwitchIconView_si_disabled_color, iconTintColor); enabled = array.getBoolean(R.styleable.SwitchIconView_si_disabled_alpha, true); noDash = array.getBoolean(R.styleable.SwitchIconView_si_no_dash, false); } finally { array.recycle(); } if (disabledStateAlpha < 0f || disabledStateAlpha > 1f) { throw new IllegalArgumentException("Wrong value for si_disabled_alpha [" + disabledStateAlpha + "]. " + "Must be value from range [0, 1]"); } colorFilter = new PorterDuffColorFilter(iconTintColor, PorterDuff.Mode.SRC_IN); setColorFilter(colorFilter); dashXStart = getPaddingLeft(); dashYStart = getPaddingTop(); dashPaint = new Paint(Paint.ANTI_ALIAS_FLAG); dashPaint.setStyle(Paint.Style.STROKE); dashPaint.setColor(iconTintColor); clipPath = new Path(); initDashCoordinates(); setFraction(enabled ? 0f : 1f); } /** * Changes state with animation * * @param enabled If TRUE - icon is enabled */ public void setIconEnabled(boolean enabled) { setIconEnabled(enabled, true); } /** * Changes state * * @param enabled If TRUE - icon is enabled */ public void setIconEnabled(boolean enabled, boolean animate) { if (this.enabled == enabled) return; switchState(animate); } /** * Check state * * @return TRUE if icon is enabled, otherwise FALSE */ public boolean isIconEnabled() { return enabled; } /** * Switches icon state with animation */ public void switchState() { switchState(true); } /** * Switches icon state * * @param animate Indicates that state will be changed with or without animation */ public void switchState(boolean animate) { float newFraction; if (enabled) { newFraction = 1f; } else { newFraction = 0f; } enabled = !enabled; if (animate) { animateToFraction(newFraction); } else { setFraction(newFraction); invalidate(); } } @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SwitchIconSavedState savedState = new SwitchIconSavedState(superState); savedState.iconEnabled = enabled; return savedState; } @Override public void onRestoreInstanceState(Parcelable state) { if (!(state instanceof SwitchIconSavedState)) { super.onRestoreInstanceState(state); return; } SwitchIconSavedState savedState = (SwitchIconSavedState) state; super.onRestoreInstanceState(savedState.getSuperState()); enabled = savedState.iconEnabled; setFraction(enabled ? 0f : 1f); } @Override protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { super.onSizeChanged(width, height, oldWidth, oldHeight); dashLengthXProjection = width - getPaddingLeft() - getPaddingRight(); dashLengthYProjection = height - getPaddingTop() - getPaddingBottom(); dashThickness = (int) (DASH_THICKNESS_PART * (dashLengthXProjection + dashLengthYProjection) / 2f); dashPaint.setStrokeWidth(dashThickness); initDashCoordinates(); updateClipPath(); } @Override protected void onDraw(Canvas canvas) { if (!noDash) { drawDash(canvas); canvas.clipPath(clipPath, Region.Op.XOR); } super.onDraw(canvas); } private void animateToFraction(float toFraction) { ValueAnimator animator = ValueAnimator.ofFloat(fraction, toFraction); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { setFraction((float) animation.getAnimatedValue()); } }); animator.setInterpolator(new DecelerateInterpolator()); animator.setDuration(animationDuration); animator.start(); } private void setFraction(float fraction) { this.fraction = fraction; updateColor(fraction); updateAlpha(fraction); updateClipPath(); postInvalidateOnAnimationCompat(); } private void initDashCoordinates() { float delta1 = 1.5f * SIN_45 * dashThickness; float delta2 = 0.5f * SIN_45 * dashThickness; dashStart.x = (int) (dashXStart + delta2); dashStart.y = dashYStart + (int) (delta1); dashEnd.x = (int) (dashXStart + dashLengthXProjection - delta1); dashEnd.y = (int) (dashYStart + dashLengthYProjection - delta2); } private void updateClipPath() { float delta = dashThickness / SIN_45; clipPath.reset(); clipPath.moveTo(dashXStart, dashYStart + delta); clipPath.lineTo(dashXStart + delta, dashYStart); clipPath.lineTo(dashXStart + dashLengthXProjection * fraction, dashYStart + dashLengthYProjection * fraction - delta); clipPath.lineTo(dashXStart + dashLengthXProjection * fraction - delta, dashYStart + dashLengthYProjection * fraction); } private void drawDash(Canvas canvas) { float x = fraction * (dashEnd.x - dashStart.x) + dashStart.x; float y = fraction * (dashEnd.y - dashStart.y) + dashStart.y; canvas.drawLine(dashStart.x, dashStart.y, x, y, dashPaint); } private void updateColor(float fraction) { if (iconTintColor != disabledStateColor) { final int color = (int) colorEvaluator.evaluate(fraction, iconTintColor, disabledStateColor); updateImageColor(color); dashPaint.setColor(color); } } private void updateAlpha(float fraction) { int alpha = (int) ((disabledStateAlpha + (1f - fraction) * (1f - disabledStateAlpha)) * 255); updateImageAlpha(alpha); dashPaint.setAlpha(alpha); } private void updateImageColor(int color) { colorFilter = new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN); setColorFilter(colorFilter); } @SuppressWarnings("deprecation") private void updateImageAlpha(int alpha) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { setImageAlpha(alpha); } else { setAlpha(alpha); } } private void postInvalidateOnAnimationCompat() { final long fakeFrameTime = 10; if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { postInvalidateOnAnimation(); } else { postInvalidateDelayed(fakeFrameTime); } } static class SwitchIconSavedState extends BaseSavedState { boolean iconEnabled; SwitchIconSavedState(Parcelable superState) { super(superState); } private SwitchIconSavedState(Parcel in) { super(in); final int enabled = in.readInt(); iconEnabled = enabled == 1; } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(iconEnabled ? 1 : 0); } public static final Parcelable.Creator<SwitchIconSavedState> CREATOR = new Parcelable.Creator<SwitchIconSavedState>() { public SwitchIconSavedState createFromParcel(Parcel in) { return new SwitchIconSavedState(in); } public SwitchIconSavedState[] newArray(int size) { return new SwitchIconSavedState[size]; } }; } }