Recreate an object without deleting the last one - java

I'm creating an app that allows user to draw on the screen. My code actually creates a Path and next creates a line. But this when the user takes up his finger of the screen and goes to another part of the screen it make a line as it happen in the next video.
http://youtu.be/CBv1wtUC2g4
The following code is the code of the drawview.
public class DrawView extends View implements OnTouchListener {
private static final String TAG = "DrawView";
List<Point> points = new ArrayList<Point>();
Paint paint = new Paint();
int action;
int draw=0;
Boolean finger=true;
public DrawView(Context context) {
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
paint.setAntiAlias(true);
}
#Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
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);
}
public boolean onTouch(View view, MotionEvent event) {
action = event.getAction();
Point point = new Point();
point.x = event.getX();
point.y = event.getY();
points.add(point);
invalidate();
Log.d(TAG, "point: " + point);
if (action == MotionEvent.ACTION_MOVE) {
finger = true;
}
if (action == MotionEvent.ACTION_UP) {
finger = false;
}
draw++;
return true;
}
}
class Point {
float x, y;
#Override
public String toString() {
return x + ", " + y;
}
}
Also I think that maybe what i want I need is an array that contains objects (paths and lines) but i don't know how to do it. Using this method i think that i'm will be able to go back deleting the last object can be this possible?

You need to create a different "Path" each time the user uplifts the finger. I've changed your code to make that. Try it to see if it's what you need.
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.graphics.Path;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
public class DrawView extends View implements OnTouchListener {
private static final String TAG = "DrawView";
private List<List<Point>> _paths = new ArrayList<List<Point>>();
private List<Point> _lastPath;
private Paint _paint = new Paint();
private Path _path = new Path();
public DrawView(Context context) {
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
setOnTouchListener(this);
_paint.setColor(Color.BLACK);
_paint.setStyle(Paint.Style.STROKE);
_paint.setStrokeWidth(5);
_paint.setAntiAlias(true);
}
#Override
protected void onDraw(Canvas canvas) {
for (List<Point> pointsPath : _paths) {
_path.reset();
boolean first = true;
for (int i = 0; i < pointsPath.size(); i += 2) {
Point point = pointsPath.get(i);
if (first) {
first = false;
_path.moveTo(point.x, point.y);
} else if (i < pointsPath.size() - 1) {
Point next = pointsPath.get(i + 1);
_path.quadTo(point.x, point.y, next.x, next.y);
} else {
_path.lineTo(point.x, point.y);
}
}
canvas.drawPath(_path, _paint);
}
}
public boolean onTouch(View view, MotionEvent event) {
Point point = new Point();
point.x = event.getX();
point.y = event.getY();
Log.d(TAG, "point: " + point);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
_lastPath = new ArrayList<Point>();
_lastPath.add(point);
_paths.add(_lastPath);
break;
case MotionEvent.ACTION_MOVE:
_lastPath.add(point);
break;
}
invalidate();
return true;
}
private class Point {
float x, y;
#Override
public String toString() {
return x + ", " + y;
}
}
}

Related

Draw 2 lines on separate canvases with same points Android

How can I draw a line on one canvas, while duplicating the line in the exact same points on another canvas. My friends told me to use the same listener events for both, but I can't figure out how to do that. I will give you the code for my canvas and lines.
package com.mypackage.morphing;
import android.content.Context;
import android.graphics.*;
import android.graphics.Paint.Style;
import android.util.AttributeSet;
import android.view.*;
import java.util.ArrayList;
public class FingerLine extends View {
private ArrayList<Line> lines = new ArrayList<Line>();
private final Paint mPaint;
private float startX;
private float startY;
private float endX;
private float endY;
private Line tempL;
private int idx = 0;
private boolean firstCanContext = false;
public FingerLine(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Style.STROKE);
mPaint.setStrokeWidth(5);
mPaint.setColor(Color.RED);
}
#Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for(Line l : lines) {
canvas.drawLine(l.startX, l.startY, l.endX, l.endY, mPaint);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lines.add(new Line(event.getX(), event.getY()));
invalidate();
break;
case MotionEvent.ACTION_MOVE:
lines.get(idx).endX = event.getX();
lines.get(idx).endY = event.getY();
invalidate();
break;
case MotionEvent.ACTION_UP: // when the user lifts up
lines.get(idx).endX = event.getX();
lines.get(idx).endY = event.getY();
idx++;
invalidate();
break;
}
return true;
}
public void clearList(){
lines.clear();
idx = 0;
invalidate();
}
public void removeLineNumber(int indexR){
lines.remove(indexR);
idx--;
invalidate();
}
}
package com.mypackage.morphing;
public class Line {
float startX, startY, endX, endY;
public Line(float startX, float startY, float endX, float endY) {
this.startX = startX;
this.startY = startY;
this.endX = endX;
this.endY = endY;
}
public Line(float startX, float startY) { // for convenience
this(startX, startY, startX, startY);
}
}
If you would draw on one canvas and you would like to see the lines drawn appear on the second canvas, simply make a method in your class to "add a line". Whenever you add a line with that method (and subsequently draw it), notify a listener (in this instance, the second canvas) that a line has been added. That second canvas can then decide to draw that line as well (or to fetch the lines from the first canvas and redraw all of them).
So u have 2 classes, both extends View.
FirstView gets the Events when the user touches the screen.
u want SecondView to draw the same things as FirstView.
Your friend says u to add a Listener for both classes.
But u could also send Lines from FirstView to SecondView.
So u create an instance of SecondView in your firstView :
public class FirstView extends View {
SecondView secondView;
Two choices now,
1) Both classes have the ArrayList, when a line is created or modified, u do it in both classes. Dont forget to refresh SecondView !
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Line line = new Line(event.getX(), event.getY());
lines.add(line);
secondView.lines.add(line);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
lines.get(idx).endX = event.getX();
lines.get(idx).endY = event.getY();
secondView.lines.get(idx).endX = event.getX();
secondView.lines.get(idx).endY = event.getY();
2) Only FirstView has the ArrayList, And u draw all lines in both classe in the onDraw method. This is a bad idea because onDraw is called often, so u have to make the least work inside.
I figured out the answer (however it may look scary)
I have 3 classes working on this (not including main, I'll get to that).
The classes are LineController.java, Line.java, and EditingView.java. LineController contains the arraylist's for the objects, lines are the line objects (I just made my own for ease) and EditingView is the types of views I am drawing on top of.
LineController class
public class LineController {
public ArrayList<Line> firstCanvas;
public ArrayList<Line> secondCanvas;
private int idx;
LineController(){
firstCanvas = new ArrayList<>();
secondCanvas = new ArrayList<>();
idx = 0;
}
public void addLine(Line l){
firstCanvas.add(l);
secondCanvas.add(l);
idx++;
}
public void addLine(float x, float y){
firstCanvas.add(new Line(x, y));
secondCanvas.add(new Line(x, y));
idx++;
}
public void addX(int index, float x){
if(index <= idx){
firstCanvas.get(index).endX = x;
secondCanvas.get(index).endX = x;
}
}
public void addX(float x){
firstCanvas.get(firstCanvas.size() - 1).endX = x;
secondCanvas.get(secondCanvas.size() - 1).endX = x;
}
public void addY(int index, float y){
if(index <= idx){
firstCanvas.get(index).endY = y;
secondCanvas.get(index).endY = y;
}
}
public void addY(float y){
firstCanvas.get(firstCanvas.size() - 1).endY = y;
secondCanvas.get(secondCanvas.size() - 1).endY = y;
}
public void clearLists(){
firstCanvas.clear();
secondCanvas.clear();
idx = 0;
}
public boolean removeLast(){
if(firstCanvas.isEmpty()){
return false;
}
if(firstCanvas.size() == 1){
idx = 0;
firstCanvas.clear();
secondCanvas.clear();
}else{
idx--;
firstCanvas.remove(idx);
secondCanvas.remove(idx);
}
return true;
}
}
EditingView class
public class EditingView extends View {
private LineController lc;
private final Paint mPaint;
private boolean drawingMode = true;
private int closestIndex = -1;
private Paint editDot;
private Paint editLine;
private boolean endOfLine;
private boolean noLine = true;
private Point lastTouch;
private final static int MAX_DISTANCE = 100;
private Line editingLine = null;
private int viewIndex;
public EditingView(Context context){
super(context);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
editDot = new Paint(Paint.ANTI_ALIAS_FLAG);
editLine = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Style.STROKE);
mPaint.setStrokeWidth(5);
mPaint.setColor(Color.RED);
editDot.setColor(Color.BLUE);
editLine.setColor(Color.CYAN);
}
public EditingView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Style.STROKE);
mPaint.setStrokeWidth(5);
mPaint.setColor(Color.RED);
}
public void init(LineController controller){
lc = controller;
}
#Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(viewIndex == 0){ // first View
for (Line l : lc.firstCanvas) {
canvas.drawLine(l.startX, l.startY, l.endX, l.endY, mPaint);
}
if(!drawingMode){
// if in edit, draw a line around the index we found
if(closestIndex != -1) {
canvas.drawCircle(lc.firstCanvas.get(closestIndex).startX,
lc.firstCanvas.get(closestIndex).startY, 20, editDot);
canvas.drawCircle(lc.firstCanvas.get(closestIndex).endX,
lc.firstCanvas.get(closestIndex).endY, 20, editDot);
}
}
}else if(viewIndex == 1){
for (Line l : lc.secondCanvas) {
canvas.drawLine(l.startX, l.startY, l.endX, l.endY, mPaint);
}
if(!drawingMode){
// if in edit, draw a line around the index we found
if(closestIndex != -1) {
canvas.drawCircle(lc.secondCanvas.get(closestIndex).startX,
lc.secondCanvas.get(closestIndex).startY, 20, editDot);
canvas.drawCircle(lc.secondCanvas.get(closestIndex).endX,
lc.secondCanvas.get(closestIndex).endY, 20, editDot);
}
}
}
}
public void drawLine(MotionEvent event){
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lc.addLine(event.getX(), event.getY());
invalidate();
break;
case MotionEvent.ACTION_MOVE:
lc.addX(event.getX());
lc.addY(event.getY());
invalidate();
break;
case MotionEvent.ACTION_UP:
lc.addX(event.getX());
lc.addY(event.getY());
invalidate();
break;
}
}
public void editLine(MotionEvent event){
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
// run function to find the closest point based on that object
lastTouch = new Point((int)event.getX(), (int)event.getY());
editingLine = findClosestLine(); // this should be for any point on the screen
if(editingLine == null){
noLine = true;
}else{
noLine = false;
endOfLine = checkPointStartEnd(editingLine);
}
invalidate();
break;
case MotionEvent.ACTION_MOVE:
// edit the point to have the new points from the touch event
if(!noLine){
// we found one
if(!endOfLine){
editingLine.startX = event.getX();
editingLine.startY = event.getY();
}else{
editingLine.endX = event.getX();
editingLine.endY = event.getY();
}
invalidate();
}
break;
case MotionEvent.ACTION_UP:
if(!noLine){
// we found one
if(!endOfLine){
editingLine.startX = event.getX();
editingLine.startY = event.getY();
}else{
editingLine.endX = event.getX();
editingLine.endY = event.getY();
}
editingLine = null;
invalidate();
}
lastTouch = null;
break;
}
}
public void editMode(int index){
drawingMode = false;
closestIndex = index;
}
public void clear() { closestIndex = -1; }
public void drawingMode(){
drawingMode = true;
}
public void viewIndex(int index){
viewIndex = index;
}
// finds the closest line in either array (checks the screen right?)
private Line findClosestLine(){
int temp1, temp2;
Line tempLine = null;
int closestDistance = MAX_DISTANCE;
// needs to loop through the array list, for both startX,Y and endX,Y of each line in the array
// then needs to get the index to that point and draw a circle around that point
// also change the colour of the line and the corresponding line based on that index
if(viewIndex == 0){
for(int i = 0; i < lc.firstCanvas.size(); i++){
temp1 = checkPoint(lc.firstCanvas.get(i));
if(temp1 < closestDistance && temp1 != -1) {
tempLine = lc.firstCanvas.get(i);
closestIndex = i;
}
}
}else{
for(int i = 0; i < lc.firstCanvas.size(); i++){
temp2 = checkPoint(lc.secondCanvas.get(i));
if(temp2 < closestDistance && temp2 != -1){
tempLine = lc.secondCanvas.get(i);
closestIndex = i;
}
}
}
return tempLine;
}
// Checks the point of a line to see if it is close
private int checkPoint(Line l){
int firstDistance = pyth((lastTouch.x - l.startX), (lastTouch.y - l.startY));
int secondDistance = pyth((lastTouch.x - l.endX), (lastTouch.y - l.endY));
if(MAX_DISTANCE > firstDistance) {
return (int)firstDistance;
}else if(MAX_DISTANCE > secondDistance){
return (int)secondDistance;
}
return -1;
}
// Checks the line we have found for the close point being start or end
private boolean checkPointStartEnd(Line l){
boolean start = false;
int firstDistance = pyth((lastTouch.x - l.startX), (lastTouch.y - l.startY));
int secondDistance = pyth((lastTouch.x - l.endX), (lastTouch.y - l.endY));
if(MAX_DISTANCE < firstDistance) {
start = true;
}else if(MAX_DISTANCE < secondDistance){
start = false;
}
return start;
}
// returns pythagorian theorum
private int pyth(double x, double y){
int z;
z = (int)Math.sqrt(((x * x) + (y * y)));
return z;
}
}
Line Class
public class Line {
float startX, startY, endX, endY;
public Line(float startX, float startY, float endX, float endY) {
this.startX = startX;
this.startY = startY;
this.endX = endX;
this.endY = endY;
}
public Line(float startX, float startY) { // for convenience
this(startX, startY, startX, startY);
}
}
MainActivity (only what's important)
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
lc = new LineController();
firstCanvas = new EditingView(this);
secondCanvas = new EditingView(this);
firstCanvas.viewIndex(0);
secondCanvas.viewIndex(1);
firstCanvas.init(lc);
secondCanvas.init(lc);
firstCanvas.setOnTouchListener(new TouchListener());
secondCanvas.setOnTouchListener(new TouchListener());
firstFrame = (FrameLayout)findViewById(R.id.firstFrame);
secondFrame = (FrameLayout)findViewById(R.id.secondFrame);
firstFrame.addView(firstCanvas);
secondFrame.addView(secondCanvas);
}
private class TouchListener implements View.OnTouchListener{
#Override
public boolean onTouch(View v, MotionEvent event) {
EditingView temp = (EditingView)v;
if(drawingMode) {
// drawing mode
temp.drawLine(event);
updateCanvas();
}else{
// edit mode
temp.editLine(event);
updateCanvas();
}
return true;
}
}
This is all that I used. What happens is that I have a controller object created inside of the main and passed to the EditViews to hold the arrays. I then access only the arrays depending on the view that is being used. I then setup a touch listener created inside of the MainActivity (could be broken up) class that will be used to handle what mode the user is in, (either draw or edit). Using all this will allow me to create new views (programmatically) using the listener for the whole screen inside of the MainActivity.
I know this isn't the best way to do it all, but it works around and I could not find anything else. Hope this helps others who are looking to do something of the same thing. Or at least work towards their own solution.

Android conflict between, OnTouchListener SimpleOnGestureListener and setOnClickListener

I am trying to create an animation:
Use Case :
1. User swipes/ drags item to right ( horizontally ), item gets added into basket. If he again swipes it adds one more of the same item into the basket.
2. User swipes/ drags item to left ( horizontally ), item gets removes from the basket, if was added before, if there is no item in the basket we leave it as it is.
Drag effect : When user drags, item gets moved along with the finger. As soon as he leave it, items gets back to its position.
Zoom effect : When user clicks on the item, item gets zoomed, letting user know that item has been added into the basket. ( replicated method of right dragged ).
I tried to use OnSwipeTouchListener Class specially for this purpose :
import android.content.Context;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import PointF;
public class OnSwipeTouchListener implements OnTouchListener {
private final GestureDetector gestureDetector;
PointF DownPT = null;
PointF StartPT = null;
float startX;
float startY;
public OnSwipeTouchListener(Context context , float x, float y) {
gestureDetector = new GestureDetector(context, new GestureListener());
DownPT = new PointF();
StartPT = new PointF();
this.startX = x;
this.startY = y;
}
public void onSwipeLeft() {
}
public void onSwipeRight() {
}
public boolean onTouch(View v, MotionEvent event) {
int eid = event.getAction();
switch (eid) {
case MotionEvent.ACTION_MOVE:
PointF mv = new PointF(event.getX() - DownPT.x, event.getY() - DownPT.y);
v.setX((int) (StartPT.x + mv.x));
v.setY(this.startY);
StartPT = new PointF(v.getX(), v.getY());
break;
case MotionEvent.ACTION_DOWN:
DownPT.x = event.getX();
DownPT.y = event.getY();
StartPT = new PointF(v.getX(), v.getY());
break;
case MotionEvent.ACTION_UP:
// Move image back to its original position.
v.setX(this.startX);
v.setY(this.startY);
break;
default:
v.setX(this.startX);
v.setY(this.startY);
break;
}
return gestureDetector.onTouchEvent(event);
}
private final class GestureListener extends SimpleOnGestureListener {
private static final int SWIPE_DISTANCE_THRESHOLD = 100;
private static final int SWIPE_VELOCITY_THRESHOLD = 100;
#Override
public boolean onDown(MotionEvent e) {
return true;
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
float distanceX = e2.getX() - e1.getX();
float distanceY = e2.getY() - e1.getY();
if (Math.abs(distanceX) > Math.abs(distanceY) && Math.abs(distanceX) > SWIPE_DISTANCE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
if (distanceX > 0)
onSwipeRight();
else
onSwipeLeft();
return true;
}
return false;
}
}
}
When My method OnTouch is like below, my onSwipeRight() and onSwipeLeft() gets triggered but when I have implemented onTouch as above in the code, these two methods does not get triggered.
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
Regarding Zoom effect on Click event.
In my Fragment I am zooming image like this.
offerimg1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
zoomAnimation(v, offerimg1);
}
});
And my zoomAnimation method is :
private void zoomAnimation(View view,ImageView image) {
Animation animation = AnimationUtils.loadAnimation(getActivity().getApplicationContext(), R.anim.zoom);
image.startAnimation(animation);
}
My Zoom is working unless I have not implemented in my fragmented :
offerimg1.setOnTouchListener(new OnSwipeTouchListener(getActivity().getApplicationContext(),offerimg1.getX(),offerimg1.getY()) {
#Override
public void onSwipeLeft() {
Log.d("onTouch "," swipe left");
}
#Override
public void onSwipeRight() {
Log.d("onTouch "," swipe right");
}
});
I am not sure what is the collision between these events. I need to achieve my above use case, every events should work on each image. As my images are in a scroll view in a fragment.
If you want I can share more code here. Please let me know if I could not clarify my problem.
I really appreciate your help.
Regards,
Shashank Pratap
Finally I have solved the problem,
Found Problem with above code :
onSwipeLeft() and onSwipeRight().. since my image is moving along with
my finger, this is the reason distanceX is turing out to be zero,
which is always less than static SWIPE_DISTANCE_THRESHOLD. and in the
code we are saying that if distanceX is negative its swipeLeft() else
swipeRight()
Solution :
We needed someway to trap which way user is moving his/her finger, therefore I decided within MotionEvent.ACTION_MOVE: event, find if mv.x is negative or mv.x is positive. Once I found it, it was easy to make decision, and on that basis we can decide which method to run within GestureListener.
Here is my OnSwipeTouchListener, I am still not sure if this is the best solution for this case, but I found it working on AVD as well as on Android device.
I am still open to find a nice and clear method ( best practice ) of doing this :
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.Toast;
import R;
import StartingPoints;
import PointF;
public class OnSwipeTouchListener implements OnTouchListener {
private static final String APP = OnSwipeTouchListener.class.getName() ;
private final GestureDetector gestureDetector;
PointF DownPT = null;
PointF StartPT = null;
Context _context;
static boolean isLeftMoved = false;
static boolean isRightMoved = false;
/**
* Max allowed duration for a "click", in milliseconds.
*/
private static final int MAX_CLICK_DURATION = 1000;
/**
* Max allowed distance to move during a "click", in DP.
*/
private static final int MAX_CLICK_DISTANCE = 15;
private static final float PIXELS_PER_SECOND = 5;
private long pressStartTime;
private float pressedX;
private float pressedY;
private boolean stayedWithinClickDistance;
Resources resources;
private float startX = 0f;
private float startY = 0f;
private boolean isNewImage = true;
public OnSwipeTouchListener(Context context, Resources resources) {
this._context = context;
gestureDetector = new GestureDetector(context, new GestureListener());
DownPT = new PointF();
StartPT = new PointF();
this.resources = resources;
}
public void onSwipeLeft() {
}
public void onSwipeRight() {
}
public boolean onTouch(View v, MotionEvent e) {
if(isNewImage){
isNewImage = false;
startX = v.getX();
startY = v.getY();
}
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
//animation code
DownPT.x = e.getX();
DownPT.y = e.getY();
StartPT = new PointF(v.getX(), v.getY());
//calculation code
pressStartTime = System.currentTimeMillis();
pressedX = e.getX();
pressedY = e.getY();
stayedWithinClickDistance = true;
break;
case MotionEvent.ACTION_MOVE:
// animation code
PointF mv = new PointF(e.getX() - DownPT.x, e.getY() - DownPT.y);
v.setX((int) (StartPT.x + mv.x));
v.setY(startY);
StartPT = new PointF(v.getX(), v.getY());
if(mv.x < 0 ){
isLeftMoved = true;
}
if(mv.x > 0 ){
isRightMoved = true;
}
//calculation code
if (stayedWithinClickDistance && distance(pressedX, pressedY, e.getX(), e.getY()) > MAX_CLICK_DISTANCE) {
stayedWithinClickDistance = false;
}
Log.d("Moved ","Item moved");
break;
case MotionEvent.ACTION_UP:
if(!stayedWithinClickDistance){
v.setX(startX);
v.setY(startY);
isNewImage = true;
}
long pressDuration = System.currentTimeMillis() - pressStartTime;
if (pressDuration < MAX_CLICK_DURATION && stayedWithinClickDistance) {
// Click event has occurred
Log.d("Stayed"," With Click event");
zoomAnimation(v);
isNewImage = true;
}
break;
default:
// Move image back to its original position, by default
Log.d("default", "This is default ");
v.setX(startX);
v.setY(startY);
isNewImage = true;
break;
}
return gestureDetector.onTouchEvent(e);
}
private float distance(float x1, float y1, float x2, float y2) {
float dx = x1 - x2;
float dy = y1 - y2;
float distanceInPx = (float) Math.sqrt(dx * dx + dy * dy);
return pxToDp(distanceInPx);
}
private float pxToDp(float px) {
return px / resources.getDisplayMetrics().density;
}
private void zoomAnimation(View view) {
Animation animation = AnimationUtils.loadAnimation(_context, R.anim.zoom);
view.startAnimation(animation);
}
private final class GestureListener extends SimpleOnGestureListener {
private static final int SWIPE_DISTANCE_THRESHOLD = 20;
private static final int SWIPE_VELOCITY_THRESHOLD = 50;
#Override
public boolean onDown(MotionEvent e) {
return true;
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
float maxFlingVelocity = ViewConfiguration.get(_context).getScaledMaximumFlingVelocity();
float velocityPercentX = velocityX / maxFlingVelocity; // the percent is a value in the range of (0, 1]
float normalizedVelocityX = velocityPercentX * PIXELS_PER_SECOND; // where PIXELS_PER_SECOND is a device-independent measurement
float distanceX = e2.getX() - e1.getX();
float distanceY = e2.getY() - e1.getY();
if (isLeftMoved || isRightMoved) {
if(isRightMoved) {
isLeftMoved = false;
isRightMoved = false;
onSwipeRight();
}
else {
isLeftMoved = false;
isRightMoved = false;
onSwipeLeft();
}
}
return false;
}
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return true;
}
}
}
Best regards,
Shashank Pratap

thread doesn't update an array

I'm trying to make a program with graphs and print the graph on the screen. I am using the GraphPainter (a SurfaceView type, implements Runnable) class to paint the graph to the screen. In the drawing function, which loops indefinitely, i am updating the elements of the graph: the node list and the edge list from the Graph class. The node list is perfectly updated from the Graph (when i delete, add or select a node), but the edge list in the Painter don't update if I delete an edge from the actual Graph.
I debugged it and the graphEdgeList from the Graph class is updated correctly when I remove an edge. It's size gets lower. Can anybody tell me why isn't the list from GraphPainter updated accordingly?
GraphDrawer.java:
package com.rares.graphit;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.Window;
import android.view.WindowManager;
public class GraphDrawer extends Activity implements OnTouchListener{
/**Suface Manipulator*/
GraphPainter gp;
/**Graph object*/
public static Graph graph = null;
/**Touch coordinates*/
public static float touchX = 0;
public static float touchY = 0;
/**Drag state*/
public boolean isDragging = false;
/**Drag coordinates*/
public static float dragX = 0;
public static float dragY = 0;
/**Id of the node that is currently selected
* Is -1 if no node is selected during a touch event*/
public static int currentId = -1;
/**Id of a node that was selected before and an action between 2 nodes or on a node
* that needs to be deleted or moved*/
public static int firstId = -1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*Set window to fullscreen**/
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
gp = new GraphPainter(this);
gp.setOnTouchListener(this);
graph = new Graph();
setContentView(gp);
}
#Override
public boolean onTouch(View view, MotionEvent event) {
// TODO Auto-generated method stub
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
touchX = event.getX();
touchY = event.getY();
currentId = graph.pointIntersectsNode(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
dragX = event.getX();
dragY = event.getY();
/**Function will return -1 if there is no intersection or node ID if drag start intersects the node*/
/**Selected node will be red on the screen*/
if ((dragX - touchX)*(dragX - touchX) + (dragY - touchY)*(dragY - touchY) > Graph.Circle.RADIUS_DEFAULT){
if(currentId != -1){
graph.setNodePosition(currentId, dragX, dragY);
}
isDragging = true;
}
break;
case MotionEvent.ACTION_UP:
if(!isDragging){
if(currentId == -1)
graph.addNode(touchX, touchY);
else{
if(!graph.getGraphNodeList().get(currentId).isSelected){
graph.markNodeAsSelected(currentId);
if(firstId == -1)
firstId = currentId;
else{
if(graph.isEdge(Math.min(firstId, currentId), Math.max(firstId, currentId))){
graph.deleteEdge(Math.min(firstId,currentId),Math.max(firstId, currentId));
}
graph.addEdge(Math.min(firstId,currentId),Math.max(firstId, currentId));
graph.markNodeAsDeselected(currentId);
graph.markNodeAsDeselected(firstId);
firstId = -1;
}
}
else{
Log.d("IDS", "ID1: " + Integer.toString(currentId) + "\nSelected: " + Integer.toString(firstId));
/*graph.markNodeAsDeselected(id1);*/
if(firstId == currentId){
graph.deleteNode(currentId);
}
currentId = -1;
firstId = -1;
}
}
}
/*else
if(id != -1){
graph.markNodeAsDeselected(id);
id = -1;
}*/
//Reset values
isDragging = false;
touchX = touchY = dragX = dragY = 0;
break;
}
return true;
}
#Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
gp.resume();
}
#Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
gp.pause();
}
}
GraphPainter.java:
package com.rares.graphit;
import java.util.ArrayList;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class GraphPainter extends SurfaceView implements Runnable {
/**ArrayLists for drawing the elements of the graph*/
ArrayList<Graph.Node> listNode;
ArrayList<Graph.Edge> listEdge;
/**Holder of surface*/
SurfaceHolder holder;
/**Drawing thread*/
Thread drawThread;
/**Canvas for drawing*/
Canvas canvas = null;
/**Bool for running state*/
boolean isRunning = false;
public GraphPainter(Context context){
super(context);
holder = getHolder();
drawThread = new Thread(this);
isRunning = true;
drawThread.start();
}
#Override
public void run(){
while (isRunning) {
if (holder.getSurface().isValid()) {
canvas = holder.lockCanvas();
/**Draw background*/
canvas.drawRGB(255, 255, 255);
/**Draw Graph*/
drawGraph();
/**Print to screen*/
holder.unlockCanvasAndPost(canvas);
}
}
}
public void drawGraph(){
/**Draw Egdes*/
listEdge = GraphDrawer.graph.getGraphEdgeList();
Log.d("PAINT_EDGES", "Size of listEdge: " + listEdge.size());
for(int i = 0; i < listEdge.size(); ++i){
float startX = listNode.get(listEdge.get(i).id1).drawingCircle.x;
float startY = listNode.get(listEdge.get(i).id1).drawingCircle.y;
float stopX = listNode.get(listEdge.get(i).id2).drawingCircle.x;
float stopY = listNode.get(listEdge.get(i).id2).drawingCircle.y;
Paint linePaint = new Paint();
linePaint.setColor(Graph.NODE_COLOR_UNSELECTED);
canvas.drawLine(startX, startY, stopX, stopY, linePaint);
}
/**Draw Nodes*/
listNode = GraphDrawer.graph.getGraphNodeList();
for(int i = 0; i < listNode.size(); ++i){
canvas.drawCircle(listNode.get(i).drawingCircle.x, listNode.get(i).drawingCircle.y, listNode.get(i).drawingCircle.R, listNode.get(i).drawingCircle.circlePaint);
}
/*clear the arraylists**/
/*listNode.clear();
listEdge.clear();*/
}
/**Will be called from GraphDrawer when onPause() will occur*/
public void pause(){
isRunning = false;
try {
drawThread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
drawThread = null;
}
public void resume(){
drawThread = new Thread(this);
isRunning = true;
drawThread.start();
}
}
Graph.java:
package com.rares.graphit;
import java.util.ArrayList;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;
/**Graph structure and manipulator. Momentarily, it will be an unoriented graph*/
public class Graph {
/**Circle for drawing purposes*/
public static class Circle{
/**Circle Radius setting*/
public static final float RADIUS_DEFAULT = 30;
/**Circle coordinates*/
float x, y;
float R;
/**Circle style*/
Paint circlePaint = null;
/*Circle default color*/
public int DEFAULT_COLOR = Color.BLUE;
public int drawColor;
/**Creates a circle with the Point as center, r as radius, and the default color
* of a node*/
public Circle(float x, float y, float r){
this.x = x;
this.y = y;
this.R = r;
this.circlePaint = new Paint();
this.circlePaint.setColor(DEFAULT_COLOR);
drawColor = DEFAULT_COLOR;
}
}
/**Graph node structure*/
public static class Node{
/**For drawing purposes*/
Circle drawingCircle;
/**Node ID in the graph*/
int ID;
/**Flag that shows wether the node is selected*/
public boolean isSelected = false;
public Node(float x, float y){
this.ID = numberOfNodes;
drawingCircle = new Circle(x, y, Circle.RADIUS_DEFAULT);
}
public void setColor(int color){
this.drawingCircle.circlePaint.setColor(color);
}
}
/**Graph edge structure*/
public static class Edge{
/**Nodes between whom the edge will be drawn
* id1 - the lower number
* id2 - the higher number
* */
public int id1, id2;
/**Constructor that will set the nodes of the edge*/
public Edge(int a, int b){
if(a > b){
id1 = b; id2 = a;
}
else{
id1 = a; id2 = b;
}
}
}
/**List of vicinity of the graph*/
public static ArrayList<Node> graphNodeList;
/**Current number of nodes*/
public static int numberOfNodes = 0;
/**List of the edges of the graph*/
public static ArrayList<Edge> graphEdgeList;
/**Node default colors*/
public static int NODE_COLOR_UNSELECTED = Color.BLUE;
public static int NODE_COLOR_SELECTED = Color.RED;
/**All elements will be added through addNode() or addEdge() methods*/
public Graph(){
graphNodeList = new ArrayList<Graph.Node>(0);
graphEdgeList = new ArrayList<Graph.Edge>(0);
}
/**List of vicinity getter*/
public ArrayList<Node> getGraphNodeList(){
return graphNodeList;
}
/**List of Edges getter*/
public ArrayList<Edge> getGraphEdgeList(){
return graphEdgeList;
}
/**Adds a node into the graph, with the canvas coordinates: x and y*/
public void addNode(float x, float y){
Node newNode = new Node(x, y);
newNode.ID = graphNodeList.size();
graphNodeList.add(newNode);
newNode = null;
}
/**Deletes a node from the graph */
void deleteNode(int id){
graphNodeList.remove(id);
deleteEdges(id);
for(int i = id; i < graphNodeList.size(); ++i){
int aux = graphNodeList.get(i).ID;
if(aux != i)
resetEdgeNodes(aux, i);
graphNodeList.get(i).ID = i;
}
}
/**Verifies if a point described by its coordinates intersects one if the nodes on
* the screen.
* Returns the ID of the node if true
* -1 otherwise*/
public int pointIntersectsNode(float X, float Y){
for(int i = 0; i < graphNodeList.size(); i++){
float centerX = graphNodeList.get(i).drawingCircle.x;
float centerY = graphNodeList.get(i).drawingCircle.y;
float circleRadius = graphNodeList.get(i).drawingCircle.R;
if((centerX-X) * (centerX-X) + (centerY-Y)*(centerY-Y) < circleRadius*circleRadius)
return i;
}
return -1;
}
/**Marks node as selected and will paint it the COLOR_SELECTED color*/
public void markNodeAsSelected(int id){
graphNodeList.get(id).setColor(NODE_COLOR_SELECTED);
graphNodeList.get(id).isSelected = true;
}
/**Marks the node back as deselected, with its default color*/
public void markNodeAsDeselected(int id) {
graphNodeList.get(id).drawingCircle.circlePaint.setColor(NODE_COLOR_UNSELECTED);
graphNodeList.get(id).isSelected = false;
}
/**Sets the position of the node with the id ID at the (X,Y) point*/
public void setNodePosition(int id, float X, float Y) {
graphNodeList.get(id).drawingCircle.x = X;
graphNodeList.get(id).drawingCircle.y = Y;
}
/**Adds an edge between two nodes*/
public void addEdge(int id1, int id2){
Edge edge = new Edge(id1, id2);
graphEdgeList.add(edge);
}
/**Verifies if an edge between nodes id1 and id2 exists*/
public boolean isEdge(int id1, int id2){
for(int i = 0; i < graphEdgeList.size(); ++i){
if(id1 == graphEdgeList.get(i).id1 && id2 == graphEdgeList.get(i).id2)
return true;
}
return false;
}
/**Deletes an edge from the list, by the nodes IDs
* id1 is the node with the minimum id
* id2 is the node with the maximum id
* */
public void deleteEdge(int id1, int id2){
Log.d("EDGE_DELETE", "Size before a single edge deletion: " + Integer.toString(graphEdgeList.size()));
for(int i = 0; i < graphEdgeList.size(); ++i)
if(id1 == graphEdgeList.get(i).id1 && id2 == graphEdgeList.get(i).id2){
Log.d("EDGE", "EDGE DELETED" + Integer.toString(id1) + " " + Integer.toString(id2));
graphEdgeList.remove(i);
Log.d("EDGE_DELETE", "Size after a single edge deletion: " + Integer.toString(graphEdgeList.size()));
return;
}
}
/**Deletes the edges which has one of the nodes with its ID set to id*/
public void deleteEdges(int id){
for(int i = 0; i < graphEdgeList.size(); ++i){
if(id == graphEdgeList.get(i).id1 || id == graphEdgeList.get(i).id2){
graphEdgeList.remove(i);
i--;
}
}
}
/**Resets the List of Edges when a node is deleted*/
public void resetEdgeNodes(int oldId, int newId){
for(int i = 0; i < graphEdgeList.size(); ++i){
int nodeId1 = graphEdgeList.get(i).id1;
int nodeId2 = graphEdgeList.get(i).id2;
if(nodeId1 == oldId){
graphEdgeList.get(i).id1 = newId;
}
else
if(nodeId2 == oldId){
graphEdgeList.get(i).id2 = newId;
}
/*If the new nodes are nod in order, swap them**/
if(graphEdgeList.get(i).id1 > graphEdgeList.get(i).id2){
int aux = graphEdgeList.get(i).id1;
graphEdgeList.get(i).id1 = graphEdgeList.get(i).id2;
graphEdgeList.get(i).id2 = aux;
}
}
}
}
The main problem you are running into is a common issue in multithreaded programming. In java, two of the concerns you need to address are the safety of accessing the graph object from one thread while modifying it from another.
The other concern is related in that both threads may have a different view of the graph object and measures must be taken to let the runtime/compiler know that after certain code boundaries, the view of the graph object must be synchronized.
It would likely help to research synchronized keyword and memory synchronizations. Of course other solutions/optimizations may exist, the best approach is to understand the multithreading concerns mentioned and first take the most straightforward solution .... Deal with performance / optimization later if possible.

Android Game Java removing a Bitmap from a linked list

So, I have a various Bitmaps on my canvas that are animated as individual classes.
When the Bitmap goes off screen i want the Bitmap to be removed from the linkedlist. they are in a linkedlist as there can be more than one of the same bitmap on the screen.
The bitmap is removing from the screen but it is still in the linkedlist as the linkedlist size is 1.
If i add more than one bitmap to the linkedlist they both get drawn but when on goes of the screen the game crashes.
Panel Class.
public void doDraw(long elapsed, long score, Canvas canvas) {
canvas.drawBitmap(background, 0, 0, null); // draw a black background
synchronized (mChimneys) {
if ((mPresents.size() < 3)) {
for (Present presents : mPresents) {
presents.doDraw(canvas);
}
}
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
synchronized (mChimneys) {
float x = event.getX();
float y = event.getY();
float mX = 0;
float mY = 0;
int mWidth = 0;
int mHeight = 0;
for (Santa santas : mSantas) {
mX = santas.getmX();
mY = santas.getmY();
mWidth = santas.getmBitmap().getWidth();
mHeight = santas.getmBitmap().getHeight();
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (x >= mX && x < (mX + mWidth) && y >= mY
&& y < (mY + mHeight)) {
if ((mPresents.size() < 4)) {
mPresents.add(new Present(getResources(), mX, mY));
mPresentNumber = mPresents.size();
}
}
}
}
return true;
}
public void animate(long elapsedTime) {
synchronized (mChimneys) {
for (Present presents : mPresents) {
boolean remove = presents.animate(elapsedTime);
if (remove) {
mPresents.remove(presents);
}
}
}
Present Class.
package com.droidnova.android;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
public class Present {
private float mX;
private float mY;
private Bitmap mBitmap;
private int mSpeedY;
Panel panel;
public Present(Resources res, float x, float y) {
Random rand = new Random();
List<Integer> my_presents = new LinkedList<Integer>();
my_presents.add(R.drawable.presentblue);
my_presents.add(R.drawable.presentpurple);
my_presents.add(R.drawable.presentred);
my_presents.add(R.drawable.presentyellow);
int choice = rand.nextInt(my_presents.size());
mBitmap = BitmapFactory.decodeResource(res, my_presents.get(choice));
mX = x + 50;
mY = y + 100;
mSpeedY = 5;
}
public void doDraw(Canvas canvas) {
canvas.drawBitmap(mBitmap, mX, mY, null);
}
public boolean animate(long elapsedTime) {
mY += mSpeedY * (elapsedTime / 20f);
boolean remove = checkBorders();
if (remove) {
return true;
}
return false;
}
private boolean checkBorders() {
if (mY + mBitmap.getHeight() >= Panel.mHeight) {
return true;
// mSpeedY = -mSpeedY;
// mY = Panel.mHeight - mBitmap.getHeight();
}
return false;
}
}
Any help is appreciated.
I think these lines may cause a problem:
for (Present presents : mPresents) {
boolean remove = presents.animate(elapsedTime);
if (remove) {
mPresents.remove(presents);
}
}
Since you are trying to remove "presents" from the "mPresents", while you are on that element of the list. I could be wrong, but I think this is a bit like trying to eat your own head. It might help to create another list (or just another Present object), and then remove that from the list after the loop is finished. For example, something like this:
ArrayList <Present> presentsToRemove = new ArrayList<Present>();
for (Present presents : mPresents) {
if (presents.animate(elapsedTime)) {
presentsToRemove.add(presents);
}
}
mPresents.removeAll(presentsToRemove);
Please let me know if this helps, or if I've misunderstood.

Android How to draw a smooth line following your finger

http://marakana.com/tutorials/android/2d-graphics-example.html
I am using this example below. But when I move my fingers too fast across the screen the line turns to individual dots.
I am not sure whether I can speed up the drawing. Or I should connect the two last points with a straight line. The second of these two solutions seems like a good option, except when moving your finger very fast you will have long sections of a straight line then sharp curves.
If there are any other solutions it would be great to hear them.
Thanks for any help in advance.
An easy solution, as you mentioned, is to simply connect the points with a straight line. Here's the code to do so:
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);
}
make sure you change your paint from fill to stroke:
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(2);
paint.setColor(Color.WHITE);
Another option is to connect the points with iterpolation using the quadTo method:
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);
}
This still results in some sharp edges.
If you're really ambitious, you can start to calculate the cubic splines as follows:
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);
}
Also, I found that you needed to change the following to avoid duplicate motion events:
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);
}
and add the dx & dy values to the Point class:
class Point {
float x, y;
float dx, dy;
#Override
public String toString() {
return x + ", " + y;
}
}
This produces smooth lines, but sometimes has to connect the dots using a loop.
Also, for long drawing sessions, this becomes computationally intensive to calculate.
Edit
I threw together a quick project demonstrating these different techniques, including Square's suggessted signature implementation. Enjoy: https://github.com/johncarl81/androiddraw
This might be not important anymore for you but I struggled a lot to solve it and I want to share, might be useful to someone else.
The tutorial with the solution #johncarl offered are great to drawing but they offered a limitation for my purposes. If you take your finger out of the screen and put it back, this solution will draw a line between the last click and your new click, making the whole drawing connected always. So I was trying to find a solution for that, and finally I got it!( sorry if sounds obvious, I am a beginner with graphics)
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;
}
}
I took the android sample for drawing with your finger and modified it a little to store every path instead of just the last one! Hope it helps someone!
Cheers.
I have experimented with several ways to render the accumulated points of the motion events.
In the end I had the best results by calculating the mid-points between two points and treating the points in the list as anchor points of quadratic Bezier curves (except the first and last point which are connected by simple lines to the next mid-point).
This gives a smooth curve without any corners. The drawn path will not touch the actual points in the list but go through every mid-point.
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);
}
If you want it 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;
}
}
In the activity just:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new DrawByFingerCanvas(this));
}
Result:
To erase all drawings just rotate the screen.
I had very similar problem. When you're calling onTouch method, you should also use method (inside onTouch(MotionEvent event))
event.getHistorySize();
and
something like that
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
}
Motion events with ACTION_MOVE may batch together multiple movement samples within a single object. The most current pointer coordinates are available using getX(int) and getY(int). Earlier coordinates within the batch are accessed using getHistoricalX(int, int) and getHistoricalY(int, int). Using them for building path makes it much smoother :
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);
Here is a good tutorial on this from Square : http://corner.squareup.com/2010/07/smooth-signatures.html
I had to make some modifications to this recently, and have now developed what I believe to be the best solution here because it does three things:
It allows you to draw different lines
It works with larger brush strokes and without using complicated cubic splines
It is faster than a lot of the solutions here because the canvas.drawPath() method is outside the for loop, so it is not called multiple times.
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;
}
}
This also works, just not quite as well
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;
}
}
It lets you draw lines reasonably well, the only problem is if you make the line thicker, which makes the lines drawn look a little odd, and really, I would recommend using the first one anyways.
You may have a lot more information available in your MotionEvent than you realize that can provide some data inbetween.
The example in your link ignores the historical touch points included within the event. See the 'Batching' section near the top of MotionEvent's documentation: http://developer.android.com/reference/android/view/MotionEvent.html Beyond that connecting the points with lines may not be a bad idea.
Here is a simplified solution that draws a line that follows your finger and is always straight:
https://stackoverflow.com/a/68076519/15463816
I had this issue, i was drawing a point instead of a line. You should create a path first to hold your line. call path.moveto on your first touch event only. Then on your canvas draw the path and then reset or rewind the path after your done (path.reset)...
Here is a simple method for smoothing points drawn with Path.lineTo
fun applySmoothing(smoothingIterations: Int) {
for (z in 1..smoothingIterations) {
for (i in graphPoints.indices) {
if (i > 0 && i < graphPoints.size-1) {
val previousPoint = graphPoints[i-1]
val currentPoint = graphPoints[i]
val nextPoint = graphPoints[i+1]
val midX = (previousPoint.x + currentPoint.x + nextPoint.x) / 3
val midY = (previousPoint.y + currentPoint.y + nextPoint.y) / 3
graphPoints[i].x = midX
graphPoints[i].y = midY
}
}
}
}

Categories