2017-12-19 34 views
0

페인트 할 CustomView를 만들었습니다. 뷰를 터치하면 점을 비트 맵으로 표시하고 다음 주기로 그 점을 그립니다.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); 
    } 
} 

답변

0

에서

덕분에 꽤 많이 당신이 효과를 직접 재현하는 데 필요한 정보의 당신에게 모든 것을 제공해야 다른 StackOverflow의 대답에 link입니다.

+0

작동하지 않습니다. 그 코드를 이해하기 위해 열심히 노력했습니다. – Godwin

+0

볼 코드가 없기 때문에 당신을 도울 수 없습니다. 또한 "작동하지 않는다"는 것은 무엇을 의미합니까? 그것은 무엇인가하고 있지만 당신이 원했던 것이거나 아무것도하지 않거나 충돌하는 것입니까? –

+0

코드가 불완전하고 그 아이디어에 대해 알지 못하기 때문에 쓰레기를 처리하고 나에게 도움이되지 않습니다. – Godwin