web-dev-qa-db-ja.com

Android指に沿って滑らかな線を引く方法

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

以下の例を使用しています。しかし、画面上で指を速すぎると、線が個々の点に変わります。

描画を高速化できるかどうかわかりません。または、最後の2点を直線で接続する必要があります。これらの2つの解決策の2つ目は良いオプションのように思えますが、非常に速く指を動かすと、直線の長いセクションがあり、その後に鋭いカーブができます。

他の解決策がある場合は、それらを聞くのは素晴らしいことです。

事前に助けてくれてありがとう。

67
Somk

あなたが言及したように、簡単な解決策は、ポイントを直線で単純に接続することです。そのためのコードは次のとおりです。

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);
}

これにより、依然として鋭いエッジが生じます。

本当に意欲的な人は、次のように3次スプラインの計算を開始できます。

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

108
John Ericksen

これはもはやあなたにとって重要ではないかもしれませんが、私はそれを解決するために多くの努力をしました、そして私は共有したいと思います、誰か他の人に役立つかもしれません。

@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サンプルを使用し、最後のパスではなくすべてのパスを保存するように少し変更しました。

乾杯。

34
caiocpricci2

モーションイベントの累積ポイントをレンダリングするいくつかの方法を試しました。最終的に、2つのポイント間の中間点を計算し、リスト内のポイントを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);
}
19

シンプルにしたい場合:

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));
}

結果:

enter image description here

すべての図面を消去するには、画面を回転させるだけです。

7
Andrey

私は非常に似た問題を抱えていました。 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
}
5
y434y

_ACTION_MOVE_を含むモーションイベントは、単一のオブジェクト内で複数のモーションサンプルをまとめてバッチ処理する場合があります。最新のポインター座標は、getX(int)およびgetY(int)を使用して利用できます。バッチ内の以前の座標には、getHistoricalX(int, int)およびgetHistoricalY(int, int)を使用してアクセスします。それらをパスの構築に使用すると、はるかにスムーズになります。

_    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);
_

Squareのこれに関する優れたチュートリアルを次に示します。 http://corner.squareup.com/2010/07/smooth-signatures.html

3
birdy

私は最近これにいくつかの修正を加えなければなりませんでしたが、今では3つのことを行うため、ここで最良のソリューションであると信じるものを開発しました:

  1. 異なる線を描くことができます
  2. 大きなブラシストロークで、複雑な3次スプラインを使用せずに動作します。
  3. canvas.drawPath()メソッドはoutside 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;
    }
}

線を合理的にうまく描画できます。唯一の問題は、線を太くすると、描画される線が少し奇妙に見えることです。実際、最初の線を使用することをお勧めします。

3
jcw

私はこの問題を抱えていました。線ではなく点を描いていました。最初にパスを作成して、回線を保持する必要があります。ファーストタッチイベントでのみpath.movetoを呼び出します。次に、キャンバスにパスを描画し、完了後(path.reset)にパスをリセットまたは巻き戻します...

0
j2emanue

MotionEventには、間にデータを提供できることに気づくよりも多くの情報があります。

リンクの例では、イベントに含まれる過去のタッチポイントは無視されます。 MotionEventのドキュメントの上部にある「バッチ処理」セクションを参照してください: http://developer.Android.com/reference/Android/view/MotionEvent.html 点を線でつなぐことは悪い考えではないかもしれません。

0
adamp