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.
Related
How to draw a circler path by swiping, it's draw with edges.
I wanted to draw path with smooth edges.
this is my code.
this.mPaint = new Paint();
this.mPaint.setStyle(Paint.Style.STROKE);
this.mPaint.setStrokeWidth(getResources().getDimensionPixelSize(R.dimen._5sdp));
this.mPaint.setStrokeCap(Paint.Cap.ROUND);
this.mPaint.setStrokeJoin(Paint.Join.ROUND);
this.mPaint.setColor(Color.BLUE);
this.mPaint.setAntiAlias(true);
this.mPaint.setDither(true);
this code is in OnTouchListener
public boolean onTouch(View view, MotionEvent motionEvent) {
int action = motionEvent.getAction();
float x = motionEvent.getX();
float y = motionEvent.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
mPath.reset();
mPath.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onT:-> x " + x + " y-> " + y);
mPath.lineTo(x, y);
view.invalidate();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mPaths.add(new CurrentDraw(mPath, mPaint));
mPath.reset();
view.invalidate();
// updateLocalSS();
return false;
}
return true;
}
this code is in ondraw() method
if (mPaths != null) {
for (int i = 0; i < mPaths.size(); i++) {
canvas.drawPath(mPaths.get(i).path, mPaths.get(i).paint);
}
}
if (mPath != null) {
canvas.drawPath(mPath, mPaint);
}
Please just try this way may help you
RectF area;
private Paint bgPaint;
private Paint initpaint() {
int bColor = Color.WHITE;
if (!isInEditMode()) {
bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
} else {
bgPaint = new Paint();
}
bgPaint.setColor(bColor);
bgPaint.setStyle(Paint.Style.STROKE);
bgPaint.setStrokeWidth(sizeBg);
}
Public void initArea(){
float drawPadding = (size / 2);
float width = getWidth();
float height = getHeight();
float left = drawPadding;
float top = drawPadding;
float right = width - drawPadding;
float bottom = height - drawPadding;
area = new RectF(left, top, right, bottom);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Path path = new Path();
path.addCircle(area.centerX(), area.centerY(), getRadius(), Path.Direction.CCW);
canvas.drawPath(path, bgPaint);
}
private float getRadius() {
if (area != null) {
return (area.width() / 2);
} else {
return 0;
}
}
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.
I created an app to draw and wanted to implement the functions undo / redo, I tried various methods found surfing but none of them work, can someone help me?
Here is my code:
Variables
'public class MainDrawingView extends View {
public MainDrawingView(Context context, AttributeSet attrs) {
super(context, attrs);
setupDrawing();
}
float TOUCH_TOLERANCE = 4;
float mX, mY;
//drawing path
private Path drawPath, drawX, drawY;
//drawing and canvas paint
private Paint drawPaint;
private Paint canvasPaint;
private View canvasback;
private Paint bccanvas;
//initial color
private int paintColor = 0xFF000000;
//canvas
private Canvas drawCanvas;
//canvas bitmap
private Bitmap canvasBitmap, trans;
private ArrayList<Path> paths = new ArrayList<Path>();
private ArrayList<Path> undonePaths = new ArrayList<Path>();'
SetupDrawing
'private void setupDrawing() {
drawPath = new Path();
drawPaint = new Paint();
bccanvas = new Paint();
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(20);
drawPaint.setStyle(Paint.Style.FILL_AND_STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}'
onSizeChanged
'#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
drawCanvas = new Canvas(canvasBitmap);
}'
Touch Event
'private void touch_start(float x, float y) {
undonePaths.clear();
drawPath.reset();
drawPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touch_move(float x, float y) {
float dx, dy;
dx = Math.abs(x - mX);
dy = Math.abs(y - mY);
if ((dx >= TOUCH_TOLERANCE) || (dy >= TOUCH_TOLERANCE)) {
drawPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
drawPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
drawPath.lineTo(mX, mY);
// commit the path to our offscreen
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
drawPath.moveTo(mX, mY);
mX = x;
mY = y;
}
}
private void touch_up() {
drawPath.reset();
}'
onTouchEvent
'public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
drawPath.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
drawPath.lineTo(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
drawPath.reset();
touch_up();
invalidate();
break;
}
return true;
}'
onDraw
'#SuppressLint("NewApi")
#Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(p, drawPaint);
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
}'
onClickUndo
'public void onClickUndo () {
if (paths.size()>0){
undonePaths.add(paths.remove(paths.size()-1));
invalidate();
undonePaths.clear();
}
else
{
}
//toast the user
}'
onClickRedo
'public void onClickRedo (){
if (undonePaths.size()>0)
{
paths.add(undonePaths.remove(undonePaths.size()-1));
invalidate();
}
else
{
}
//toast the user
}'
TL;DR.
I created a similar application in Java Swing a couple of months ago. The way I implemented the 2 methods was I had created a Stack of shapes which was added to as the user was adding shapes to the whiteboard and then push/popped from this to undo/redo (storing the shape which was removed in a temp variable)
protected Shape removed; //shape object of an undo-ed object
protected Stack<Shape> shapes = new Stack<Shape>(); //stack to store the shapes
public void undo()
{
if (!shapes.empty()) //only undo if there's shapes on the board
{
removed = shapes.pop(); //pop removes a shape and returns it into 'removed' object
repaint();
}
}
public void redo()
{
if (removed != null) //only redo if something has been undone
{
shapes.push(removed); //push adds the object back onto the stack
removed = null;
repaint();
}
}
I hope this helps!
A good approach is to create an interface called something like "Undoable". It has at least 3 overrrides: do(), undo() and redo(). Then do all your work in classes that implement Undoable, and keep a stack of these around = the undo history.
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.
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.