In my project, users can view PDF documents and I want them to have the option to annotate each page in the document via onDraw and Paint. I would like the document to open for viewing first with the option to turn the drawing/painting function on and off via a button like the WhatsApp paint function.
I have a PaintView class extending my PDFView but when I open a PDF, the onDraw is called straight away, allowing me to draw over the PDF but not being able to then turn off this function and swipe between pages. When I move initDraw to a button I get a null pointer in my PaintView class.
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Canvas.drawColor(int)' on a null object reference
at com.example.dissertation814.pdfViewer.PaintView.onDraw(PaintView.java:60)
My viewer activity:
public class PdfViewerActivity extends AppCompatActivity {
private boolean isDrawInit = false;
private PaintView paintView;
//firebase auth
private FirebaseAuth mAuth;
//variables
public String currentUserAccount;
public String teacherAccountNav = "Teacher";
PDFView pdfView;
#RequiresApi(api = Build.VERSION_CODES.KITKAT)
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pdf_viewer);
//PDFView to display PDFs
pdfView = findViewById(R.id.pdfView);
//use best quality
pdfView.useBestQuality(true);
//get data from intent
Intent i = this.getIntent();
Uri uri = i.getParcelableExtra("FILE_PATH_URI");
//Get the pdf file
assert uri != null;
File file = new File(Objects.requireNonNull(uri.getPath()));
if(file.canRead()){
//load pdf file
pdfView.fromFile(file)
.defaultPage(0)
.enableSwipe(true)
.swipeHorizontal(true)
.pageSnap(true)
.onDrawAll(new OnDrawListener() {
#Override
public void onLayerDrawn(Canvas canvas, float pageWidth, float pageHeight, int displayedPage) {
Bitmap bitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
Paint paint = new Paint();
paint.setColor(Color.BLACK);
canvas.drawBitmap(bitmap, 0,0, paint);
}
})
.onLoad(new OnLoadCompleteListener() {
#Override
public void loadComplete(int nbPages) {
Toast.makeText(PdfViewerActivity.this, "No. of pages: " + nbPages, Toast.LENGTH_SHORT).show();
}
}).load();
}
}
#RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
public void onInitDrawClick(View view){
}
#RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
#Override
protected void onResume() {
super.onResume();
if(!isDrawInit){
initDraw();
isDrawInit = true;
}
}
//initialise paint view
#RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
private void initDraw(){
paintView = findViewById(R.id.paintView);
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
paintView.init(metrics);
}
//user finger path from paint view class
static class FingerPath{
int colour;
int strokeWidth;
Path path;
FingerPath(int colour, int strokeWidth, Path path){
this.colour = colour;
this.strokeWidth = strokeWidth;
this.path = path;
}
}
My PaintView class:
public class PaintView extends PDFView {
private Paint mPaint;
private Canvas mCanvas;
private Bitmap mBitmap;
private ArrayList<PdfViewerActivity.FingerPath> paths = new ArrayList<>();
private Paint mBitmapPaint = new Paint(Paint.DITHER_FLAG);
private static final float TOUCH_TOLERANCE = 4;
private Path mPath;
private float mX;
private float mY;
public int brushColour = Color.BLACK;
public int brushSize = 10;
public PaintView(Context context, AttributeSet set) {
super(context, set);
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.setXfermode(null);
mPaint.setAlpha(0xff);
}
public void init (DisplayMetrics metrics){
int height = (int) (metrics.heightPixels);
int width = metrics.widthPixels;
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
mCanvas.drawColor(Color.TRANSPARENT);
for(PdfViewerActivity.FingerPath fp : paths){
mPaint.setColor(fp.colour);
mPaint.setStrokeWidth(fp.strokeWidth);
mPaint.setMaskFilter(null);
mCanvas.drawPath(fp.path, mPaint);
}
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
canvas.restore();
}
#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();
break;
}
return true;
}
private void touchUp(){
mPath.lineTo(mX,mY);
}
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 touchStart(float x, float y){
mPath = new Path();
PdfViewerActivity.FingerPath fp = new PdfViewerActivity.FingerPath(brushColour, brushSize, mPath);
paths.add(fp);
mPath.reset();
mPath.moveTo(x,y);
mX = x;
mY = y;
}
public void clear(){
paths.clear();
invalidate();
}
My XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/black"
tools:context=".pdfViewer.PdfViewerActivity">
<Button
android:id="#+id/initDraw"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="init"
app:layout_constraintBottom_toTopOf="#+id/relativeLayout"
app:layout_constraintEnd_toStartOf="#+id/homeButton"
app:layout_constraintStart_toEndOf="#+id/backButton"
app:layout_constraintTop_toTopOf="parent"
android:onClick="onInitDrawClick"/>
<ImageButton
android:id="#+id/backButton"
android:layout_width="60dp"
android:layout_height="60dp"
android:background="#color/black"
android:contentDescription="#string/back_button"
android:onClick="onBackClicked"
android:src="#drawable/backward_arrow"
app:layout_constraintBottom_toTopOf="#+id/relativeLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.112"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.8" />
<ImageButton
android:id="#+id/homeButton"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginEnd="64dp"
android:layout_marginRight="64dp"
android:background="#color/black"
android:onClick="onHomeClicked"
android:src="#drawable/ic_home_black_24dp"
app:layout_constraintBottom_toTopOf="#+id/relativeLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="#+id/backButton"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.8" />
<RelativeLayout
android:id="#+id/relativeLayout"
android:layout_width="match_parent"
android:layout_height="800dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.919"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<com.github.barteksc.pdfviewer.PDFView
android:id="#+id/pdfView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.example.dissertation814.pdfViewer.PaintView
android:id="#+id/paintView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/transparent"/>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
onDraw is a method, thus it's not something you initialize. I don't think you should try to disable the method either. Although you can override it, which puts you in control of what is drawn.
Consider another solution to you problem. Instead of enabling or disabling the onDraw method, you control which view gets to process the user input.
Solution:
When returning true in the method onTouchEvent, you state that no views above this one -- in the view hierarcy -- needs to process this input.
What you instead should do is to check whether or not the drawing feature should be on. If the drawing feature is disabled, you return false. Else if the drawing feature is enabled, you process the input, then return true.
Example:
#Override
public boolean onTouchEvent(MotionEvent event) {
// Check whether or not the drawing feature is disabled
if (drawingIsEnabled == false) {
// Let parent views process this input
return false;
}
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();
break;
}
// Prevent parent views from processing this input
return true;
}
When returning false, the input is passed further up the view hierarchy so that the parent view will get the chance to process the input. (This will allow you to swipe pages)
If you however return true, you are preventing parent views from processing the input. (This will prevent the parent view from swiping pages while you're drawing, which would be quite annoying)
Hope this helps!
Related
I am implementing a code in which i am trying to have a foreground and a background image, i want to erase the foreground image and if i made a mistake i would be able to undo it.
for background i am using an image view and foreground is bitmap on canvas
i have worked this far,
it is erasing the foreground(i.e. the canvas) but undo redo is not working
code is:
public class MainActivity extends AppCompatActivity {
private Bitmap DrawBitmap;
private Canvas mCanvas;
private Path mPath;
private Paint DrawBitmapPaint;
RelativeLayout Rl;
CustomView View;
DrawView drawView;
private Button undo, redo;
private ArrayList<Path> paths = new ArrayList<Path>();
private ArrayList<Path> undonePaths = new ArrayList<Path>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.loadActivity();
}
private Paint mPaint;
public class CustomView extends View {
public CustomView(Context c) {
super(c);
create_image();
setLayerType(android.view.View.LAYER_TYPE_SOFTWARE, mPaint);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// mCanvas.drawColor(Color.BLUE);
super.onSizeChanged(w, h, oldw, oldh);
}
#Override
protected void onDraw(Canvas canvas) {
for (Path p : paths){
canvas.drawPath(p, mPaint);
}
setDrawingCacheEnabled(true);
canvas.drawBitmap(DrawBitmap, 0, 0, DrawBitmapPaint);
canvas.drawPath(mPath, mPaint);
canvas.drawRect(mY, 0, mY, 0, DrawBitmapPaint);
}
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);
mCanvas.drawPath(mPath, mPaint);
mPath = new Path();
paths.add(mPath);
}
#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();
// performClick();
invalidate();
break;
}
return true;
}
public void clear() {
create_image();
// Added later..
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
// mCanvas.drawColor(Color.BLUE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(40);
this.invalidate();
}
public void onClickUndo() {
if (paths.size() > 0) {
undonePaths.add(paths.remove(paths.size() - 1));
invalidate();
} else {
}
//toast the user
}
public void onClickRedo() {
if (undonePaths.size() > 0) {
paths.add(undonePaths.remove(undonePaths.size() - 1));
invalidate();
} else {
}
//toast the user
}
}
public void loadActivity() {
undo = (Button) findViewById(R.id.button1);
redo = (Button) findViewById(R.id.button2);
drawView = new DrawView(this);
View = new CustomView(this);
Rl = (RelativeLayout) findViewById(R.id.linearLayout2);
Rl.addView(View);
Bitmap bitmap = BitmapFactory.decodeResource(MainActivity.this.getResources(), R.drawable.sample);
bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
// mCanvas.drawColor(Color.BLUE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(40);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mPaint.setStrokeWidth(40);
mPaint.setColor(Color.BLUE);
mPaint.setStrokeWidth(40);
undo.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
View.onClickUndo();
}
});
redo.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
View.onClickRedo();
}
});
}
public void create_image() {
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int screenWidth = displaymetrics.widthPixels;
int screenHeight = displaymetrics.heightPixels;
DrawBitmap = Bitmap.createBitmap(screenWidth, screenHeight,
Bitmap.Config.ARGB_4444);
mCanvas = new Canvas(DrawBitmap);
Bitmap bitmap = BitmapFactory.decodeResource(MainActivity.this.getResources(), R.drawable.sample);
bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
mCanvas.drawBitmap(bitmap, 0, 0, null);
mPath = new Path();
DrawBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
paths.add(mPath);
}
}
and my layout is:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="com.approduction.drawing.MainActivity">
<LinearLayout
android:id="#+id/linearLayout1"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Undo" />
<Button
android:id="#+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Redo" />
</LinearLayout>
<RelativeLayout
android:id="#+id/linearLayout2"
android:layout_below="#id/linearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#mipmap/ic_launcher"/>
</RelativeLayout>
any suggestions would be valuable
Thanks in advance..
The problem is you have 2 canvas here: canvas and mCanvas.
Your undo/redo works only for canvas and not mCanvas. That's the issue you have. You need to either remove mCanvas in onTouchUp() or alter the code there to implement undo/redo functionality.
Refer my answer here for more info: https://stackoverflow.com/a/38220061/4747587
I want to be able to edit photos, allowing a user to put annotations on a photo they have captured. I am fine with Portrait photos, but Landscape photos are causing me major problems. The problem is that the drawing area ends up being the entire layout and I just want the photo boundaries to be editable (drawable). How do I do this? Portrait is ok since it fills up the layout.
Here is what it looks like:
And here is the code XML def:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical"
tools:context=".MainActivity" >
<!-- Top Buttons -->
<LinearLayout
android:id="#+id/drawToolbar"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_gravity="center"
android:orientation="horizontal" >
<ImageButton
android:id="#+id/new_btn"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:src="#drawable/new_pic" />
<!--
<ImageButton
android:id="#+id/existing_btn"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:src="#drawable/new_pic" />
-->
<ImageButton
android:id="#+id/draw_btn"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:src="#drawable/brush" />
<ImageButton
android:id="#+id/erase_btn"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:src="#drawable/eraser" />
</LinearLayout>
<!-- Custom View -->
<RelativeLayout
android:id="#+id/imgStack"
android:layout_weight="1"
android:layout_width="fill_parent"
android:layout_height="0dp">
<ImageView
android:id="#+id/imgBackground"
android:layout_alignParentTop="true"
android:layout_marginBottom="3dp"
android:layout_height="fill_parent"
android:layout_width="fill_parent"/>
<com.invocore.fastfield.views.DrawingView
android:id="#+id/drawing"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginBottom="3dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginTop="3dp"
android:background="#android:color/transparent" />
</RelativeLayout>
<!-- Color Palette -->
... removed buttons to save space...
</LinearLayout>
The Activity code looks like this (have removed stuff to save space):
public class PhotoCaptureEditActivity extends Activity implements OnClickListener {
//custom drawing view
private DrawingView drawView;
private ImageView imgBackground;
private RelativeLayout imgStack;
// toolbars
private LinearLayout drawToolbar, drawColors;
//buttons
private ImageButton currPaint, drawBtn, eraseBtn, newBtn; //, saveBtn, existingBtn;
//sizes
private float smallBrush, mediumBrush, largeBrush;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_photo_capture_edit);
ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
// reset all pending values
GlobalState.getInstance().pendingFieldKey = null;
GlobalState.getInstance().pendingFieldValue = null;
//get drawing view
drawView = (DrawingView)findViewById(R.id.drawing);
drawView.setVisibility(View.VISIBLE);
imgBackground = (ImageView)findViewById(R.id.imgBackground);
imgStack = (RelativeLayout)findViewById(R.id.imgStack);
// toolbars
drawToolbar = (LinearLayout)findViewById(R.id.drawToolbar);
drawColors = (LinearLayout)findViewById(R.id.drawColors);
//get the palette and first color button
LinearLayout paintLayout = (LinearLayout)findViewById(R.id.paint_colors);
currPaint = (ImageButton)paintLayout.getChildAt(0);
currPaint.setImageDrawable(getResources().getDrawable(R.drawable.paint_pressed));
[removed button and other code to save space]
enableDrawing();
// Must be a PNG for editing
Bitmap image = Utilities.loadImageByPath(filename);
if (image != null) {
imgBackground.setImageBitmap(image);
}
}
//user clicked paint
public void paintClicked(View view){
//use chosen color
setModeText("Draw Mode");
//set erase false
drawView.setErase(false);
drawView.setBrushSize(drawView.getLastBrushSize());
if(view!=currPaint){
ImageButton imgView = (ImageButton)view;
String color = view.getTag().toString();
drawView.setColor(color);
//update ui
imgView.setImageDrawable(getResources().getDrawable(R.drawable.paint_pressed));
currPaint.setImageDrawable(getResources().getDrawable(R.drawable.paint));
currPaint=(ImageButton)view;
}
}
[Lots of other code...]
}
And the DrawingView class:
public class DrawingView extends View {
//drawing path
private Path drawPath;
//drawing and canvas paint
private Paint drawPaint, canvasPaint;
//initial color
private int paintColor = 0xFF660000;
//canvas
private Canvas drawCanvas;
//canvas bitmap
private Bitmap canvasBitmap;
//brush sizes
private float brushSize, lastBrushSize;
//erase flag
private boolean erase=false;
public DrawingView(Context context, AttributeSet attrs){
super(context, attrs);
setupDrawing();
}
//setup drawing
private void setupDrawing(){
//prepare for drawing and setup paint stroke properties
brushSize = getResources().getInteger(mycompany.R.integer.medium_size);
lastBrushSize = brushSize;
drawPath = new Path();
drawPaint = new Paint();
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(brushSize);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
//size assigned to view
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if(canvasBitmap == null) {
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444);
}
drawCanvas = new Canvas(canvasBitmap);
drawCanvas.drawColor(Color.TRANSPARENT);
}
//draw the view - will be called after touch event
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
canvas.drawPath(drawPath, drawPaint);
}
//register user touches as drawing action
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
//respond to down, move and up events
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
drawPath.moveTo(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
if(erase == true) {
drawPath.lineTo(touchX, touchY);
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
drawPath.moveTo(touchX, touchY);
}
else {
drawPath.lineTo(touchX, touchY);
}
break;
case MotionEvent.ACTION_UP:
drawPath.lineTo(touchX, touchY);
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
break;
default:
return false;
}
//redraw
invalidate();
return true;
}
//update color
public void setColor(String newColor){
invalidate();
paintColor = Color.parseColor(newColor);
drawPaint.setColor(paintColor);
// set into draw mode if color was clicked
setBrushSize(getLastBrushSize());
setErase(false);
}
//set brush size
public void setBrushSize(float newSize){
float pixelAmount = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
newSize, getResources().getDisplayMetrics());
brushSize=pixelAmount;
drawPaint.setStrokeWidth(brushSize);
}
//get and set last brush size
public void setLastBrushSize(float lastSize){
lastBrushSize=lastSize;
}
public float getLastBrushSize(){
return lastBrushSize;
}
//set erase true or false
public void setErase(boolean isErase){
erase=isErase;
if(erase) drawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
else drawPaint.setXfermode(null);
}
public void startExisting(String path) {
File file = new File(path);
if(file.exists() == true) {
Bitmap captured = BitmapFactory.decodeFile(file.toString());
canvasBitmap = captured.copy(Bitmap.Config.ARGB_8888, true);
drawCanvas = new Canvas(canvasBitmap);
drawCanvas.drawColor(Color.TRANSPARENT);
invalidate();
}
}
public void startNew() {
if(drawCanvas != null) {
drawCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
invalidate();
}
// setting a solid background color
//drawCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
//drawCanvas.drawColor(Color.GREEN, PorterDuff.Mode.CLEAR);
}
public Bitmap getImage() {
return canvasBitmap;
}
}
Thanks!
Uri currImageURI = intent.getData();
String s= getRealPathFromURI(currImageURI);
File file = new File(s);
if (file.exists()) {
Drawable drawImg = Drawable.createFromPath(file.getAbsolutePath());
drawView.setBackground(drawImg);
}
else {
// file does not exist
}
I need to implement the pencil shades from 9b to 9h with the list showing the shades on pencil button Click() and after clicking the list item, the pencil should change to its new shade. Also need the eraser implementation on erase button click to erase the path drawn by pencil.
This is the code for drawing with a fixed size pen on a canvas without a button click. And the clearCanvas() method is called on button click that clears the whole Canvas.
Any help would be appreciated.
public class MainActivity extends AppCompatActivity {
private CanvasView customCanvas;
private Toolbar toolbar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
customCanvas = (CanvasView) findViewById(R.id.custom_canvas);
toolbar = (Toolbar) findViewById(R.id.actionBar);
setSupportActionBar(toolbar);
assert getSupportActionBar()!=null;
getSupportActionBar().setHomeButtonEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
public void onClick(View view){
customCanvas.clearCanvas();
}
}
public class CanvasView extends View{
public int width;
public int height;
private Bitmap mBitmap;
private Canvas mCanvas;
private Path mPath;
Context context;
private Paint mPaint, mErase;
private float mX, mY;
private static final float TOLERANCE = 5;
private boolean erase=false;
public CanvasView(Context c, AttributeSet attrs) {
super(c, attrs);
// we set a new Path
mPath = new Path();
// and we set a new Paint with the desired attributes
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(4f);
}
// override onSizeChanged
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// your Canvas will draw onto the defined Bitmap
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
// override onDraw
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// draw the mPath with the mPaint on the canvas when onDraw
canvas.drawPath(mPath, mPaint);
}
// when ACTION_DOWN start touch according to the x,y values
private void startTouch(float x, float y) {
mPath.moveTo(x, y);
mX = x;
mY = y;
}
// when ACTION_MOVE move touch according to the x,y values
private void moveTouch(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOLERANCE || dy >= TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
// Clear Canvas
public void clearCanvas(){
mPath.reset();
invalidate();
}
// when ACTION_UP stop touch
private void upTouch() {
mPath.lineTo(mX, mY);
}
//override the onTouchEvent
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startTouch(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
moveTouch(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
upTouch();
invalidate();
break;
}
return true;
}
}
XML file:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="#+id/actionBar"
layout="#layout/action_bar" />
<View
android:layout_below="#+id/actionBar"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#2084AF" />
<com.admin.mainactivity.CanvasView
android:layout_below="#+id/actionBar"
android:id="#+id/custom_canvas"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
I want to design a Scratch Card-like effect in android. Is It possible to do this with a Framelayout or RelativeLayout by stacking the coupon at the back of the bitmap and setting the colour of CustomCanvas to gray or something. I've tried doing this but I wasn't able to get the stacked image from the back. The background was just turning white and nothing else. I then tried to the COLOR.TRANSPARENT and there was no Scratch at all. How can I do this?
Below is my CustomCanvas Class extending the View class:
public class CanvasView extends View {
public int width,height;
private Bitmap bitmap;
private Canvas canvas;
Context context;
private Path mPath;
private Paint paint;
private float mX, mY;
private static final float TOLERANCE = 5;
public CanvasView(Context context, AttributeSet attrs) {
super(context, attrs);
mPath = new Path();
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeWidth(100f);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
bitmap = Bitmap.createBitmap(w,h, Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bitmap,0,0,paint);
canvas.drawPath(mPath, paint);
}
private void startTouch(float x, float y){
mPath.moveTo(x,y);
mX = x;
mY = y;
}
private void moveTouch(float x, float y){
float dx = Math.abs(x-mX);
float dy = Math.abs(y-mY);
if(dx >= TOLERANCE || dy >= TOLERANCE){
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
public void clearCanvas(){
mPath.reset();
invalidate();
}
private void upTouch(){
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:
startTouch(x,y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
moveTouch(x,y);
invalidate();
break;
case MotionEvent.ACTION_UP:
upTouch();
float r = calculatingPercentage(bitmap.getWidth(),bitmap.getHeight());
if(calculatingPercentage(bitmap.getWidth(),bitmap.getHeight()) >= 50.00){
Toast.makeText(getContext(),"Done 50%",Toast.LENGTH_SHORT).show();
}
invalidate();
break;
}
return true;
}
//FUNCTION I WAS USING TO CALCULATE THE SCRATCHED AREA'S PERCENTAGE
private float calculatingPercentage(int width,int height){
int[] xArray = new int[100];
int[] yArray = new int[100];
float percentTransparent;
Random r = new Random();
for(int i = 0; i<100;i++){
xArray[i] = r.nextInt(width - 10) +10;
}
for(int i = 0; i<100;i++){
yArray[i] = r.nextInt(height - 10) +10;
}
int pixelCount = 0;
for(int i = 0; i<100;i++){
int x = xArray[i];
int y = yArray[i];
int color = Color.WHITE;
int black = Color.BLACK;
if(bitmap.getPixel(xArray[i],yArray[i]) == Color.WHITE){
pixelCount++;
}
}
percentTransparent = (pixelCount/100);
return percentTransparent;
}
}
ActivityB which is calling the Canvasview class:
public class ActivityB extends Activity {
private CanvasView customCanvas;
private Button bt;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_a);
customCanvas = (CanvasView) findViewById(R.id.signature_canvas);
bt = (Button) findViewById(R.id.buttonAgain);
bt.setVisibility(View.VISIBLE);
bt.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
clearCanvas();
}
});
}
public void clearCanvas(){
customCanvas.clearCanvas();
}
}
And here is my fragment_a.xml layout:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/firstFrame"
android:background="#F07818"
tools:context="com.example.dremer.fragmentspractice.FragmentA">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="40dp"
android:src="#drawable/benz"/>
<com.example.dremer.fragmentspractice.CanvasView
android:id="#+id/signature_canvas"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textColor="#FFFFFF"
android:background="#FFFFFF"/>
<Button
android:id="#+id/buttonAgain"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Scratch Again"
android:layout_gravity="bottom|center"
android:paddingBottom="20dp"/>
</FrameLayout>
Can you guys please help me with this?
Thanks!
I followed the fingerpaint code and some other code to create an app that allows you to draw onto the screen, with a background image in place to draw over top of. In addition, I'd like to make an eraser tool that erases just the pen, not the background image. I think I have to use two canvases for this, but couldn't figure out how it would work with Canvas' onDraw.
Here's my code. Right now pen drawing works, and eraser drawing erases but it erases the background image. I only want it to erase the pen.
If you could give me hints on what to do I would really appreciate it.
===
//MainActivity.java
public class MainActivity extends Activity implements OnClickListener {
public MyView myView;
boolean penOn;
boolean eraserOn;
Bitmap bMap;
private Canvas mCanvas;
private Canvas myForegroundCanvas;
private Path mPath;
private Bitmap mBitmap;
private Paint mPaint, mBitmapPaint;
private ArrayList<DrawAction> paths = new ArrayList<DrawAction>();
private ArrayList<DrawAction> undonePaths = new ArrayList<DrawAction>();
int selectedColor=Color.RED;
RelativeLayout container;
private int width;
private int height;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View mainView = getLayoutInflater().inflate(R.layout.activity_main,
null);
myView = new MyView(this);
container = (RelativeLayout) mainView.findViewById(R.id.container);
container.addView(myView);
setContentView(mainView);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(16);
Button pen = (Button) mainView.findViewById(R.id.buttonpen);
pen.setOnClickListener(this);
Button eraser = (Button) mainView.findViewById(R.id.buttonerase);
eraser.setOnClickListener(this);
pen.bringToFront();
eraser.bringToFront();
}
public class MyView extends View {
public MyView(Context c) {
super(c);
if (android.os.Build.VERSION.SDK_INT >= 11) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
DisplayMetrics displaymetrics = new DisplayMetrics();
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
MainActivity.this.width = size.x;
MainActivity.this.height = size.y;
bMap = getBitmapFromAsset(MainActivity.this, "google.png");
Bitmap background = Bitmap.createScaledBitmap(bMap,
MainActivity.this.width, MainActivity.this.height, false);
mBitmap = background.copy(Bitmap.Config.ARGB_8888, true);
mCanvas = new Canvas(mBitmap);
myForegroundCanvas = new Canvas(mBitmap);
mPath = new Path();
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
Paint textPaint = new Paint();
textPaint.setTextSize(40);
final Iterator<DrawAction> i = paths.iterator();
while (i.hasNext()) {
final DrawAction d = i.next();
if (d.type.equals("pen")) {
mPaint.setColor(d.color);
mPaint.setXfermode(null);// clear the draw
canvas.drawPath(d.path, mPaint);
// myForegroundCanvas.drawPath(d.path, mPaint);
} else if (d.type.equals("eraser")) {
// mPaint.setAlpha(0xFF);//transperent color
mPaint.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.CLEAR));// clear the draw
canvas.drawPath(d.path, mPaint);
// myForegroundCanvas.drawPath(d.path, mPaint);
}
}
mPaint.setColor(selectedColor);
canvas.drawPath(mPath, mPaint);
// canvas.drawCanvas(myForegroundCanvas, 0, 0, null);
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
private void touch_start(float x, float y) {
undonePaths.clear();
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);
DrawAction d = new DrawAction(mPath, Color.RED);
paths.add(d);
mPath = new Path();
}
private void touch_start_eraser(float x, float y) {
undonePaths.clear();
mPaint.setColor(Color.WHITE);
selectedColor=Color.WHITE;
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touch_move_eraser(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_eraser() {
mPath.lineTo(mX, mY);
DrawAction d = new DrawAction(mPath, true);
paths.add(d);
mPath = new Path();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if ( !penOn && eraserOn) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start_eraser(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move_eraser(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up_eraser();
invalidate();
break;
}
return true;
} else {
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;
}
}
}
public Bitmap getBitmapFromAsset(Context context, String strName) {
AssetManager assetManager = context.getAssets();
InputStream istr;
Bitmap bitmap = null;
try {
istr = assetManager.open(strName);
bitmap = BitmapFactory.decodeStream(istr);
} catch (IOException e) {
return null;
}
return bitmap;
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.buttonpen:
penOn=true;
eraserOn=false;
break;
case R.id.buttonerase:
penOn=false;
eraserOn=true;
break;
}
}
}
===
//DrawAction.java, a helper class for holding the path and what type of drawing to do (pen or erase)
public class DrawAction {
public String type;
public Path path;
public int color;
public DrawAction(final Path p, final int color) {
this.type="pen";
this.path=p;
this.color=color;
}
public DrawAction(final Path p, final boolean isEraser) {
this.type="eraser";
this.path=p;
}
}
====
//activity_main.xml the layout code
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.doodle5.MainActivity"
tools:ignore="MergeRootFrame" >
<Button
android:id="#+id/buttonpen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="25dp"
android:layout_marginRight="25dp"
android:layout_marginTop="50dp"
android:text="Pen" />
<Button
android:id="#+id/buttonerase"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="25dp"
android:layout_marginTop="50dp"
android:text="Erase" />
</RelativeLayout>
I figured it out. I ended up placing the background image as a background drawable on the container, and not drawing it onto the canvas.
In onDraw I commented out this line:
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
In onCreate I added:
BitmapDrawable ob = new BitmapDrawable(backgroundBitmap);
container.setBackgroundDrawable(ob);