Android jak narysować gładką linię po palcu
Http://marakana.com/tutorials/android/2d-graphics-example.html
Używam poniższego przykładu. Ale kiedy przesuwam palce zbyt szybko po ekranie, linia zmienia się w pojedyncze kropki.
Nie jestem pewien, czy mogę przyspieszyć rysowanie. Albo powinienem połączyć dwa ostatnie punkty linią prostą. Drugie z tych dwóch rozwiązań wydaje się dobrym rozwiązaniem, z wyjątkiem gdy poruszając palcem bardzo szybko będziesz miał długie odcinki linii prostej, a następnie ostre krzywe.
Jeśli są jakieś inne rozwiązania, byłoby miło je usłyszeć.
Z góry dzięki za pomoc.
9 answers
Łatwym rozwiązaniem, jak wspomniałeś, jest po prostu połączenie punktów linią prostą. Oto kod, aby to zrobić:
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);
}
Upewnij się, że zmieniasz farbę z wypełnienia na obrys:
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(2);
paint.setColor(Color.WHITE);
Inną opcją jest połączenie punktów z iterpolacją za pomocą metody 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);
}
To nadal powoduje ostre krawędzie.
Jeśli jesteś naprawdę ambitny, Możesz zacząć obliczać sześcienne spliny w następujący sposób:
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);
}
Również, odkryłem, że potrzebujesz aby zmienić następujące elementy, aby uniknąć powielania zdarzeń ruchu:
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);
}
I dodaj wartości dx & dy do klasy punktowej:
class Point {
float x, y;
float dx, dy;
@Override
public String toString() {
return x + ", " + y;
}
}
To tworzy gładkie linie, ale czasami trzeba połączyć kropki za pomocą pętli. Również w przypadku długich sesji rysowania staje się to obliczeniowo intensywne do obliczania.
Mam nadzieję, że to pomoże... zabawne rzeczy do zabawy.Edit
Rzuciłem razem szybki projekt demonstrujący te różne techniki, w tym Kwadrat proponuje implementację podpisu. Enjoy: https://github.com/johncarl81/androiddraw
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-31 08:17:28
To może nie być już ważne dla ciebie, ale starałem się wiele rozwiązać i chcę się podzielić, może być przydatne dla kogoś innego.
Tutorial z rozwiązaniem @ johncarl oferowane są świetne do rysowania, ale oferują ograniczenie dla moich celów. Jeśli wyjmiesz palec z ekranu i odłożysz go z powrotem, To rozwiązanie narysuje linię między ostatnim kliknięciem a nowym kliknięciem, dzięki czemu cały rysunek będzie zawsze połączony. Więc starałem się znaleźć na to rozwiązanie i w końcu to mam!(przepraszam, jeśli brzmi to oczywiste, jestem początkujący z Grafiką)
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;
}
}
Wziąłem próbkę Androida do rysowania palcem i zmodyfikowałem ją trochę, aby przechowywać każdą ścieżkę, a nie tylko ostatnią! Mam nadzieję, że to komuś pomoże!
Zdrówko.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-12 23:40:21
Eksperymentowałem z kilkoma sposobami renderowania zgromadzonych punktów zdarzeń ruchu. W końcu miałem najlepsze wyniki, obliczając punkty środkowe między dwoma punktami i traktując punkty na liście jako punkty kontrolne kwadratowych krzywych Beziera (z wyjątkiem pierwszego i ostatniego punktu, które są połączone prostymi liniami do następnego punktu środkowego).
Daje to gładką krzywą bez żadnych narożników. Narysowana ścieżka nie dotknie rzeczywistych punktów na liście, ale przejdzie przez każdy w połowie.
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);
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-06 12:58:12
Miałem bardzo podobny problem. Podczas wywoływania metody onTouch należy również użyć metody (wewnątrz zdarzenia onTouch (MotionEvent))
event.getHistorySize();
Oraz coś w tym stylu
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
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-27 19:14:39
Jeśli chcesz to proste:
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;
}
}
W aktywności tylko:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new DrawByFingerCanvas(this));
}
Wynik:
Aby usunąć wszystkie rysunki, po prostu Obróć ekran.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-18 14:29:14
Musiałem ostatnio wprowadzić pewne modyfikacje, a teraz opracowałem to, co uważam za najlepsze rozwiązanie, ponieważ robi to trzy rzeczy: {]}
- pozwala na rysowanie różnych linii Działa z większymi pociągnięciami pędzla i bez użycia skomplikowanych sześciennych splajnów [7]}
- jest szybsza od wielu rozwiązań tutaj, ponieważ metoda
canvas.drawPath()
jest poza pętlą for, więc nie jest wywoływana wielokrotnie.
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;
}
}
To także działa, tylko nie tak dobrze
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;
}
}
To pozwala rysować linie dość dobrze, jedynym problemem jest, jeśli linia grubsza, co sprawia, że rysowane linie wyglądają trochę dziwnie, i naprawdę, polecam korzystanie z pierwszego i tak.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-05 07:59:47
Zdarzenia ruchu z ACTION_MOVE
mogą łączyć wiele próbek ruchu w jednym obiekcie. Najbardziej aktualne współrzędne wskaźnika są dostępne za pomocą getX (int) i getY (int). Wcześniejsze współrzędne w partii są dostępne za pomocą getHistoricalX(int, int)
i getHistoricalY(int, int)
. Używanie ich do budowania ścieżki sprawia, że jest ona znacznie gładsza: {]}
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);
Oto dobry tutorial na ten temat od Square: http://corner.squareup.com/2010/07/smooth-signatures.html
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-16 15:40:37
Możesz mieć o wiele więcej informacji dostępnych w MotionEvent
, niż zdajesz sobie sprawę, że możesz dostarczyć pewne dane w between.
Przykład w linku ignoruje historyczne punkty kontaktu zawarte w wydarzeniu. Zobacz sekcję "Dozowanie" w górnej części dokumentacji MotionEvent
: http://developer.android.com/reference/android/view/MotionEvent.html poza tym łączenie punktów z liniami może nie być złym pomysłem.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-27 18:37:08
Miałem taki problem, rysowałem punkt zamiast linii. Powinieneś najpierw utworzyć ścieżkę, aby utrzymać linię. ścieżka połączenia.moveto tylko w przypadku pierwszego dotknięcia. Następnie na kanwie Narysuj ścieżkę, a następnie zresetuj lub przewiń ścieżkę po zakończeniu (ścieżka.reset)...
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-14 19:00:36