如何在CustomView上制作尾随动作效果

时间:2017-12-19 15:22:57

标签: android android-custom-view ontouchlistener

我创建了一个CustomView来绘画。当我们触摸视图时,我会将该点标记为位图并在下一个周期中绘制它。

这对我来说非常有用。我想在移动指针时产生拖尾效果。这意味着当我们移动指针视图时应标记该点并最终淡出(在2秒内)。

我的问题是我无法改变每一点上的油漆。

我非常喜欢CustomView。

提前致谢

修改

package com.godwin.handdrawview;

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.Xfermode;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.graphics.ColorUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.LinearInterpolator;

import com.godwin.handdrawview.view.ViewCompat;
import com.godwin.handdrawview.view.ViewTreeObserverCompat;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Created by Godwin on 13-11-2017 15:21 for DrawView.
 *
 * @author : Godwin Joseph Kurinjikattu
 */
public class HandDrawView extends View {

    private static final String TAG = HandDrawView.class.getSimpleName();

    private static final int DEFAULT_STROKE_WIDTH = 10;
    private static final int DEFAULT_STROKE_WIDTH_FOR_ERASER = 50;
    private static final int DEFAULT_COLOR = Color.BLACK;

    private float mLastTouchX;
    private float mLastTouchY;

    private TimePoint mPoint;

    private static final int DOUBLE_CLICK_DELAY_MS = 200;
    private boolean mClearOnDoubleClick;
    private long mFirstClick;
    private int mCountClick;

    private Paint mPaint;
    private Paint mDummyPaint;

    private RectF mDirtyRect;

    private int mStrokeWidth = DEFAULT_STROKE_WIDTH;
    private int mStrokeWidthForEraser = DEFAULT_STROKE_WIDTH_FOR_ERASER;

    private int mStrokeColor = DEFAULT_COLOR;

    @DrawableRes
    private int mPointerRes = 0;

    private Bitmap mPointerBitmap = null;
    private Bitmap mOverlayBitmap = null;
    private Bitmap mDrawingBitmap = null;
    private Canvas mDrawingBitmapCanvas = null;

    private List<TimePoint> mPoints = new ArrayList<>();
    private List<TimePoint> mPointsCache = new ArrayList<>();
    private List<TimePoint> mExtractedPoints = new ArrayList<>();

    private List<TimePoint> mFadeTimePoints = new ArrayList<>();

    private ControlTimedPoints mControlPointCached = null;
    private Bezier mBezierCached = new Bezier();

    private OnDrawListener mOnDrawListener;

    private boolean isCapturing;
    private boolean isFadeEffect;

    private PathMeasure mPathMeasure;
    private Path mPath;

    private Mode mMode = Mode.MARKER;

    /**
     * Simple constructor to use when creating a view from code.
     *
     * @param context The Context the view is running in, through which it can                access the current theme, resources, etc.
     */
    public HandDrawView(Context context) {
        super(context);
        init(null, 0, 0);
    }

    /**
     * Instantiates a new Hand draw view.
     *
     * @param context the context
     * @param attrs   the attrs
     */
    public HandDrawView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0, 0);
    }

    /**
     * Instantiates a new Hand draw view.
     *
     * @param context      the context
     * @param attrs        the attrs
     * @param defStyleAttr the def style attr
     */
    public HandDrawView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs, defStyleAttr, 0);
    }

    /**
     * Instantiates a new Hand draw view.
     *
     * @param context      the context
     * @param attrs        the attrs
     * @param defStyleAttr the def style attr
     * @param defStyleRes  the def style res
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public HandDrawView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();

        Bundle state = new Bundle();
        state.putParcelable("PARENT", superState);
        state.putInt("mStrokeWidth", mStrokeWidth);
        state.putInt("mStrokeColor", mStrokeColor);
        state.putInt("mPointerRes", mPointerRes);

        return state;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        Bundle savedState = (Bundle) state;

        Parcelable superState = savedState.getParcelable("PARENT");
        super.onRestoreInstanceState(superState);

        mStrokeColor = savedState.getInt("mStrokeColor");
        mStrokeWidth = savedState.getInt("mStrokeWidth");
        mPointerRes = savedState.getInt("mPointerRes");

        initPaint();
    }

    private void init(AttributeSet attrs, int defStyle, int defStyleRes) {
        final TypedArray attrArray = getContext().obtainStyledAttributes(attrs, R.styleable.HandDrawView, defStyle, defStyleRes);
        initAttributes(attrArray);

        mDirtyRect = new RectF();
        initPaint();
        initPath();
//        setLayerType(LAYER_TYPE_HARDWARE, null);
    }

    private void initPaint() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setColor(mStrokeColor);

        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeJoin(Paint.Join.ROUND);

        mDummyPaint = new Paint();
    }

    private void initPath() {
        mPathMeasure = new PathMeasure();
        mPath = new Path();
    }

    private void initAttributes(TypedArray attrArray) {
        this.mStrokeColor = attrArray.getInt(R.styleable.HandDrawView_strokeColor, mStrokeColor);
        this.mStrokeWidth = attrArray.getInt(R.styleable.HandDrawView_strokeWidth, mStrokeWidth);
        this.mPointerRes = attrArray.getInt(R.styleable.HandDrawView_pointerDrawable, mPointerRes);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mDrawingBitmap != null) {
            canvas.drawBitmap(mDrawingBitmap, 0, 0, getPaint());
        }
        if (mPointerBitmap != null && mPoint != null) {
            drawPointerBitmap(canvas, mPoint);
        } else if (mPointerBitmap == null && mPoint != null) {
            drawCustomPointer(canvas, mPoint);
        }
        if (mOverlayBitmap != null) {
            drawOverlayBitmap(canvas);
        }
    }

    private void drawCustomPointer(Canvas canvas, TimePoint mPoint) {
        float x = mPoint.getX(), y = mPoint.getY();
        Path path = new Path();
        path.moveTo(x, y);
        path.lineTo(x + 10, y);
        path.lineTo(x + 100, y - 100);
        path.lineTo(x + 120, y - 110);
        path.lineTo(x + 90, y - 130);
        path.lineTo(x + 70, y - 90);
        path.lineTo(x, y - 10);
        path.lineTo(x, y);

        canvas.drawPath(path, mPaint);

    }

    private void drawPointerBitmap(Canvas canvas, TimePoint point) {
        if (mPointerBitmap != null) {
            canvas.drawBitmap(mPointerBitmap,
                    point.getX(),
                    point.getY() - mPointerBitmap.getHeight(),
                    mDummyPaint);
        }
    }

    private void drawOverlayBitmap(Canvas canvas) {
        canvas.drawBitmap(mOverlayBitmap,
                getWidth() / 2 - mOverlayBitmap.getWidth() / 2,
                getHeight() / 2 - mOverlayBitmap.getHeight() / 2,
                mDummyPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!isEnabled())
            return false;
        TimePoint p = TimePoint.from(event.getX(), event.getY());
        mPoint = p.clone();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                mPoints.clear();
                mFadeTimePoints.clear();

                if (isDoubleClick()) break;
                this.mLastTouchX = event.getX();
                this.mLastTouchY = event.getY();
                moveTo();
                addPoint(getNewPoint(p));
                if (null != mOnDrawListener) {
                    mOnDrawListener.onStartDraw(this);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                resetDirtyRect(p);
                lineTo();
                addPoint(getNewPoint(p));
                if (null != mOnDrawListener) {
                    mOnDrawListener.onDrawing(this, getBitmap());
                }
                break;
            case MotionEvent.ACTION_UP:
                resetDirtyRect(p);
                addPoint(getNewPoint(p));
                getParent().requestDisallowInterceptTouchEvent(true);
                if (null != mOnDrawListener) {
                    mOnDrawListener.onStopDrawing(this, getBitmap());
                }
                break;
            default:
                return false;
        }
        //invalidate();
        invalidate(
                (int) (mDirtyRect.left),
                (int) (mDirtyRect.top),
                (int) (mDirtyRect.right),
                (int) (mDirtyRect.bottom));
        return true;
    }

    private void moveTo() {
        mPath.moveTo(mPoint.x, mPoint.y);
    }

    private void lineTo() {
        mPath.lineTo(mPoint.x, mPoint.y);
    }

    private void resetDirtyRect(TimePoint point) {
// The mLastTouchX and mLastTouchY were set when the ACTION_DOWN motion event occurred.
        mDirtyRect.left = Math.min(mLastTouchX, point.x);
        mDirtyRect.right = Math.max(mLastTouchX, point.x);
        mDirtyRect.top = Math.min(mLastTouchY, point.y);
        mDirtyRect.bottom = Math.max(mLastTouchY, point.y);
    }

    private void addPoint(TimePoint timePoint) {
        capture(timePoint);
        mPoints.add(timePoint);

        int pointsCount = mPoints.size();
        if (pointsCount > 3) {

            ControlTimedPoints tmp = calculateCurveControlPoints(mPoints.get(0), mPoints.get(1), mPoints.get(2));
            TimePoint t2 = tmp.t2;
            recyclePoint(tmp.t1);

            tmp = calculateCurveControlPoints(mPoints.get(1), mPoints.get(2), mPoints.get(3));
            TimePoint t3 = tmp.t1;
            recyclePoint(tmp.t2);

            Bezier curve = mBezierCached.set(mPoints.get(1), t2, t3, mPoints.get(2));
            addBezier(curve);

            // Remove the first element from the list,
            // so that we always have no more than 4 mPoints in mPoints array.
            recyclePoint(mPoints.remove(0));

            recyclePoint(t2);
            recyclePoint(t3);
        } else if (pointsCount == 1) {
            // To reduce the initial lag make it work with 3 mPoints
            // by duplicating the first point
            mPoints.add(getNewPoint(mPoints.get(0)));
        }
    }

    private void capture(TimePoint timePoint) {
        if (isCapturing)
            mExtractedPoints.add(timePoint.clone());
    }

    //region Setter and getter

    /**
     * Start capturing.
     */
    public void startCapturing() {
        this.isCapturing = true;
        mExtractedPoints.clear();
    }

    /**
     * Stop capaturing.
     */
    public void stopCapturing() {
        this.isCapturing = false;
    }

    /**
     * Is capturing boolean.
     *
     * @return the boolean
     */
    public boolean isCapturing() {
        return isCapturing;
    }

    public boolean isFadeEffect() {
        return isFadeEffect;
    }

    public void setFadeEffect(boolean fadeEffect) {
        isFadeEffect = fadeEffect;
    }

    /**
     * Gets stroke width.
     *
     * @return the stroke width
     */
    public int getStrokeWidth() {
        return mStrokeWidth;
    }

    /**
     * Sets stroke width.
     *
     * @param mStrokeWidth the m stroke width
     */
    public void setStrokeWidth(int mStrokeWidth) {
        this.mStrokeWidth = mStrokeWidth;
        ensureDrawingBitmap();
    }

    /**
     * Gets stroke width for eraser.
     *
     * @return the stroke width for eraser
     */
    public int getStrokeWidthForEraser() {
        return mStrokeWidthForEraser;
    }

    /**
     * Sets stroke width for eraser.
     *
     * @param mStrokeWidthForEraser the m stroke width for eraser
     */
    public void setStrokeWidthForEraser(int mStrokeWidthForEraser) {
        this.mStrokeWidthForEraser = mStrokeWidthForEraser;
    }

    /**
     * Gets stroke color.
     *
     * @return the stroke color
     */
    public int getStrokeColor() {
        return mStrokeColor;
    }

    /**
     * Sets stroke color.
     *
     * @param mStrokeColor the m stroke color
     */
    public void setStrokeColor(int mStrokeColor) {
        this.mStrokeColor = mStrokeColor;
        if (null == mPaint) {
            initPaint();
        } else {
            mPaint.setColor(mStrokeColor);
        }
    }

    /**
     * Sets clear on double click.
     *
     * @param clearOnDoubleClick the clear on double click
     */
    public void setClearOnDoubleClick(boolean clearOnDoubleClick) {
        mClearOnDoubleClick = clearOnDoubleClick;
    }

    /**
     * Gets pointer res.
     *
     * @return the pointer res
     */
    public int getPointerRes() {
        return mPointerRes;
    }

    /**
     * Sets pointer res.
     *
     * @param mPointerRes the m pointer res
     */
    public void setPointerRes(int mPointerRes) {
        this.mPointerRes = mPointerRes;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inMutable = true;
        this.mPointerBitmap = BitmapFactory.decodeResource(getResources(), mPointerRes, options);
        invalidate();
    }

    /**
     * Gets pointer bitmap.
     *
     * @return the pointer bitmap
     */
    public Bitmap getPointerBitmap() {
        return mPointerBitmap;
    }

    /**
     * Sets pointer bitmap.
     *
     * @param mPointerBitmap the m pointer bitmap
     */
    public void setPointerBitmap(Bitmap mPointerBitmap) {
        this.mPointerBitmap = mPointerBitmap;
        invalidate();
    }

    /**
     * Gets on draw listener.
     *
     * @return the on draw listener
     */
    public OnDrawListener getOnDrawListener() {
        return mOnDrawListener;
    }

    /**
     * Sets on draw listener.
     *
     * @param mOnDrawListener the m on draw listener
     */
    public void setOnDrawListener(OnDrawListener mOnDrawListener) {
        this.mOnDrawListener = mOnDrawListener;
    }

    /**
     * Gets overlay bitmap.
     *
     * @return the overlay bitmap
     */
    public Bitmap getOverlayBitmap() {
        return mOverlayBitmap;
    }

    /**
     * Sets overlay bitmap.
     *
     * @param mOverlayBitmap the m overlay bitmap
     */
    public void setOverlayBitmap(Bitmap mOverlayBitmap) {
        this.mOverlayBitmap = mOverlayBitmap;
        invalidate();
    }
    //endregion

    /**
     * Start animation.
     */
    public void startAnimation() {
        animation(mExtractedPoints);
    }

    Random random = new Random();

    private void fade() {
        final short steps = 150;
        final byte stepDistance = 5;
        final byte maxTrailRadius = 15;
        mPathMeasure.setPath(mPath, false);
        final float pathLength = mPathMeasure.getLength();
        for (short i = 1; i <= steps; i++) {
            final float distance = pathLength - i * stepDistance;
            if (distance >= 0) {
                final float trailRadius = maxTrailRadius * (1 - (float) i / steps);
                mPathMeasure.getPosTan(distance, mPoint.getPoints(), null);
                final float x = mPoint.getPoints()[0] + random.nextFloat( ) - trailRadius;
                final float y = mPoint.getPoints()[1] +  random.nextFloat( ) - trailRadius;
                mPaint.setShader(new RadialGradient(
                        x,
                        y,
                        trailRadius > 0 ? trailRadius : Float.MIN_VALUE,
                        ColorUtils.setAlphaComponent(mPaint.getColor(), random.nextInt(0xff)),
                        Color.TRANSPARENT,
                        Shader.TileMode.CLAMP
                ));
                mDrawingBitmapCanvas.drawCircle(x, y, trailRadius, mPaint);
            }
        }
    }

    private Paint getPaint() {
        mPaint.setShader(null);
        if (mMode == Mode.MARKER) {
            mPaint.setXfermode(null);
            mPaint.setStrokeWidth(mStrokeWidth);
        } else {
            mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
            mPaint.setStrokeWidth(mStrokeWidthForEraser);
        }
        return mPaint;
    }

    /**
     * Clear the canvas.
     */
    public void clear() {
        if (mPoints != null) {
            mPoints.clear();
        }
        if (mExtractedPoints != null)
            mExtractedPoints.clear();

        if (mDrawingBitmap != null) {
            mDrawingBitmap = null;
            ensureDrawingBitmap();
        }
        if (null != mOnDrawListener) {
            mOnDrawListener.onClearCanvas(this);
        }
        invalidate();
    }

    /**
     * Gets mode.
     *
     * @return the mode
     */
    public Mode getMode() {
        return mMode;
    }

    /**
     * Sets mode.
     *
     * @param mMode the m mode
     */
    public void setMode(Mode mMode) {
        this.mMode = mMode;
    }

    private TimePoint getNewPoint(TimePoint timePoint) {
        int mCacheSize = mPointsCache.size();
        TimePoint temp;
        if (mCacheSize == 0) {
            // Cache is empty, create a new point
            temp = new TimePoint();
        } else {
            // Get point from cache
            temp = mPointsCache.remove(mCacheSize - 1);
        }

        return temp.set(timePoint);
    }


    /**
     * Called when replaying history to ensure the dirty region includes all
     * mPoints.
     *
     * @param historicalX the previous x coordinate.
     * @param historicalY the previous y coordinate.
     */
    private void expandDirtyRect(float historicalX, float historicalY) {
        if (historicalX < mDirtyRect.left) {
            mDirtyRect.left = historicalX;
        } else if (historicalX > mDirtyRect.right) {
            mDirtyRect.right = historicalX;
        }
        if (historicalY < mDirtyRect.top) {
            mDirtyRect.top = historicalY;
        } else if (historicalY > mDirtyRect.bottom) {
            mDirtyRect.bottom = historicalY;
        }
    }

    private void recyclePoint(TimePoint point) {
        mPointsCache.add(point);
    }
}

1 个答案:

答案 0 :(得分:0)

这是另一个StackOverflow答案的link,它几​​乎可以为您提供自己重现效果所需的所有信息。