Android Comment dessiner une ligne lisse après votre doigt

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

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

Je ne sais pas si je peux accélérer le dessin. Ou je devrais relier les deux derniers points avec une ligne droite. La seconde 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 d’une ligne droite puis des courbes nettes.

S’il y a d’autres solutions, il serait bon de les entendre.

Merci d’avance pour toute aide.

Une solution simple, comme vous l’avez mentionné, consiste à connecter simplement les points avec une 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 une 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 se traduit toujours par 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 Ssortingng toSsortingng() { return x + ", " + y; } } 

Cela produit des lignes lisses, mais doit parfois connecter les points en utilisant une boucle. En outre, pour les longues sessions de dessin, le calcul nécessite beaucoup de calculs.

J'espère que ça aide ... des trucs amusants avec lesquels jouer.

modifier

J'ai rassemblé un projet rapide démontrant ces différentes techniques, y compris l'implémentation de signatures proposée par Square. Profitez: https://github.com/johncarl81/androiddraw

Ce n’est peut-être plus important pour vous, mais j’ai eu beaucoup de mal à le résoudre et je veux partager, cela pourrait être utile à quelqu’un d’autre.

Le tutoriel avec la solution proposée par @johncarl est excellent pour dessiner, mais ils offrent une limitation à mes fins. Si vous retirez votre doigt de l’écran et le remettez en place, cette solution trace une ligne entre le dernier clic et votre nouveau clic, ce qui rend le dessin entier connecté en permanence. J’essayais donc de trouver une solution pour ça, et finalement je l’ai eu!

 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 Ssortingng 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 paths = new LinkedList(); 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’échantillon androïde pour dessiner avec votre doigt et l’ai modifié un peu pour stocker chaque chemin plutôt que le dernier! J’espère que ça aide quelqu’un!

À votre santé.

J’ai expérimenté plusieurs façons de rendre les points cumulés des événements de mouvement. Au final, j’ai obtenu les meilleurs résultats en calculant les points intermédiaires 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 reliés par des lignes simples 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 intermédiaire.

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

J’ai eu un problème très similaire. Lorsque vous appelez la méthode onTouch, vous devez également utiliser la méthode (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 } 

Si vous voulez que ce soit 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:

entrer la description de l'image ici

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

J’ai dû apporter quelques modifications à cela récemment et j’ai maintenant développé ce que j’estime être la meilleure solution, car elle fait trois choses:

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

 public class DrawView extends View implements OnTouchListener { private static final Ssortingng TAG = "DrawView"; List points = new ArrayList(); Paint paint = new Paint(); List newLine = new ArrayList(); public DrawView(Context context, AtsortingbuteSet 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 

Cela fonctionne aussi, mais pas tout à fait 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 Ssortingng TAG = "DrawView"; List points = new ArrayList(); Paint paint = new Paint(); List newLine = new ArrayList(); public DrawView(Context context, AtsortingbuteSet 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 

Il vous permet de tracer des lignes assez bien, le seul problème est que si vous épaississez la ligne, ce qui rend les lignes dessinées un peu bizarre, et vraiment, je vous recommande d’utiliser le premier.

Les événements de mouvement avec ACTION_MOVE peuvent regrouper plusieurs échantillons de mouvement dans un seul object. Les coordonnées de pointeur les plus courantes sont disponibles avec getX (int) et getY (int). Les coordonnées précédentes dans le lot sont accessibles en utilisant getHistoricalX(int, int) et getHistoricalY(int, int) . Les utiliser pour construire un chemin le rend beaucoup plus fluide:

  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 à ce sujet depuis Square: http://corner.squareup.com/2010/07/smooth-signatures.html

Vous pouvez avoir beaucoup plus d’informations disponibles dans votre MotionEvent que vous ne le réalisez et qui peuvent fournir des données entre elles.

L’exemple de votre lien ignore les points de contact historiques inclus dans l’événement. Voir la section « MotionEvent par MotionEvent » en haut de la MotionEvent de MotionEvent : http://developer.android.com/reference/android/view/MotionEvent.html Au-delà de la connexion des points avec des lignes peut ne pas être une mauvaise idée.

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 uniquement sur votre premier événement tactile. Ensuite, sur votre canvas, tracez le chemin, puis réinitialisez ou rembobinez le chemin après votre passage (path.reset) …