Draw 2 lines on separate canvases with same points Android - java

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.

Related

How to connect two buttons with a line in Android?

I want to connect two opposite radiobuttons or normal Buttons with a line in Canvas like this: image
I have the FingerLine Class to draw the red line and in the layout I have 3 columns (RecyclerView,FingerLine,RecyclerView). I want to develop an application to match two image with lines.
Currently what I do is draw in the middle but I would like the drawing line to go from one button to another I don't know how to do it.
public class FingerLine extends View {
private final Paint mPaint;
private float startX;
private float startY;
private float endX;
private float endY;
float joinX, joinY = 0;
ArrayList<Line> lines = new ArrayList<Line>();
class Line {
float startX, startY, stopX, stopY;
public Line(float startX, float startY, float stopX, float stopY) {
this.startX = startX;
this.startY = startY;
this.stopX = stopX;
this.stopY = stopY;
}
public Line(float startX, float startY) { // for convenience
this(startX, startY, startX, startY);
}
}
public FingerLine(Context context) {
this(context, null);
}
public FingerLine(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Style.STROKE);
mPaint.setColor(Color.RED);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (Line l : lines) {
canvas.drawLine(l.startX, l.startY, l.stopX, l.stopY, mPaint);
}
}
#Override
public boolean onTouchEvent(#NonNull MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (joinX <= 0 || joinY <= 0) {
lines.add(new Line(event.getX(), event.getY()));
}else{
lines.add(new Line(event.getX(), event.getY()));
}
break;
case MotionEvent.ACTION_MOVE:
if(lines.size() > 0) {
Line current = lines.get(lines.size() - 1);
current.stopX = event.getX();
current.stopY = event.getY();
invalidate();
break;
}
case (MotionEvent.ACTION_CANCEL):
break;
case (MotionEvent.ACTION_OUTSIDE):
break;
case MotionEvent.ACTION_UP:
if(lines.size() > 0) {
Line current = lines.get(lines.size() - 1);
current.stopX = event.getX();
current.stopY = event.getY();
joinX = event.getX();
joinY = event.getY();
invalidate();
break;
}
}
return true;
}
}
I think for first you have to find an image of a line.
then you have to create 6(or how many you want) imageview s with the image of line and then make them invisible with android:visibility = "invisible".
then you have to find two checked radiobuttons in your activity.java and then set align_right and align_left to the checked radiobuttons for your imageview in activity.java and then make it visible.

android drawing becomes leggy when draw with brushes and colors

This is the first time I am working with the Canvas class in Android
what I want is to draw with different colors and brushes on the canvas with using Path class. I don't want to use Bitmap, because I need to apply proper undo redo functionality on It. What I am doing is just draw on the canvas, but in onDraw function, I draw whole path using for loop, but If user select different colors and brushes to draw, here issue occurs. Then I will have to store all of them in particular list of Paint, same I am doing right now. Is there another good approach for this ?
Code I use is :
DrawView class
public class DrawView extends View implements OnTouchListener {
private Canvas mCanvas;
private Path mPath;
private Paint mPaint;
private ArrayList<Path> paths = new ArrayList<Path>();
private ArrayList<Paint> paints = new ArrayList<Paint>();
private ArrayList<Path> undonePaths = new ArrayList<Path>();
private Context context;
private int initialcolor = 0xff000000;
private StyleEnum styleEnum;
private Style mStyle;
private int strokeWidth = 6;
private int effect;
public DrawView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
mPaint = new Paint();
// add paint strokes
addPaintStrokes(mPaint);
mCanvas = new Canvas();
mPath = new Path();
paths.add(mPath);
paints.add(mPaint);
rect = new Rect();
mRectPaint = new Paint(Paint.DITHER_FLAG);
mRectPaint.setColor(Color.WHITE);
mRectPaint.setStyle(Paint.Style.FILL);
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
Here I use another ArrayList of type Paint to contain paint related items to maintain on every new created object
#Override
protected void onDraw(Canvas canvas) {
float cX = canvas.getWidth() / 2.0f;
float cY = canvas.getHeight() / (mScaleFactor * 10);
canvas.save();
canvas.translate(mPosX, mPosY);
canvas.scale(mScaleFactor, mScaleFactor, cX, cY);
RectF rec = new RectF(0, 0, canvas.getWidth(), canvas.getHeight());
canvas.drawRect(rec, mRectPaint);
if( paths.size() > 0 ){
for(int i = 0 ; i < paths.size() ; i++){
canvas.drawPath(paths.get(i), paints.get(i));
}
}
rect = canvas.getClipBounds();
canvas.restore();
}
private void touch_start(float x, float y) {
mStyle.strokeStart(x, 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 >= 4 || dy >= 4) {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mStyle.stroke(mCanvas, x, y);
mX = x;
mY = y;
}
}
private void touch_up() {
mPath.lineTo(mX, mY);
mCanvas.drawPath(mPath, mPaint);
mPath = new Path();
paths.add(mPath);
mPaint = new Paint();
addPaintStrokes(mPaint);
paints.add(mPaint);
}
public void onClickUndo () {
if (paths.size()>0) {
undonePaths.add(paths.remove(paths.size()-1));
invalidate();
}
}
public void onClickRedo (){
if (undonePaths.size()>0) {
paths.add(undonePaths.remove(undonePaths.size()-1));
invalidate();
}
}
#Override
public boolean onTouch(View arg0, MotionEvent event) {
mScaleDetector.onTouchEvent(event);
if (isDrawingEnabled) {
// Drawing Enable
float x = event.getX();
float y = event.getY();
x = x / mScaleFactor + rect.left;
y = y / mScaleFactor + rect.top;
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;
}
} else{
// Dragging Enable
final int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
float x = event.getX();
float y = event.getY();
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = event.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE: {
final int pointerIndex = event.findPointerIndex(mActivePointerId);
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
// Only move if the ScaleGestureDetector isn't processing
if (!mScaleDetector.isInProgress()) {
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
mPosX += dx;
mPosY += dy;
invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
break;
}
case MotionEvent.ACTION_UP: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastTouchX = event.getX(newPointerIndex);
mLastTouchY = event.getY(newPointerIndex);
mActivePointerId = event.getPointerId(newPointerIndex);
}
}
break;
}
}
return true;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
if(!isDrawingEnabled){
mScaleFactor *= detector.getScaleFactor();
// Don't let the object get too small or too large.
mScaleFactor = Math.max(1.0f, Math.min(mScaleFactor, 5.0f));
invalidate();
}
return true;
}
}
private void addPaintStrokes(Paint p){
p.setAntiAlias(true);
p.setDither(true);
p.setColor(initialcolor);
setBrushStyle(effect);
p.setStyle(Paint.Style.STROKE);
p.setStrokeJoin(Paint.Join.ROUND);
p.setStrokeCap(Paint.Cap.ROUND);
p.setStrokeWidth(getStrokeWidth());
}
public void setBrushStyle(int mBrushStyle) {
Log.e("mBrushStyle : ", "" + mBrushStyle);
this.effect = mBrushStyle;
switch (mBrushStyle) {
case 0: {
mPaint.setMaskFilter(null);
break;
}
case 1: {
MaskFilter mEmboss = new EmbossMaskFilter(new float[] { 1, 1, 1 },
0.4f, 6, 3.5f);
mPaint.setMaskFilter(mEmboss);
break;
}
case 2: {
int brushSize = getStrokeWidth();
if (brushSize > 0) {
MaskFilter mBlur = new BlurMaskFilter(brushSize,
BlurMaskFilter.Blur.NORMAL);
mPaint.setMaskFilter(mBlur);
} else {
MaskFilter mBlur = new BlurMaskFilter(1,
BlurMaskFilter.Blur.NORMAL);
mPaint.setMaskFilter(mBlur);
}
break;
}
}
}
Try moving the Path to undonePaths for saving the path while removing path.
undo logic:
1. move the current path to undonePaths(temp arraylist)
2. remove the current path for the paths arraylist.
redo logic:
1. add current undonePaths to paths arraylist
2. remove the moved path from undonePaths
this works gd for me,try the below code snippet.
public void onClickUndo() {
if (paths.size() > 0) {
undonePaths.add(paths.remove(paths.size() - 1));
invalidate();
} else {
}
}
public void onClickRedo() {
if (undonePaths.size() > 0) {
paths.add(undonePaths.remove(undonePaths.size() - 1));
invalidate();
} else {
}
}
Enjoy coding :) :) :)
The problem is that with every path added, you are drawing cumulatively more to the screen with each call to onDraw.
If you can limit the number of undos allowed, then you can do it with a Bitmap. Create your bitmap like this:
#Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
super.onSizeChanged(width, h, oldWidth, oldHeight);
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
In your touch_up method, once your paths ArrayList reaches a certain size (the number of levels of undo you will support), take its first Path and draw it to mCanvas instead (right in this method so it is done only once) and remove it from the ArrayList (and its associated Paint from the other ArrayList). In your onDraw method, draw your Bitmap first.
If you must have unlimited undo, I think you could still do this with a Bitmap. Just keep the same array lists you have now, but only draw to mBitmap in your touch methods. Draw only the mBitmap in your onDraw method. When you need to undo, you can clear mBitmap, delete your last path, and redraw all the remaining paths to mBitmap one time.

Android vertical scroll

I have class, which draw many crosses in vertical way. I want learn how do this, so I try it on this simple example:
public class Draw extends View {
Paint paint = new Paint();
public Draw(Context context) {
super(context);
paint.setColor(Color.BLACK);
}
#Override
public void onDraw(Canvas canvas) {
int x = 1;
for(int i = 0; i < 100; i++){
if (i%2 == 0) x = 2;
else x=1;
canvas.drawLine(0, 0 + x*i*20, 20, 20 + x*i*20, paint);
canvas.drawLine(20, 0 + x*i*20, 0, 20 + x*i*20, paint);
}
}
}
and I want scroll it.
I implemented onTouchEvent with MotionEvent.ACTION_MOVE:
public class DrawActivity extends Activity {
private Draw dv;
private float xDistance, yDistance, lastX, lastY;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dv = new Draw(this);
setContentView(dv);
}
public boolean onTouchEvent(MotionEvent event) {
int action = MotionEventCompat.getActionMasked(event);
switch (action) {
case (MotionEvent.ACTION_MOVE):
final float curX = event.getX();
final float curY = event.getY();
xDistance += Math.abs(curX - lastX);
yDistance += Math.abs(curY - lastY);
lastX = curX;
lastY = curY;
if (xDistance > yDistance)
return false;
default:
return super.onTouchEvent(event);
}
}
}
but it not working. How Can I scroll a canvas?
Since you are extending View you should update it's scrollX value (or store itself) and translate the content of the drawing by its value.

Android: How to get the string of points from the line being drawn?

This is actually our Thesis, we are required to use the Ramer-Douglas-Peucker Algorithm in simplifying lines, can anyboy help me how to implement this in an Android App.
I just want to know how to get the string of points from the line I've drawn and simplify the line by reducing the total no. points based on the given code below?
This is the main class.
public class SketchTimeNewActivity extends GraphicsView implements ColorOption.OnColorChangedListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MyView(this));
myPaint = new Paint();
myPaint.setAntiAlias(true);
myPaint.setDither(true);
myPaint.setColor(Color.CYAN);
myPaint.setStyle(Paint.Style.STROKE);
myPaint.setStrokeJoin(Paint.Join.ROUND);
myPaint.setStrokeCap(Paint.Cap.ROUND);
myPaint.setStrokeWidth(12);
}
private Paint myPaint;
public void colorChanged(int color) {
myPaint.setColor(color);
}
public class MyView extends View {
private static final float MINP = 0.25f;
private static final float MAXP = 0.75f;
private Bitmap mBitmap;
private Canvas mCanvas;
private Path mPath;
private Paint mBitmapPaint;
public MyView(Context c) {
super(c);
mPath = new Path();
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
}
#Override
protected void onSizeChanged(int width, int height, int oldwidth, int oldheight) {
super.onSizeChanged(width, height, oldwidth, oldheight);
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(color.black);
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
canvas.drawPath(mPath, myPaint);
}
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, myPaint);
// kill this so we don't double draw
mPath.reset();
}
#Override
public boolean onTouchEvent(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;
}
}
private static final int COLOR_MENU_ID = Menu.FIRST;
private static final int EXISTING_MENU_ID = Menu.FIRST + 2;
private static final int ENHANCED_MENU_ID = Menu.FIRST + 3;
private static final int ERASE_MENU_ID = Menu.FIRST + 1;
#Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, COLOR_MENU_ID, 0, "Color").setShortcut('1', 'c');
menu.add(0, EXISTING_MENU_ID, 0, "Enhanced").setShortcut('2', 's');
menu.add(0, ENHANCED_MENU_ID, 0, "Existing").setShortcut('3', 'z');
menu.add(0, ERASE_MENU_ID, 0, "Erase").setShortcut('4', 'z');
return true;
}
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
myPaint.setXfermode(null);
myPaint.setAlpha(0xFFAAAAAA);
When the EXISTING MENU is clicked, it will simplify the line being drawn and display a line that has lesser points or a line that is already simplified. I'm planning to create a new class for it but I don't know how to get the string of points from the line being drawn in the canvas.
switch (item.getItemId()) {
case COLOR_MENU_ID:
new ColorOption(this, this, myPaint.getColor()).show();
return true;
/** case EXISTING_MENU_ID:
return true;
case ENHANCED_MENU_ID:
return true;*/
case ERASE_MENU_ID:{
myPaint.setColor(color.black);
myPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
return true;
}
}
return super.onOptionsItemSelected(item);
}
}
Copy paste from my original comment for some context:
You are generating the points on the line from your onTouchEvent, so
in stead of trying to query the Canvas afterwards, why not simply keep
a list of these created points? You can add a point as you draw each
new line segment.
In terms of code, your most basic example will look something like this:
List<Point> mPoints = new ArrayList<Point>();
private void touch_up() {
// save this point on the line
mPoints.add(new Point(mX, mY);
// add point to path
mPath.lineTo(mX, mY);
// commit the path to our offscreen
mCanvas.drawPath(mPath, myPaint);
// kill this so we don't double draw
mPath.reset();
}
I'm assuming here that touch_up() is where you add each line segment's start or endpoint to the path.
//Edit: Upon reading your problem once more, I feel like you might be asking for all points on the line you've drawn - since your code snippet includes curves? My guess is that the only way to realise this is by evaluating every (x,y) using the different underlying mathematical equations and store the result for each point. In other words: by writing your own lineTo and quadTo functions.
For straight lines this is relatively trivial, but curves will increase the level of difficulty. You might want to take a look at the Path's source code, which internally delegates most of its work to a java.awt.geom.GeneralPath object.

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