Programing

Android 손가락을 따라 부드러운 선을 그리는 방법

lottogame 2020. 11. 10. 07:43
반응형

Android 손가락을 따라 부드러운 선을 그리는 방법


http://marakana.com/tutorials/android/2d-graphics-example.html

이 예제를 아래에서 사용하고 있습니다. 그러나 화면에서 손가락을 너무 빨리 움직이면 선이 개별 점으로 바뀝니다.

드로잉 속도를 높일 수 있을지 모르겠습니다. 또는 마지막 두 점을 직선으로 연결해야합니다. 이 두 가지 솔루션 중 두 번째 방법은 손가락을 매우 빠르게 움직일 때 직선의 긴 부분과 날카로운 곡선이 있다는 점을 제외하면 좋은 옵션처럼 보입니다.

다른 해결책이 있다면 그것을 듣는 것이 좋을 것입니다.

미리 도움을 주셔서 감사합니다.


당신이 언급했듯이 쉬운 해결책은 단순히 점들을 직선으로 연결하는 것입니다. 이를 수행하는 코드는 다음과 같습니다.

public void onDraw(Canvas canvas) {
    Path path = new Path();
    boolean first = true;
    for(Point point : points){
        if(first){
            first = false;
            path.moveTo(point.x, point.y);
        }
        else{
            path.lineTo(point.x, point.y);
        }
    }
    canvas.drawPath(path, paint);
}

채우기에서 획으로 페인트를 변경했는지 확인하십시오.

paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(2);
paint.setColor(Color.WHITE);

또 다른 옵션은 quadTo 메서드를 사용하여 점을 반복으로 연결하는 것입니다.

public void onDraw(Canvas canvas) {
    Path path = new Path();
    boolean first = true;
    for(int i = 0; i < points.size(); i += 2){
        Point point = points.get(i);
        if(first){
            first = false;
            path.moveTo(point.x, point.y);
        }

        else if(i < points.size() - 1){
            Point next = points.get(i + 1);
            path.quadTo(point.x, point.y, next.x, next.y);
        }
        else{
            path.lineTo(point.x, point.y);
        }
    }

    canvas.drawPath(path, paint);
}

이로 인해 여전히 날카로운 모서리가 생깁니다.

정말 야심이 있다면 다음과 같이 큐빅 스플라인 계산을 시작할 수 있습니다.

public void onDraw(Canvas canvas) {
    Path path = new Path();

    if(points.size() > 1){
        for(int i = points.size() - 2; i < points.size(); i++){
            if(i >= 0){
                Point point = points.get(i);

                if(i == 0){
                    Point next = points.get(i + 1);
                    point.dx = ((next.x - point.x) / 3);
                    point.dy = ((next.y - point.y) / 3);
                }
                else if(i == points.size() - 1){
                    Point prev = points.get(i - 1);
                    point.dx = ((point.x - prev.x) / 3);
                    point.dy = ((point.y - prev.y) / 3);
                }
                else{
                    Point next = points.get(i + 1);
                    Point prev = points.get(i - 1);
                    point.dx = ((next.x - prev.x) / 3);
                    point.dy = ((next.y - prev.y) / 3);
                }
            }
        }
    }

    boolean first = true;
    for(int i = 0; i < points.size(); i++){
        Point point = points.get(i);
        if(first){
            first = false;
            path.moveTo(point.x, point.y);
        }
        else{
            Point prev = points.get(i - 1);
            path.cubicTo(prev.x + prev.dx, prev.y + prev.dy, point.x - point.dx, point.y - point.dy, point.x, point.y);
        }
    }
    canvas.drawPath(path, paint);
}

또한 중복 모션 이벤트를 방지하려면 다음을 변경해야합니다.

public boolean onTouch(View view, MotionEvent event) {
    if(event.getAction() != MotionEvent.ACTION_UP){
        Point point = new Point();
        point.x = event.getX();
        point.y = event.getY();
        points.add(point);
        invalidate();
        Log.d(TAG, "point: " + point);
        return true;
    }
    return super.onTouchEvent(event);
}

dx 및 dy 값을 Point 클래스에 추가합니다.

class Point {
    float x, y;
    float dx, dy;

    @Override
    public String toString() {
        return x + ", " + y;
    }
}

이것은 부드러운 선을 생성하지만 때때로 루프를 사용하여 점을 연결해야합니다. 또한 긴 드로잉 세션의 경우 계산에 많은 계산이 필요합니다.

도움이 되었으면 좋겠네요

편집하다

저는 Square의 제안 된 서명 구현을 포함하여 이러한 다양한 기술을 보여주는 빠른 프로젝트를 모았습니다. 즐기십시오 : https://github.com/johncarl81/androiddraw


이것은 더 이상 당신에게 중요하지 않을 수도 있지만, 나는 그것을 해결하기 위해 많은 노력을 기울 였고 공유하고 싶습니다. 다른 사람에게 유용 할 수 있습니다.

@johncarl이 제공하는 솔루션이 포함 된 튜토리얼은 그림 그리기에는 좋지만 내 목적에 제한이 있습니다. 화면에서 손가락을 떼었 다가 다시 넣으면이 솔루션은 마지막 클릭과 새 클릭 사이에 선을 그려 전체 그림이 항상 연결되도록합니다. 그래서 그에 대한 해결책을 찾으려고 노력했고 마침내 그것을 얻었습니다! (명백하게 들리면 죄송합니다, 저는 그래픽 초보자입니다)

public class MainActivity extends Activity {
    DrawView drawView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Set full screen view
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                     WindowManager.LayoutParams.FLAG_FULLSCREEN);
        requestWindowFeature(Window.FEATURE_NO_TITLE);

        drawView = new DrawView(this);
        setContentView(drawView);
        drawView.requestFocus();
    }
}


public class DrawingPanel extends View implements OnTouchListener {
    private static final String TAG = "DrawView";

    private static final float MINP = 0.25f;
    private static final float MAXP = 0.75f;

    private Canvas  mCanvas;
    private Path    mPath;
    private Paint       mPaint;   
    private LinkedList<Path> paths = new LinkedList<Path>();

    public DrawingPanel(Context context) {
        super(context);
        setFocusable(true);
        setFocusableInTouchMode(true);

        this.setOnTouchListener(this);

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setColor(Color.BLACK);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(6);
        mCanvas = new Canvas();
        mPath = new Path();
        paths.add(mPath);
    }               

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
    }

    @Override
    protected void onDraw(Canvas canvas) {            
        for (Path p : paths){
            canvas.drawPath(p, mPaint);
        }
    }

    private float mX, mY;
    private static final float TOUCH_TOLERANCE = 4;

    private void touch_start(float x, float y) {
        mPath.reset();
        mPath.moveTo(x, y);
        mX = x;
        mY = y;
    }

    private void touch_move(float x, float y) {
        float dx = Math.abs(x - mX);
        float dy = Math.abs(y - mY);
        if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
            mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
            mX = x;
            mY = y;
        }
    }

    private void touch_up() {
        mPath.lineTo(mX, mY);
        // commit the path to our offscreen
        mCanvas.drawPath(mPath, mPaint);
        // kill this so we don't double draw            
        mPath = new Path();
        paths.add(mPath);
    }

    @Override
    public boolean onTouch(View arg0, MotionEvent event) {
        float x = event.getX();
        float y = event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touch_start(x, y);
                invalidate();
                break;
            case MotionEvent.ACTION_MOVE:
                touch_move(x, y);
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                touch_up();
                invalidate();
                break;
        }
        return true;
    } 
}  

손가락으로 그리기 위해 Android 샘플을 가져 와서 마지막 경로가 아닌 모든 경로를 저장하도록 약간 수정했습니다! 누군가에게 도움이되기를 바랍니다!

건배.


모션 이벤트의 누적 지점을 렌더링하는 여러 방법을 실험했습니다. 결국 두 점 사이의 중간 점을 계산하고 목록의 점을 2 차 베 지어 곡선의 앵커 포인트로 처리하여 최상의 결과를 얻었습니다 (단순한 선으로 다음 중간 점으로 연결된 첫 번째 및 마지막 점 제외). ).

이것은 모서리가없는 부드러운 곡선을 제공합니다. 그려진 경로는 목록의 실제 지점에 닿지 않고 모든 중간 지점을 통과합니다.

Path path = new Path();
if (points.size() > 1) {
    Point prevPoint = null;
    for (int i = 0; i < points.size(); i++) {
        Point point = points.get(i);

        if (i == 0) {
            path.moveTo(point.x, point.y);
        } else {
            float midX = (prevPoint.x + point.x) / 2;
            float midY = (prevPoint.y + point.y) / 2;

            if (i == 1) {
                path.lineTo(midX, midY);
            } else {
                path.quadTo(prevPoint.x, prevPoint.y, midX, midY);
            }
        }
        prevPoint = point;
    }
    path.lineTo(prevPoint.x, prevPoint.y);
}

간단하게하려면 :

public class DrawByFingerCanvas extends View {

    private Paint brush = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Path path = new Path();

    public DrawByFingerCanvas(Context context) {
        super(context);
        brush.setStyle(Paint.Style.STROKE);
        brush.setStrokeWidth(5);
    }

    @Override
    protected void onDraw(Canvas c) {
        c.drawPath(path, brush);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        float x = event.getX();
        float y = event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                path.moveTo(x,y);
                break;
            case MotionEvent.ACTION_MOVE:
                path.lineTo(x, y);
                break;
            default:
                return false;
        }
        invalidate();
        return true;
    }
}

활동에서 :

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(new DrawByFingerCanvas(this));
}

결과:

여기에 이미지 설명 입력

모든 그림을 지우려면 화면을 회전하면됩니다.


나는 매우 비슷한 문제가 있었다. onTouch 메서드를 호출 할 때는 메서드 (onTouch (MotionEvent event) 내부)도 사용해야합니다.

event.getHistorySize();

그리고 그런 것

int histPointsAmount = event.getHistorySize(); 
for(int i = 0; i < histPointsAmount; i++){
    // get points from event.getHistoricalX(i);
    // event.getHistoricalY(i); and use them for your purpouse
}

나는 최근에 이것에 약간의 수정을 가해 야했고, 세 가지를 수행하기 때문에 내가 여기에서 가장 좋은 해결책이라고 생각하는 것을 개발했습니다.

  1. 그것은 당신이 다른 선을 그릴 수 있습니다
  2. 더 큰 브러시 스트로크와 복잡한 큐빅 스플라인을 사용하지 않고 작동합니다.
  3. canvas.drawPath()메서드가 for 루프 외부에 있기 때문에 여기에있는 많은 솔루션보다 빠르기 때문에 여러 번 호출되지 않습니다.

public class DrawView extends View implements OnTouchListener {
private static final String TAG = "DrawView";

List<Point> points = new ArrayList<Point>();
Paint paint = new Paint();
List<Integer> newLine = new ArrayList<Integer>();

public DrawView(Context context, AttributeSet attrs){
        super(context, attrs);
        setFocusable(true);
        setFocusableInTouchMode(true);
        setClickable(true);

        this.setOnTouchListener(this);

        paint.setColor(Color.WHITE);
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(20);

    }

    public void setColor(int color){
        paint.setColor(color);
    }
    public void setBrushSize(int size){
        paint.setStrokeWidth((float)size);
    }
    public DrawView(Context context) {
        super(context);
        setFocusable(true);
        setFocusableInTouchMode(true);

        this.setOnTouchListener(this);


        paint.setColor(Color.BLUE);
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(20);
    }

    @Override
    public void onDraw(Canvas canvas) {
        Path path = new Path();
        path.setFillType(Path.FillType.EVEN_ODD);
        for (int i = 0; i<points.size(); i++) {
            Point newPoint = new Point();
            if (newLine.contains(i)||i==0){
                newPoint = points.get(i)
                path.moveTo(newPoint.x, newPoint.y);
            } else {
                newPoint = points.get(i);

                path.lineTo(newPoint.x, newPoint.y);
            }

        }
        canvas.drawPath(path, paint);
    }

    public boolean onTouch(View view, MotionEvent event) {
        Point point = new Point();
        point.x = event.getX();
        point.y = event.getY();
        points.add(point);
        invalidate();
        Log.d(TAG, "point: " + point);
        if(event.getAction() == MotionEvent.ACTION_UP){
            // return super.onTouchEvent(event);
            newLine.add(points.size());
        }
        return true;
    }
    }

    class Point {
        float x, y;

    @Override
    public String toString() {
        return x + ", " + y;
    }
    }

이것도 효과가 있습니다.

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

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.util.*;

public class DrawView extends View implements OnTouchListener {
    private static final String TAG = "DrawView";
List<Point> points = new ArrayList<Point>();
Paint paint = new Paint();
List<Integer> newLine = new ArrayList<Integer>();

public DrawView(Context context, AttributeSet attrs){
    super(context, attrs);
    setFocusable(true);
    setFocusableInTouchMode(true);

    this.setOnTouchListener(this);

    paint.setColor(Color.WHITE);
    paint.setAntiAlias(true);
}
public DrawView(Context context) {
    super(context);
    setFocusable(true);
    setFocusableInTouchMode(true);

    this.setOnTouchListener(this);

    paint.setColor(Color.WHITE);
    paint.setAntiAlias(true);
    }

@Override
public void onDraw(Canvas canvas) {
    for (int i = 0; i<points.size(); i++) {
        Point newPoint = new Point();
        Point oldPoint = new Point();
        if (newLine.contains(i)||i==0){
            newPoint = points.get(i);
            oldPoint = newPoint;
        } else {
            newPoint = points.get(i);
            oldPoint = points.get(i-1);
        }
            canvas.drawLine(oldPoint.x, oldPoint.y, newPoint.x, newPoint.y, paint);
    }
}

public boolean onTouch(View view, MotionEvent event) {
    Point point = new Point();
    point.x = event.getX();
    point.y = event.getY();
    points.add(point);
    invalidate();
    Log.d(TAG, "point: " + point);
    if(event.getAction() == MotionEvent.ACTION_UP){
        // return super.onTouchEvent(event);
        newLine.add(points.size());
    }
    return true;
    }
}

class Point {
    float x, y;

    @Override
    public String toString() {
        return x + ", " + y;
    }
}

선을 합리적으로 잘 그릴 수 있습니다. 유일한 문제는 선을 두껍게 만들면 선이 약간 이상하게 보이기 때문에 어쨌든 첫 번째 선을 사용하는 것이 좋습니다.


Motion events with ACTION_MOVE may batch together multiple movement samples within a single object. The most current pointer coordinates are available using getX(int) and getY(int). Earlier coordinates within the batch are accessed using getHistoricalX(int, int) and getHistoricalY(int, int). Using them for building path makes it much smoother :

    int historySize = event.getHistorySize();
    for (int i = 0; i < historySize; i++) {
      float historicalX = event.getHistoricalX(i);
      float historicalY = event.getHistoricalY(i);
      path.lineTo(historicalX, historicalY);
    }

    // After replaying history, connect the line to the touch point.
    path.lineTo(eventX, eventY);

Here is a good tutorial on this from Square : http://corner.squareup.com/2010/07/smooth-signatures.html


You may have a lot more information available in your MotionEvent than you realize that can provide some data inbetween.

링크의 예는 이벤트에 포함 된 과거 터치 포인트를 무시합니다. MotionEvent의 문서 상단 근처에있는 '배칭'섹션을 참조하세요 . http://developer.android.com/reference/android/view/MotionEvent.html 점을 선으로 연결하는 것은 나쁜 생각이 아닐 수도 있습니다.


나는이 문제가 있었고 선 대신 점을 그렸습니다. 선을 유지하려면 먼저 경로를 만들어야합니다. 첫 번째 터치 이벤트에서만 path.moveto를 호출하십시오. 그런 다음 캔버스에 경로를 그린 다음 완료된 후 경로를 재설정하거나 되감습니다 (path.reset) ...

참고 URL : https://stackoverflow.com/questions/8287949/android-how-to-draw-a-smooth-line-following-your-finger

반응형