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.
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
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é.
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);
}
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:
Pour effacer tous les dessins, faites simplement pivoter l'écran.
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
}
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
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:
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.
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) ...
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.