web-dev-qa-db-fra.com

Android Comment tracer une ligne lisse en suivant votre doigt

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

J'utilise cet exemple ci-dessous. Mais lorsque je déplace trop rapidement mes doigts sur l'écran, la ligne se transforme en points individuels.

Je ne suis pas sûr de pouvoir accélérer le dessin. Ou je devrais relier les deux derniers points en ligne droite. La deuxième de ces deux solutions semble être une bonne option, sauf que lorsque vous déplacez votre doigt très rapidement, vous aurez de longues sections de ligne droite, puis des courbes nettes.

S'il y a d'autres solutions, ce serait bien de les entendre.

Merci d'avance pour toute aide.

67
Somk

Comme vous l'avez dit, une solution simple consiste à relier les points en ligne droite. Voici le code pour le faire:

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

assurez-vous de changer votre peinture de remplissage en trait:

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

Une autre option consiste à connecter les points avec iterpolation en utilisant la méthode 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);
}

Cela entraîne toujours des arêtes vives.

Si vous êtes vraiment ambitieux, vous pouvez commencer à calculer les splines cubiques comme suit:

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

En outre, j'ai constaté que vous deviez modifier les éléments suivants pour éviter les événements de mouvement en double:

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

et ajoutez les valeurs dx & dy à la classe Point:

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

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

Cela produit des lignes lisses, mais doit parfois relier les points à l’aide d’une boucle. De plus, pour les longues sessions de dessin, cela demande beaucoup de calcul.

J'espère que ça aide ... des choses amusantes à jouer.

Modifier

J'ai lancé ensemble un projet rapide démontrant ces différentes techniques, y compris la mise en œuvre de la signature suggérée par Square. Profitez de: https://github.com/johncarl81/androiddraw

108
John Ericksen

Cela n’a peut-être plus d’importance pour vous, mais j’ai beaucoup lutté pour le résoudre et j’ai envie de partager, cela pourrait être utile à quelqu'un d’autre.

Le tutoriel avec la solution proposée par @johncarl est très utile pour dessiner, mais il offre une limitation à mes fins. Si vous retirez votre doigt de l'écran et le remettez en place, cette solution tracera une ligne entre le dernier clic et le nouveau clic, en connectant l'ensemble du dessin. J'essayais donc de trouver une solution à ce problème, et finalement je l'ai compris (désolé si cela semble évident, je suis un débutant en graphisme)

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

J'ai pris l'exemple Android) pour dessiner avec votre doigt et je l'ai modifié un peu pour stocker chaque chemin au lieu du dernier! J'espère que cela aidera quelqu'un!

À votre santé.

34
caiocpricci2

J'ai expérimenté plusieurs manières de rendre les points accumulés des événements de mouvement. Au final, j’ai obtenu les meilleurs résultats en calculant les points médians entre deux points et en traitant les points de la liste comme des points d’ancrage des courbes de Bézier quadratiques (sauf le premier et le dernier point qui sont reliés par de simples lignes au point médian suivant). ).

Cela donne une courbe lisse sans aucun coin. Le chemin tracé ne touchera pas les points réels de la liste mais passera par chaque point milieu.

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

Si vous le voulez simple:

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

Dans l'activité juste:

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

Résultat:

enter image description here

Pour effacer tous les dessins, faites simplement pivoter l'écran.

7
Andrey

J'ai eu un problème très similaire. Lorsque vous appelez la méthode onTouch, vous devez également utiliser method (dans onTouch (événement MotionEvent))

event.getHistorySize();

et quelque chose comme ça

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

Les événements de mouvement avec ACTION_MOVE Peuvent regrouper plusieurs échantillons de mouvement dans un même objet. Les coordonnées les plus récentes du pointeur sont disponibles avec getX (int) et getY (int). Les coordonnées antérieures du lot sont accessibles à l'aide de getHistoricalX(int, int) et getHistoricalY(int, int). Leur utilisation pour construire le chemin le rend beaucoup plus lisse:

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

Voici un bon tutoriel de Square à ce sujet: http://corner.squareup.com/2010/07/smooth-signatures.html

3
birdy

J'ai dû apporter quelques modifications à cela récemment, et j'ai maintenant développé ce que je crois être la meilleure solution ici, car il fait trois choses:

  1. Il vous permet de tracer des lignes différentes
  2. Il fonctionne avec des coups de pinceau plus importants et sans utiliser de splines cubiques complexes
  3. C'est plus rapide que beaucoup de solutions ici parce que la méthode canvas.drawPath() est dehors la boucle for, donc elle n'est pas appelée plusieurs fois.

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

Cela fonctionne aussi, mais pas aussi bien

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

Cela vous permet de tracer des lignes raisonnablement bien. Le seul problème est que vous rajoutez une épaisseur plus épaisse à la ligne, ce qui donne un aspect un peu étrange aux lignes tracées.

3
jcw

J'ai eu ce problème, je dessinais un point au lieu d'une ligne. Vous devez d'abord créer un chemin pour tenir votre ligne. appelez path.moveto sur votre premier événement tactile uniquement. Ensuite, sur votre toile, tracez le chemin puis réinitialisez ou rembobinez le chemin après avoir terminé (path.reset) ...

0
j2emanue

Il se peut que vous ayez beaucoup plus d'informations disponibles dans votre MotionEvent que vous ne le réalisez, qui peuvent fournir des données intermédiaires.

L'exemple de votre lien ignore les points de contact historiques inclus dans l'événement. Voir la section 'Traitement par lots' au sommet de la documentation de MotionEvent: http://developer.Android.com/reference/Android/view/MotionEvent.html Au-delà de ce qui relie les points avec les lignes peuvent ne pas être une mauvaise idée.

0
adamp