package me.jfenn.slideactionview; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import java.util.HashMap; import java.util.Map; import androidx.annotation.ColorInt; import androidx.annotation.Nullable; import me.jfenn.androidutils.DimenUtils; import me.jfenn.androidutils.ImageUtils; import me.jfenn.androidutils.anim.AnimatedFloat; public class SlideActionView extends View implements View.OnTouchListener { private float x = -1; private AnimatedFloat selected; private Map<Float, AnimatedFloat> ripples; private int handleRadius; private int expandedHandleRadius; private int selectionRadius; private int rippleRadius; private Paint normalPaint; private Paint outlinePaint; private Paint bitmapPaint; private Bitmap leftImage, rightImage; private SlideActionListener listener; public SlideActionView(Context context) { super(context); init(); } public SlideActionView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } public SlideActionView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public SlideActionView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(); } private void init() { handleRadius = DimenUtils.dpToPx(12); expandedHandleRadius = DimenUtils.dpToPx(32); selectionRadius = DimenUtils.dpToPx(42); rippleRadius = DimenUtils.dpToPx(140); selected = new AnimatedFloat(0); ripples = new HashMap<>(); normalPaint = new Paint(); normalPaint.setStyle(Paint.Style.FILL); normalPaint.setColor(Color.GRAY); normalPaint.setAntiAlias(true); normalPaint.setDither(true); outlinePaint = new Paint(); outlinePaint.setStyle(Paint.Style.STROKE); outlinePaint.setColor(Color.GRAY); outlinePaint.setAntiAlias(true); outlinePaint.setDither(true); bitmapPaint = new Paint(); bitmapPaint.setStyle(Paint.Style.FILL); bitmapPaint.setColor(Color.GRAY); bitmapPaint.setAntiAlias(true); bitmapPaint.setDither(true); bitmapPaint.setFilterBitmap(true); setOnTouchListener(this); setFocusable(true); setClickable(true); } /** * Specify an interface to pass events to when an action * is selected. * * @param listener An interface to pass events to. */ public void setListener(SlideActionListener listener) { this.listener = listener; } /** * Specifies the icon to display on the left side of the view, * as a Drawable. If it is just as easier to pass a Bitmap, you * should avoid using this method; all it does is convert the * drawable to a bitmap, then call the same method again. * * @param drawable The Drawable to use as an icon. */ public void setLeftIcon(Drawable drawable) { setLeftIcon(ImageUtils.drawableToBitmap(drawable)); } /** * Specifies the icon to display on the left side of the view. * * @param bitmap The Bitmap to use as an icon. */ public void setLeftIcon(Bitmap bitmap) { leftImage = bitmap; postInvalidate(); } /** * Specifies the icon to display on the right side of the view, * as a Drawable. If it is just as easier to pass a Bitmap, you * should avoid using this method; all it does is convert the * drawable to a bitmap, then call the same method again. * * @param drawable The Drawable to use as an icon. */ public void setRightIcon(Drawable drawable) { setRightIcon(ImageUtils.drawableToBitmap(drawable)); } /** * Specifies the icon to display on the right side of the view. * * @param bitmap The Bitmap to use as an icon. */ public void setRightIcon(Bitmap bitmap) { rightImage = bitmap; postInvalidate(); } /** * Specify the color of the touch handle in the center of * the view. The alpha of this color is modified to be somewhere * between 0 and 150. * * @param handleColor The color of the touch handle. */ public void setTouchHandleColor(@ColorInt int handleColor) { normalPaint.setColor(handleColor); } /** * @return The color of the touch handle in the center of the view. */ @ColorInt public int getTouchHandleColor() { return normalPaint.getColor(); } /** * Specify the color of the random outlines drawn all over the place. * * @param outlineColor The color of the random outlines. */ public void setOutlineColor(@ColorInt int outlineColor) { outlinePaint.setColor(outlineColor); } /** * @return The color of the random outlines drawn all over the place. */ @ColorInt public int getOutlineColor() { return outlinePaint.getColor(); } /** * Specify the color applied to the left/right icons as a filter. * * @param iconColor The color that the left/right icons are filtered by. */ public void setIconColor(@ColorInt int iconColor) { bitmapPaint.setColor(iconColor); bitmapPaint.setColorFilter(new PorterDuffColorFilter(iconColor, PorterDuff.Mode.SRC_IN)); } /** * @return The color applied to the left/right icons as a filter. */ @ColorInt public int getIconColor() { return bitmapPaint.getColor(); } @Override public void draw(Canvas canvas) { super.draw(canvas); selected.next(true); if (x < 0) x = (float) getWidth() / 2; normalPaint.setAlpha(150 - (int) (selected.val() * 100)); int radius = (int) ((handleRadius * (1 - selected.val())) + (expandedHandleRadius * selected.val())); float drawnX = (x * selected.val()) + (((float) getWidth() / 2) * (1 - selected.val())); canvas.drawCircle(drawnX, (float) getHeight() / 2, radius, normalPaint); if (leftImage != null && rightImage != null) { bitmapPaint.setAlpha((int) (255 * Math.min(1f, Math.max(0f, (getWidth() - drawnX - selectionRadius) / getWidth())))); canvas.drawBitmap(leftImage, selectionRadius - (leftImage.getWidth() / 2), (getHeight() - leftImage.getHeight()) / 2, bitmapPaint); bitmapPaint.setAlpha((int) (255 * Math.min(1f, Math.max(0f, (drawnX - selectionRadius) / getWidth())))); canvas.drawBitmap(rightImage, getWidth() - selectionRadius - (leftImage.getWidth() / 2), (getHeight() - leftImage.getHeight()) / 2, bitmapPaint); } if (Math.abs((getWidth() / 2) - drawnX) > selectionRadius / 2) { if (drawnX * 2 < getWidth()) { float progress = Math.min(1f, Math.max(0f, ((getWidth() - ((drawnX + selectionRadius) * 2)) / getWidth()))); progress = (float) Math.pow(progress, 0.2f); outlinePaint.setAlpha((int) (255 * progress)); canvas.drawCircle(selectionRadius, getHeight() / 2, (selectionRadius / 2) + (rippleRadius * (1 - progress)), outlinePaint); } else { float progress = Math.min(1f, Math.max(0f, (((drawnX - selectionRadius) * 2) - getWidth()) / getWidth())); progress = (float) Math.pow(progress, 0.2f); outlinePaint.setAlpha((int) (255 * progress)); canvas.drawCircle(getWidth() - selectionRadius, getHeight() / 2, (selectionRadius / 2) + (rippleRadius * (1 - progress)), outlinePaint); } } for (float x : ripples.keySet()) { AnimatedFloat scale = ripples.get(x); scale.next(true, 1600); normalPaint.setAlpha((int) (150 * (scale.getTarget() - scale.val()) / scale.getTarget())); canvas.drawCircle(x, getHeight() / 2, scale.val(), normalPaint); if (scale.isTarget()) ripples.remove(x); } if (!selected.isTarget() || ripples.size() > 0) postInvalidate(); } @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN && Math.abs(event.getX() - (getWidth() / 2)) < selectionRadius) selected.to(1f); else if (event.getAction() == MotionEvent.ACTION_UP && selected.getTarget() > 0) { selected.to(0f); if (event.getX() > getWidth() - (selectionRadius * 2)) { AnimatedFloat ripple = new AnimatedFloat(selectionRadius); ripple.to((float) rippleRadius); ripples.put((float) getWidth() - selectionRadius, ripple); if (listener != null) listener.onSlideRight(); postInvalidate(); } else if (event.getX() < selectionRadius * 2) { AnimatedFloat ripple = new AnimatedFloat(selectionRadius); ripple.to((float) rippleRadius); ripples.put((float) selectionRadius, ripple); if (listener != null) listener.onSlideLeft(); postInvalidate(); } return true; } if (selected.getTarget() > 0) { x = event.getX(); postInvalidate(); } return false; } }