How to make canvas scrollable after scaling in Android Studio(Java) - java

I am developing an app in which I am showing canvas above pdf file so I can annotate it with free hand drawing to give remarks. My problem is that my canvas is zoomable but not scrollable. I am able to scale the canvas but unable to scroll it. Zoomed area is not scrollable please help how I can achieve it. My code is below
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.PorterDuffXfermode;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import com.github.barteksc.pdfviewer.PDFView;
import com.example.smartschool.ViewPDFFileActivity;
public class PaintView extends PDFView {
static Path drawPath;
//drawing and canvas paint
private static Paint drawPaint;
private static Paint canvasPaint;
//initial color
static int paintColor = 0xFFFF0000;
//stroke width
private static float STROKE_WIDTH = 5f;
//canvas
private Canvas drawCanvas;
//canvas bitmap
private Bitmap canvasBitmap;
//eraser mode
private static boolean erase=false;
boolean scaling=false;
private float scaleFactor = 1.f;
public ScaleGestureDetector detector = new ScaleGestureDetector(getContext(), new ScaleListener());
//constructor
public PaintView(Context context, AttributeSet attrs){
super(context, attrs);
setupDrawing();
setErase(erase);
}
private static void setupDrawing(){
drawPath = new Path();
drawPaint = new Paint();
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(STROKE_WIDTH);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
//*************************************** View assigned size ****************************************************
#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);
}
public static void setErase(boolean isErase){
erase=isErase;
drawPaint = new Paint();
if(erase) {
setupDrawing();
int srcColor= 0x00000000;
PorterDuff.Mode mode = PorterDuff.Mode.CLEAR;
PorterDuffColorFilter porterDuffColorFilter = new PorterDuffColorFilter(srcColor, mode);
drawPaint.setStrokeWidth(40);
drawPaint.setColorFilter(porterDuffColorFilter);
drawPaint.setColor(srcColor);
drawPaint.setXfermode(new PorterDuffXfermode(mode));
}
else {
setupDrawing();
}
}
//************************************ draw view *************************************************************
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawCanvas.drawColor(Color.TRANSPARENT);
canvas.save();
canvas.scale(scaleFactor, scaleFactor);
canvas.drawPath(drawPath, drawPaint);
canvas.drawBitmap(canvasBitmap,0,0, canvasPaint);
canvas.restore();
}
//*************************** respond to touch interaction **************************************************
#Override
public boolean onTouchEvent(MotionEvent event) {
detector.onTouchEvent(event);
float touchX = event.getX()/scaleFactor;
float touchY = event.getY()/scaleFactor;
//respond to down, move and up events
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if(ViewPDFFileActivity.isDrawEnabled)
drawPath.moveTo(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
if(ViewPDFFileActivity.isDrawEnabled) {
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.lineTo(touchX, touchY);
}
break;
case MotionEvent.ACTION_UP:
if(ViewPDFFileActivity.isDrawEnabled) {
drawPath.lineTo(touchX, touchY);
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
}
break;
default:
return false;
}
//redraw
invalidate();
return true;
}
//*********************************** return current alpha ***********************************************
public int getPaintAlpha(){
return Math.round((float)STROKE_WIDTH/255*100);
}
//************************************** set alpha ******************************************************
public void setPaintAlpha(int newAlpha){
STROKE_WIDTH=Math.round((float)newAlpha/100*255);
drawPaint.setStrokeWidth(newAlpha);
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
if(ViewPDFFileActivity.isDrawEnabled) return true;
scaleFactor *= detector.getScaleFactor();
scaleFactor = Math.max(1f, Math.min(scaleFactor, 5f));
invalidate();
ViewPDFFileActivity.pdfView.setMaxZoom(scaleFactor);
ViewPDFFileActivity.pdfView.zoomTo(scaleFactor);
ViewPDFFileActivity.pdfView.invalidate();
return true;
}
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
scaling=true;
return super.onScaleBegin(detector);
}
#Override
public void onScaleEnd(ScaleGestureDetector detector) {
super.onScaleEnd(detector);
scaling=false;
}
}
}

Related

I am making an app and I require to use the interface Parcelable but facing issues and not sure how to implement it

I am new to coding and to java and android. I am making an app that has multiple canvas views (where a person can draw lines with their finger) in multiple fragments (requested to be like this). I have managed to get the fragments to work and to draw lines on each canvas. Next, I want to be able to save the lines drawn by the user when the user goes to a different fragment or closes the app and when the user comes back to a fragment, the drawing for that fragment will still be there, and can continue editing it by adding more lines or clear it by hitting Clear, the same should happen for other fragments that should have their respective drawings.
I was previously told about the interface Parcelable and have made some steps to implement it but I am finding it hard to understand what I am doing and having trouble reading in an ArrayList. The error is stating that it is expecting ClassLoader but being provided with an ArrayList (I hope in the right place), and that's if I make the getter static but that creates an error: Non-static field 'paths' cannot be referenced from a static context in the getter in the PaintView class (this controls the drawing). I get a similar static context message for the readArrayList method in the FingerPath class. Once this is figured out, I will need to find out how to bring the relevant, saved drawing back to the canvas when the fragment is opened which I am not sure how, I think when the Bundle isn't equal to null but not sure.
I'll show the code below (right now it is on a test app)
FingerPath class
import android.graphics.Path;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
public class FingerPath implements Parcelable {
public int color;
public boolean emboss;
public boolean blur;
public int strokeWidth;
public Path path;
public FingerPath(int color, boolean emboss, boolean blur, int strokeWidth, Path path) {
this.color = color;
this.emboss = emboss;
this.blur = blur;
this.strokeWidth = strokeWidth;
this.path = path;
}
protected FingerPath(Parcel in) {
color = in.readInt();
emboss = in.readByte() != 0;
blur = in.readByte() != 0;
strokeWidth = in.readInt();
//line below causing me issues
path = in.readArrayList(PaintView.getPaths());
}
public static final Creator<FingerPath> CREATOR = new Creator<FingerPath>() {
#Override
public FingerPath createFromParcel(Parcel in) {
return new FingerPath(in);
}
#Override
public FingerPath[] newArray(int size) {
return new FingerPath[size];
}
};
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeInt(color);
parcel.writeByte((byte) (emboss ? 1 : 0));
parcel.writeByte((byte) (blur ? 1 : 0));
parcel.writeInt(strokeWidth);
//parcel.writeParcelableArray(PaintView.getPaths(), 0);
}
}
PaintView
Controls what happens when the user starts drawing
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.EmbossMaskFilter;
import android.graphics.MaskFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
public class PaintView extends View {
public static int BRUSH_SIZE = 10;
public static final int DEFAULT_COLOR = Color.WHITE;
public static int DEFAULT_BG_COLOR = Color.GRAY;
private static final float TOUCH_TOLERANCE = 4;
private float mX, mY;
private Path mPath;
private Paint mPaint;
private ArrayList<FingerPath> paths = new ArrayList<>();
private int currentColor;
private int backgroundColor = DEFAULT_BG_COLOR;
private int strokeWidth;
private boolean emboss;
private boolean blur;
private MaskFilter mEmboss;
private MaskFilter mBlur;
private Bitmap mBitmap;
private Canvas mCanvas;
private Paint mBitmapPaint = new Paint(Paint.DITHER_FLAG);
public PaintView(Context context) {
this(context, null);
}
public PaintView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(DEFAULT_COLOR);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setXfermode(null);
mPaint.setAlpha(0xff);
mEmboss = new EmbossMaskFilter(new float[] {1, 1, 1}, 0.4f, 6, 3.5f);
mBlur = new BlurMaskFilter(5, BlurMaskFilter.Blur.NORMAL);
}
//the getter in question
public ArrayList getPaths() {
return paths;
}
public void init(DisplayMetrics metrics) {
int height = metrics.heightPixels;
int width = metrics.widthPixels;
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
currentColor = DEFAULT_COLOR;
strokeWidth = BRUSH_SIZE;
}
public void normal() {
emboss = false;
blur = false;
}
public void clear() {
backgroundColor = DEFAULT_BG_COLOR;
paths.clear();
normal();
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
canvas.save();
mCanvas.drawColor(backgroundColor);
for (FingerPath fp: paths) {
mPaint.setColor(fp.color);
mPaint.setStrokeWidth(fp.strokeWidth);
mPaint.setMaskFilter(null);
if (fp.emboss)
mPaint.setMaskFilter(mEmboss);
else if (fp.blur)
mPaint.setMaskFilter(mBlur);
mCanvas.drawPath(fp.path, mPaint);
}
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
canvas.restore();
}
private void touchStart(float x, float y) {
mPath = new Path();
FingerPath fp = new FingerPath(currentColor, emboss, blur, strokeWidth, mPath);
paths.add(fp);
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touchMove(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 touchUp() {
mPath.lineTo(mX, mY);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN :
touchStart(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE :
touchMove(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP :
touchUp();
invalidate();
break;
}
return true;
}
}
Here's an example of a fragment
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
public class LineLHwFragment extends Fragment {
private PaintView paintView;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_line_l_hw, container, false);
paintView = v.findViewById(R.id.lineLPaintView);
DisplayMetrics metrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
paintView.init(metrics);
setHasOptionsMenu(true);
return v;
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.main, menu);
super.onCreateOptionsMenu(menu, inflater);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.normal:
paintView.normal();
return true;
case R.id.clear:
paintView.clear();
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList("line test", paintView.getPaths());
}
}

Erase canvas bitmap with alpha rubber

The intention is that the rubber/eraser interacted by user touch take two or more passes over to completely erase the image.
The current code is erasing the bitmap with only touch and I'm working on this direction with no success, any recommendations is also welcome to help
MainActivity.class
import android.os.Bundle;
public class MainActivity extends androidx.appcompat.app.AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new Rachet(MainActivity.this));
}
}
Rachet.class
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
public class Rachet extends androidx.appcompat.widget.AppCompatImageView {
private float mX, mY;
private Canvas canvas;
private Bitmap bitmap;
private Paint bitmapPaint = new Paint(Paint.DITHER_FLAG);
private Path rubberPath = new Path();
private Paint rubberPaint;
public Rachet(Context context) {
super(context);
init();
}
public Rachet(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public Rachet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void init() {
rubberPaint = new Paint();
rubberPaint.setAntiAlias(true);
rubberPaint.setDither(true);
rubberPaint.setColor(0x0000000);
rubberPaint.setStyle(Paint.Style.STROKE);
rubberPaint.setStrokeJoin(Paint.Join.BEVEL);
rubberPaint.setStrokeCap(Paint.Cap.ROUND);
rubberPaint.setStrokeWidth(100);
rubberPaint.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.CLEAR
));
}
#Override
protected void onSizeChanged(int width, int height, int oldw, int oldh) {
super.onSizeChanged(width, height, oldw, oldh);
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.foreground), width, height, true));
bitmapDrawable.setBounds(new Rect(0, 0, width, height));
bitmapDrawable.draw(canvas);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bitmap, 0, 0, bitmapPaint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
Float x = event.getX();
Float y = event.getY();
if (event.getAction() == MotionEvent.ACTION_DOWN) {
rubberPath.reset();
rubberPath.moveTo(x, y);
mX = x;
mY = y;
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
rubberPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
if (event.getAction() == MotionEvent.ACTION_MOVE || event.getAction() == MotionEvent.ACTION_UP) {
rubberPath.lineTo(mX, mY);
canvas.drawPath(rubberPath, rubberPaint);
rubberPath.reset();
rubberPath.moveTo(mX, mY);
}
invalidate();
return true;
}
}
This is an example of a expected result

I cant draw on my Canvas by onTouch method.Why dont work?

This is my first question here! I hope that you can help me!
I'm explain:
I'm trying to develop an app like a paint in android studio to work with Paint,Canvas,and this class...
I have a class call Lienzo.java; This is the code of my class:
package com.example.pedro.paint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.graphics.Path;
/**
* Created by Pedro on 26/02/2018.
*/
public class Lienzo extends View {
//PATH TO DRAW THE LINES
private Path drawPath;
//PAINT DRAWING AND PAINT CANVAS
private Paint drawPaint,canvasPaint;
//COLOR INITIAL
private int paintColor=0xFFFF0000;
//CANVAS
private Canvas drawCanvas;
//CANVAS TO SAVE
private Bitmap canvasBitmap;
public Lienzo(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
setupDrawing();
}
private void setupDrawing(){
//CONFIGURATION AREA TO DRAW
drawPath = new Path();
drawPaint=new Paint();
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(20);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint= new Paint(Paint.DITHER_FLAG);
}
//ASIGN SIZE TO VIEW
#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);
}
//PAINT THE VIEW.CALL BY ONTHOUCHEVENT
#Override
protected void onDraw(Canvas canvas){
canvas.drawBitmap(canvasBitmap,0,0,canvasPaint);
canvas.drawPath(drawPath,drawPaint);
}
//REGISTER USER TOUCH
public boolean OnTouchEvent(MotionEvent event)
{
float touchX =event.getX();
float touchY=event.getY();
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
drawPath.moveTo(touchX,touchY);
break;
case MotionEvent.ACTION_MOVE:
drawPath.lineTo(touchX,touchY);
break;
case MotionEvent.ACTION_UP:
drawPath.lineTo(touchX,touchY);
drawCanvas.drawPath(drawPath,drawPaint);
drawPath.reset();
break;
default:
return false;
}
//REPAINT
invalidate();
return true;
}
}
I have no error but I try to paint but do nothing.
This is a copy of an example.The original https://www.youtube.com/watch?v=GAr_agEokr8 works but my code,its equal and dont work.
Anybody knows why?
Thanks and regards!
OnTouchEvent change to onTouchEvent and add #Override

Draw a circle in Android Canvas when previous circle was selected at predefined positions

This is my first time using Canvas in Android.
I am creating an app that would display circles at certain positions on the screen one at a time (positions are selected randomly). New circle should be drawn after the previous one was selected/touched, and the previous one should disappear.
I have some ideas about it: to keep an arraylist of Point objects(each object contains x,y coordinate of the centre of the circle) and randomly select one each time the circle is drawn on the screen. So first I am populating an array of points. I also know how to randomly select the element from arraylist.
My biggest confustion is how to connect onDraw and onTouchEvent methods with each other? I know I should check if the circle was selected and only then draw a new circle at the randomly selected position, but I m not sure how to make a call for onDraw() method from the onTouchEvent...
Could you please help with this issue?
My code is below:
package com.example.researcher.heatmap;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
/**
* TODO: document your custom view class.
*/
public class MyView extends View {
Paint paint;
ArrayList<Point> points = new ArrayList<>();
public MyView(Context context) {
super(context);
init();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
// Load attributes
paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(5);
paint.setStyle(Paint.Style.STROKE);
populateArrayList();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setStyle(Paint.Style.STROKE);
canvas.drawColor(Color.WHITE);
int i=1; // should be random, will randomize later
for(Point p: points) {
p.x = points.get(i).x;
p.y = points.get(i).y;
canvas.drawCircle(p.x, p.y, 50, paint);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
int i=1;
for(Point p: points) {
Canvas canvas = new Canvas();
p.x = points.get(i).x;
p.y = points.get(i).y;
canvas.drawCircle(p.x, p.y, 50, paint);
}
postInvalidate();
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
break;
}
}
postInvalidate();
return true;
}
public void populateArrayList(){
points.clear();
points.add(new Point(120, 120));
points.add(new Point(150, 320));
points.add(new Point(280, 200));
}
}
Thank you Chris for your help! I really appreciate it.
Here is my solution if someone will need it for reference
package com.example.researcher.heatmap;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* TODO: document your custom view class.
*/
public class MyView extends View {
Paint paint;
ArrayList<Point> points = new ArrayList<>();
private int pointsPos = 0; //Which point we will be drawing
public float x;
public float y;
public int radius = 150;
public MyView(Context context) {
super(context);
x = this.getX();
y = this.getY();
init();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
x = this.getX();
y = this.getY();
init();
}
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
x = this.getX();
y = this.getY();
init();
}
private void init() {
// Load attributes
paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(5);
paint.setStyle(Paint.Style.STROKE);
populateArrayList();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setStyle(Paint.Style.STROKE);
canvas.drawColor(Color.WHITE);
canvas.drawCircle(points.get(pointsPos).x, points.get(pointsPos).y, radius, paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
//Check if the point press is within the circle
if(contains(event, points.get(pointsPos))){
Random r = new Random(System.nanoTime());
pointsPos = r.nextInt(points.size());; //between 0 and points.length
postInvalidate();
}
case MotionEvent.ACTION_CANCEL: {
break;
}
}
postInvalidate();
return true;
}
private boolean contains(MotionEvent event, Point point) {
float xTouch = event.getX();
float yTouch = event.getY();
if ((xTouch - point.x) * (xTouch - point.x) + (yTouch - point.y) * (yTouch - point.y) <= radius * radius) {
return true;
}
else {
return false;
}
}
public void populateArrayList(){
points.clear();
points.add(new Point(220, 1020));
points.add(new Point(550, 320));
points.add(new Point(780, 500));
}
}
Class var
private int state = 0 //0 normal, 1 new circle
private int pointPos = 0; //Which point we will be drawing
onDraw was overwriting the x/y of all your other points
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setStyle(Paint.Style.STROKE);
canvas.drawColor(Color.WHITE);
if(state == 1){
pointPos = random(); //between 0 and points.length
state = 0;
}
canvas.drawCircle(points.get(pointsPos).x, points.get(pointsPos).y, 50, paint);
}
onTouchEvent: Drawing should only be done on the ondraw, use flags/states to keep track of what you should be doing on the next draw call
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//Though these days this is usually done on the up event
//Check if the point press is within the circle
if(contains(event, points.get(pointPos))){
state = 1;
postInvalidate();
}
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
break;
}
}
postInvalidate();
return true;
}

Android Graphics - clear canvas while dragging bitmap

I've got one bitmap on a canvas (backgroundBitmap) that I want to remain unchanged and another smaller bitmap (draggableBitmap) that I want the user to be able to drag above backgroundBitmap. (And I do mean "above" as in z-axis).
My thinking is that I just redraw the background with each ACTION_MOVE. When I do this with a solid color, it works perfectly. When I redraw the backgroundBitmap instead of the color, the backgroundBitmap remains visible but the draggableBitmap just repeats itself along the dragged path. Why is the solid color working to "clear" the image and a bitmap won't?
EDIT:
Many thanks to Steve K for helping out with this. I've updated the code below to reflect the progress so far. The only issue at this point is that the image goes completely black after dragging begins. Does this have something to do with the background bitmap being set to mutable? I was getting a mutability error without it.
package com.example.drawing;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class DrawingView extends View {
// drawing and canvas paint
private Paint canvasPaint;
// canvas
private Canvas drawCanvas;
// canvas bitmap
private Bitmap backgroundBitmap, draggableBitmap;
public DrawingView(Context context, AttributeSet attrs) {
super(context, attrs);
setupDrawing();
}
private void setupDrawing() {
canvasPaint = new Paint();
canvasPaint.setColor(Color.TRANSPARENT);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
BitmapFactory.Options opt1 = new BitmapFactory.Options();
opt1.inMutable = true;
opt1.inSampleSize = 4;
BitmapFactory.Options opt2 = new BitmapFactory.Options();
opt2.inMutable = true;
backgroundBitmap = BitmapFactory.decodeFile("/storage/emulated/0/DCIM/Camera/20140901_223343.jpg", opt1);
draggableBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.draggable_image, opt2);
drawCanvas = new Canvas(backgroundBitmap);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(backgroundBitmap, 0, 0, null);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Nothing here
break;
case MotionEvent.ACTION_MOVE:
drawCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
drawCanvas.drawBitmap(backgroundBitmap, 0, 0, null);
drawCanvas.drawBitmap(draggableBitmap, touchX, touchY, null);
break;
case MotionEvent.ACTION_UP:
// Nothing here
break;
default:
return false;
}
invalidate();
return true;
}
}
The color has no defined boundaries, so it 'clears' the canvas by writing to every pixel. Your floating bitmap doesn't - it only redraws in the place where it is. The whole canvas needs to be redrawn, first cleared with a color, then drawn over.
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Nothing here
break;
case MotionEvent.ACTION_MOVE:
drawCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
drawCanvas.drawBitmap(backgroundBitmap, 0, 0, null);
drawCanvas.drawBitmap(draggableBitmap, touchX, touchY, null);
break;
case MotionEvent.ACTION_UP:
// Nothing here
break;
default:
return false;
}
invalidate();
return true;
}
Yes, when you create the canvas, it must have a mutable bitmap to draw into. When you tried to draw backgroundBitmap to the canvas which had been initialized with backgroundBitmap as its target, it drew the backgroundBitmap, but the important thing to understand is that since it's your target drawing surface, what backgroundBitmap contains is literally the picture that you see (the sum total of everything drawn to the canvas), so when you draw it again, nothing happens. By initializing the canvas with backgroundBitmap, everything you drew to the canvas CHANGED backgroundBitmap. You need a clean surface to draw into that's not one of your sprites.
I ended up adding a third bitmap. While I confess I don't entirely understand how this works, I did remember reading something about the necessity of a bitmap to which all other drawings were applied in a Canvas.
drawCanvas is constructed with canvasBitmap and then drawCanvas is used to redraw the bgBitmap and draggableBitmap. I'm assuming my error in the original code was that the canvasBitmap was constructed using the bitmap I was trying to draw over it?
package com.example.drawing;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class DrawingView extends View {
// drawing and canvas paint
private Paint canvasPaint;
// canvas
private Canvas drawCanvas;
// canvas bitmap
private Bitmap canvasBitmap, draggableBitmap, bgBitmap;
public DrawingView(Context context, AttributeSet attrs) {
super(context, attrs);
setupDrawing();
}
private void setupDrawing() {
canvasPaint = new Paint();
canvasPaint.setColor(Color.TRANSPARENT);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
BitmapFactory.Options opt1 = new BitmapFactory.Options();
opt1.inMutable = true;
opt1.inSampleSize = 4;
BitmapFactory.Options opt2 = new BitmapFactory.Options();
opt2.inMutable = true;
bgBitmap = BitmapFactory.decodeFile("/storage/emulated/0/DCIM/Camera/20140901_223343.jpg", opt1);
draggableBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.draggable_image, opt2);
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
drawCanvas = new Canvas(canvasBitmap);
drawCanvas.drawBitmap(bgBitmap, 0, 0, null);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(canvasBitmap, 0, 0, null);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Nothing here
break;
case MotionEvent.ACTION_MOVE:
drawCanvas.drawBitmap(bgBitmap, 0, 0, null);
drawCanvas.drawBitmap(draggableBitmap, touchX, touchY, null);
break;
case MotionEvent.ACTION_UP:
// Nothing here
break;
default:
return false;
}
invalidate();
return true;
}
}

Categories