I am beginner in Android and I have a rather simple question. I have created a custom view and then injected it into another layout through xml. I want to make the background of this custom view transparent.
xml injection of my custom view:
<com.sagar.utils.ConnectDotsView
android:id="#+id/connect_dots_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Here is the code of the custom View:
public class ConnectDotsView extends View {
private Bitmap mBitmap;
private Canvas mCanvas;
private Path mPath;
private Paint mPaint;
private static final int TOUCH_TOLERANCE_DP = 24;
private static final int BACKGROUND = 0xFFDDDDDD;
// Points to be connected.
private List<Point> mPoints = new ArrayList<>();
private int mLastPointIndex = 0;
private int mTouchTolerance;
private boolean isPathStarted = false;
CompleteListener completeListener;
public ConnectDotsView(Context context) {
super(context);
mCanvas = new Canvas();
mPath = new Path();
initPaint();
}
public interface CompleteListener {
void onCompleteListener();
}
public void setOnCompleteListener(CompleteListener listener) {
completeListener = listener;
}
public ConnectDotsView(Context context, AttributeSet attrs) {
super(context, attrs);
mCanvas = new Canvas();
mPath = new Path();
initPaint();
}
public ConnectDotsView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mCanvas = new Canvas();
mPath = new Path();
initPaint();
}
public void clear() {
mBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
mBitmap.eraseColor(BACKGROUND);
mCanvas.setBitmap(mBitmap);
invalidate();
}
#Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
super.onSizeChanged(width, height, oldWidth, oldHeight);
clear();
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(BACKGROUND);
canvas.drawBitmap(mBitmap, 0, 0, null);
canvas.drawPath(mPath, mPaint);
mPaint.setColor(Color.parseColor("#56CBF9"));
// TODO remove if you don't want points to be visible.
for (Point point : mPoints) {
canvas.drawPoint(point.x, point.y, mPaint);
mPaint.setColor(Color.BLACK);
}
}
#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(x, y);
invalidate();
break;
}
return true;
}
private void touch_start(float x, float y) {
if (checkPoint(x, y, mLastPointIndex)) {
mPath.reset();
// User starts from given point so path can be drawn.
isPathStarted = true;
} else {
// User starts move from point which does not belong to mPoints list
isPathStarted = false;
}
}
private void touch_move(float x, float y) {
if (isPathStarted) {
mPath.reset();
Point point = mPoints.get(mLastPointIndex);
mPath.moveTo(point.x, point.y);
if (checkPoint(x, y, mLastPointIndex + 1)) {
point = mPoints.get(mLastPointIndex + 1);
mPath.lineTo(point.x, point.y);
mCanvas.drawPath(mPath, mPaint);
mPath.reset();
++mLastPointIndex;
} else {
int positionIndex = mLastPointIndex + 1;
if (positionIndex >= mPoints.size()) {
completeListener.onCompleteListener();
} else {
mPath.lineTo(x, y);
}
}
}
}
private void touch_up(float x, float y) {
mPath.reset();
if (checkPoint(x, y, mLastPointIndex + 1) && isPathStarted) {
// Move finished at valid point so I draw whole line.
// That's the start point of current line segment.
Point point = mPoints.get(mLastPointIndex);
mPath.moveTo(point.x, point.y);
// And that's the end point.
point = mPoints.get(mLastPointIndex + 1);
mPath.lineTo(point.x, point.y);
mCanvas.drawPath(mPath, mPaint);
mPath.reset();
// Increment point index.
++mLastPointIndex;
isPathStarted = false;
}
}
/**
* Checks if user touch point with some tolerance
*/
private boolean checkPoint(float x, float y, int pointIndex) {
if (pointIndex >= mPoints.size()) {
// All dots already connected.
return false;
}
Point point = mPoints.get(pointIndex);
if (x > (point.x - mTouchTolerance) && x < (point.x + mTouchTolerance)) {
if (y > (point.y - mTouchTolerance) && y < (point.y + mTouchTolerance)) {
return true;
}
}
return false;
}
/**
* Sets up paint attributes.
*/
private void initPaint() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(12);
mTouchTolerance = dp2px(TOUCH_TOLERANCE_DP);
}
/**
* Converts dpi units to px
*
* #param dp
* #return
*/
private int dp2px(int dp) {
Resources r = getContext().getResources();
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());
return (int) px;
}
public void setPaint(Paint paint) {
this.mPaint = paint;
}
public Bitmap getBitmap() {
return mBitmap;
}
public List<Point> getPoints() {
return mPoints;
}
public void setPoints(List<Point> points) {
mLastPointIndex = 0;
this.mPoints = points;
}
}
I want to make the above custom View's background as transparent. How can I achieve that either through xml or code?
Thanks in advance.
In your Class ConnectDotsView change:
private static final int BACKGROUND = 0xFFDDDDDD;
to
private static final int BACKGROUND = Color.TRANSPARENT;
or
private static final int BACKGROUND = Color.parseColor("#00000000");
#00AABBCC = ARGB (00 is Alpha, AA is red, BB is green and CC is blue), 00 alpha is 0% and FF is 100%. This mean #00AABBCC will be transparent, #80AABBCC will be at 50% transparent and #FFAABBCC not transparent
Change alpha of your parent view using view.setAlpha(float opacity) where 0f is fully transparent view.
In your initPaint() method, call the following function:
setBackgroundColor(R.color.colorTransparent);
And in the values/colors.xml folder, set your colorTransparent with RGBA as 00:
<color name="colorTransparent">#00000000</color>
Alternatively, when you call your custom view in xml, use: android:background="#00000000".
This should solve your problem.
Edited:
just use setBackgroundColor(Color.TRANSPARENT);
Or setBackgroundColor(0x00000000);
50% opacity: setBackgroundColor(0x80000000);
To control the extent of transparency, use #Robillo's code, but modify this:
<color name="colorTransparent">#xy000000</color>
replace xy with your desired opacity. 00 will be 0% opaque, FF is 100% opaque (ie white colour)
Related
I want to implement eraser in my paint app. But the code
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
draws black line on canvas. If I am changing the background color the canvas draws black on it too.
I have also tried using setLayerType() but it draws white on any color background.
// In constructor
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Below is the code for my PaintView.
public class PaintView extends View {
private Bitmap bitmapBackground,bitmapView;
private int backgroundColor;
private int brushSize;
private int eraserSize;
private float mX,mY;
private Canvas canvas=null;
private final int TOUCH_TOLERANCE=4;
private int paintColor;
private int modeStatus;
/*
1 for brush
2 for eraser
*/
private ArrayList<Paint> paints = new ArrayList<>();
private ArrayList<Path> paths = new ArrayList<>();
private int historyPointer=0;
public PaintView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
initialise();
}
private void initialise() {
eraserSize=12;
brushSize=12;
backgroundColor= Color.WHITE;
paintColor = Color.BLACK;
modeStatus = 1;
paints.add(createPaint());
paths.add(new Path());
historyPointer++;
}
private float toPx(int brushSize) {
return brushSize*(getResources().getDisplayMetrics().density);
}
public void init(int width,int height) {
bitmapBackground=Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
bitmapView=Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
canvas=new Canvas(bitmapView);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(backgroundColor);
canvas.drawBitmap(bitmapBackground,0,0,null);
for (int i=0;i<historyPointer;i++) {
Path path = paths.get(i);
Paint paint = paints.get(i);
canvas.drawPath(path,paint);
}
}
private Paint createPaint() {
Paint paint = new Paint();
paint.setColor(paintColor);
paint.setAntiAlias(true);
paint.setDither(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);
if (modeStatus==1) {
paint.setXfermode(null);
paint.setShader(null);
paint.setMaskFilter(null);
paint.setStrokeWidth(toPx(brushSize));
}
else {
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
paint.setStrokeWidth(toPx(eraserSize));
}
return paint;
}
private Path createPath(float x,float y) {
Path path = new Path();
path.moveTo(x,y);
return path;
}
public void setBackgroundColor(int backgroundColor) {
this.backgroundColor=backgroundColor;
invalidate(); //Redraw
}
public void setBrushSize(int brushSize) {
this.brushSize=brushSize;
modeStatus=1;
}
public void setBrushColor(int color) {
paintColor=color;
}
public void setEraserSize(int eraserSize) {
this.eraserSize=eraserSize;
modeStatus=2;
}
public int getBrushSize() {
return this.brushSize;
}
public int getEraserSize() {
return this.eraserSize;
}
private void updateHistory(Path path) {
if (historyPointer==paths.size()) {
paths.add(path);
paints.add(createPaint());
historyPointer++;
}
else {
// For undo and redo
paths.set(historyPointer,path);
paints.set(historyPointer,createPaint());
historyPointer++;
for (int i=historyPointer,size=paths.size();i<size;i++) {
paths.remove(historyPointer);
paints.remove(historyPointer);
}
}
}
private Path getCurrentPath() {
return paths.get(historyPointer-1);
}
private Paint getCurrentPaint() {
return paints.get(historyPointer-1);
}
#SuppressLint("ClickableViewAccessibility")
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
// Case when finger touches the screen
case MotionEvent.ACTION_DOWN:
touchStart(x,y);
break;
// Case when finger moves on screen
case MotionEvent.ACTION_MOVE:
touchMove(x,y);
break;
// Case when finger is taken away from screen
case MotionEvent.ACTION_UP:
touchUp();
break;
default :
return false;
}
return true;
}
private void touchStart(float x, float y) {
mX=x;
mY=y;
updateHistory(createPath(x,y));
}
private void touchMove(float x, float y) {
float dx = Math.abs(x-mX);
float dy = Math.abs(y-mY);
Path path = getCurrentPath();
if (dx>=TOUCH_TOLERANCE || dy>=TOUCH_TOLERANCE) {
path.quadTo(x,y,(x+mX)/2,(y+mY)/2);
mX=x;
mY=y;
}
invalidate();;
}
private void touchUp() {
}
}
I got the answer after many research and the following worked.
Just save the canvas layer after setting the background color and at last restore to count.
The onDraw method is as follows -
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(backgroundColor);
canvas.drawBitmap(bitmapBackground,0,0,null);
int layerId = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG); // Line 1 added
for (int i=0;i<historyPointer;i++) {
Path path = paths.get(i);
Paint paint = paints.get(i);
canvas.drawPath(path,paint);
}
canvas.restoreToCount(layerId); // Line 2 added
}
I managed to create my own custom path drawing application and it is as follows
public class CanvasView extends View {
Context context;
HashMap<Integer,PathWrapper> locToPath=new HashMap<>();
ArrayList<PathWrapper> activePaths=new ArrayList<>();
CoMingleAndroidRuntime<Screenshare> screenRuntime;
boolean inited=false;
Integer myLocation;
public CanvasView(Context context,AttributeSet attr) {
super(context, attr);
setWillNotDraw(false);
this.context = context;
}
public void init(CoMingleAndroidRuntime<Screenshare> screenRuntime){
inited=true;
this.screenRuntime=screenRuntime;
this.myLocation=screenRuntime.getLocation();
addPath(myLocation);
invalidate();
}
public void addPath(int Location){
Paint mPaint=new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setAlpha(195);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeWidth(50f);
locToPath.put(Location, new PathWrapper(new Path(), mPaint, Location));
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for(PathWrapper path:activePaths){
canvas.drawPath(path.path, path.paint);
}
invalidate();
}
public void respondActionColorChanged(int R,int G,int B){
locToPath.get(myLocation).paint.setColor(Color.rgb(R, G, B));
}
public void respondActionColorChanged(int loc,int R,int G,int B){
locToPath.get(loc).paint.setColor(Color.rgb(R, G, B));
}
public void respondActionDown(final Integer loc, int xTouch,int yTouch){
activePaths.add(locToPath.get(loc));
locToPath.get(loc).path.moveTo(xTouch, yTouch);
locToPath.get(loc).lastPoint = new Point(xTouch, yTouch);
if(loc==myLocation){
screenRuntime.getRewriteMachine().addActionDown(xTouch, yTouch);
}
}
public void respondActionMove(final Integer loc,int xTouch,int yTouch){
float dx = Math.abs(xTouch - locToPath.get(loc).lastPoint.x);
float dy = Math.abs(yTouch - locToPath.get(loc).lastPoint.y);
if (dx >= 5 || dy >= 5) {
locToPath.get(loc).path.quadTo(locToPath.get(loc).lastPoint.x, locToPath.get(loc).lastPoint.y, (xTouch + locToPath.get(loc).lastPoint.x) / 2, (yTouch + locToPath.get(loc).lastPoint.y) / 2);
locToPath.get(loc).lastPoint = new Point(xTouch, yTouch);
if(loc==myLocation){
screenRuntime.getRewriteMachine().addActionMove(xTouch, yTouch);
}
}
}
public void respondActionUp(final Integer loc,int x,int y){
locToPath.get(loc).path.lineTo(locToPath.get(loc).lastPoint.x, locToPath.get(loc).lastPoint.y);
if(loc==myLocation){
screenRuntime.getRewriteMachine().addActionUp(x, y);
}
activePaths.remove(locToPath.get(loc));
locToPath.get(loc).path.reset();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(inited) {
int xTouch;
int yTouch;
xTouch = (int) event.getX(0);
yTouch = (int) event.getY(0);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
respondActionDown(myLocation,xTouch,yTouch);
break;
case MotionEvent.ACTION_MOVE:
respondActionMove(myLocation, xTouch,yTouch);
break;
case MotionEvent.ACTION_UP:
respondActionUp(myLocation, xTouch,yTouch);
break;
}
return true;
}
return false;
}
This code works perfectly for my app (Ignore the location stuff and the runtime and rewriteMachine stuff).
My question is, I would like to have parts of the path be colored differently, the ultimate goal is that I would like only the last few pixels of the path to be visible and the remainder should have an Alpha of 0, such that when the user draws, he only sees the last few pixels of the path which then slowly turns invisible. Is this possible? and if so how would I do it?
Thanks.
Instead of adding points to a path, create a list of paths, and every time add a new path to the list that has only a small chunk that starts at the end point of the previous path, and has only one other point (end-point). Then you can draw each path with a different color:
Paint mPaint=new Paint();
mPaint.setColor(Color.BLACK);
//rest of mPaint...
canvas.drawPath(path1, mPaint);
mPaint=new Paint();
mPaint.setColor(Color.BLUE);
//rest of mPaint...
canvas.drawPath(path2, mPaint);
Note that path1 is different from path2, and more importantly you create a new mPaint for each color. I'm not sure if it would work if you just would call mPaint.setColor(Color.BLUE) on the previously created and used paint.
I found this code on stack and it works well. However, there is an issue. While I'm able to set its background color, the color changes to black as soon as the clearSignature() function is called.
Why is that happening?
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.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* A simple view to capture a path traced onto the screen. Initially intended to
* be used to captures signatures.
*
* #author Andrew Crichton
* #version 0.1
*/
public class SignatureView extends View
{
private Path mPath;
private Paint mPaint;
private Paint bgPaint = new Paint(Color.TRANSPARENT);
private Bitmap mBitmap;
private Canvas mCanvas;
private float curX, curY;
private static final int TOUCH_TOLERANCE = 4;
private static final int STROKE_WIDTH = 4;
boolean modified = false;
public SignatureView(Context context)
{
super(context);
init();
}
public SignatureView(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
public SignatureView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
init();
}
private void init()
{
setFocusable(true);
mPath = new Path();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(STROKE_WIDTH);
}
public void setSigColor(int color)
{
mPaint.setColor(color);
}
public void setSigColor(int a, int red, int green, int blue)
{
mPaint.setARGB(a, red, green, blue);
}
public boolean clearSignature()
{
if (mBitmap != null)
createFakeMotionEvents();
if (mCanvas != null)
{
mCanvas.drawColor(Color.BLACK);
mCanvas.drawPaint(bgPaint);
mPath.reset();
invalidate();
}
else
{
return false;
}
return true;
}
public Bitmap getImage()
{
return this.mBitmap;
}
public void setImage(Bitmap bitmap)
{
this.mBitmap = bitmap;
this.invalidate();
}
public boolean hasChanged()
{
return modified;
}
#Override protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
{
int bitmapWidth = mBitmap != null ? mBitmap.getWidth() : 0;
int bitmapHeight = mBitmap != null ? mBitmap.getWidth() : 0;
if (bitmapWidth >= width && bitmapHeight >= height)
return;
if (bitmapWidth < width)
bitmapWidth = width;
if (bitmapHeight < height)
bitmapHeight = height;
Bitmap newBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
Canvas newCanvas = new Canvas();
newCanvas.setBitmap(newBitmap);
if (mBitmap != null)
newCanvas.drawBitmap(mBitmap, 0, 0, null);
mBitmap = newBitmap;
mCanvas = newCanvas;
}
private void createFakeMotionEvents()
{
MotionEvent downEvent = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis() + 100, MotionEvent.ACTION_DOWN,
1f, 1f, 0);
MotionEvent upEvent = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis() + 100, MotionEvent.ACTION_UP, 1f,
1f, 0);
onTouchEvent(downEvent);
onTouchEvent(upEvent);
}
#Override protected void onDraw(Canvas canvas)
{
modified = true;
canvas.drawColor(Color.RED);
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
canvas.drawPath(mPath, mPaint);
}
#Override public boolean onTouchEvent(MotionEvent event)
{
float x = event.getX();
float y = event.getY();
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
touchDown(x, y);
break;
case MotionEvent.ACTION_MOVE:
touchMove(x, y);
break;
case MotionEvent.ACTION_UP:
touchUp();
break;
}
invalidate();
return true;
}
/**
* ---------------------------------------------------------- Private
* methods ---------------------------------------------------------
*/
private void touchDown(float x, float y)
{
mPath.reset();
mPath.moveTo(x, y);
curX = x;
curY = y;
}
private void touchMove(float x, float y)
{
float dx = Math.abs(x - curX);
float dy = Math.abs(y - curY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE)
{
mPath.quadTo(curX, curY, (x + curX) / 2, (y + curY) / 2);
curX = x;
curY = y;
}
}
private void touchUp()
{
mPath.lineTo(curX, curY);
if (mCanvas == null)
{
mCanvas = new Canvas();
mCanvas.setBitmap(mBitmap);
}
mCanvas.drawPath(mPath, mPaint);
mPath.reset();
}
}
Well I have updated the original SignatureView code, now it supports a custom signature background color. This color is different from the view's background color!
setSigBackgroundColor()
I also made some other optimizations, use on your own risk as this is minimal tested!
Small list of optimizations:
Better bitmap recycling etc.
Recycling of MotionEvents
Added signature background color set method
Optimizations
Changed setImage method, although still not very safe to use!
New code:
import android.annotation.SuppressLint;
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.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* A simple view to capture a path traced onto the screen. Initially intended to
* be used to captures signatures.
*
* #author Andrew Crichton
* #version 0.1.1
*
* Modified by Rolf Smit
* -Recycle bitmaps
* -Recycle MotionEvents
* -Signature Background color changes
* -Optimizations
* -Changed setImage method, although still unsafe to use!
*/
public class SignatureView extends View {
private Path mPath;
private Paint mPaint;
private Bitmap mBitmap;
private Canvas mCanvas;
private int sigBackgroundColor = Color.TRANSPARENT;
private float curX, curY;
private static final int TOUCH_TOLERANCE = 4;
private static final int STROKE_WIDTH = 4;
boolean modified = false;
public SignatureView(Context context) {
super(context);
init();
}
public SignatureView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public SignatureView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
setFocusable(true);
mPath = new Path();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(STROKE_WIDTH);
}
public void setSigColor(int color) {
mPaint.setColor(color);
}
public void setSigColor(int alpha, int red, int green, int blue) {
mPaint.setARGB(alpha, red, green, blue);
}
public void setSigBackgroundColor(int color){
sigBackgroundColor = color;
}
public void setSigBackgroundColor(int alpha, int red, int green, int blue){
sigBackgroundColor = Color.argb(alpha, red, green, blue);
}
public boolean clearSignature() {
if (mBitmap != null) {
createFakeMotionEvents();
}
if (mCanvas != null) {
mCanvas.drawColor(sigBackgroundColor);
mPath.reset();
invalidate();
} else {
return false;
}
return true;
}
public Bitmap getImage() {
return Bitmap.createBitmap(mBitmap);
}
public void setImage(Bitmap bitmap){
this.mBitmap = bitmap;
if(mCanvas != null){
mCanvas.setBitmap(mBitmap);
}
}
public boolean hasChanged() {
return modified;
}
#Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
int bitmapWidth = mBitmap != null ? mBitmap.getWidth() : 0;
int bitmapHeight = mBitmap != null ? mBitmap.getWidth() : 0;
if (bitmapWidth >= width && bitmapHeight >= height) {
return;
}
if (bitmapWidth < width) {
bitmapWidth = width;
}
if (bitmapHeight < height) {
bitmapHeight = height;
}
Bitmap newBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
Canvas newCanvas = new Canvas();
newCanvas.setBitmap(newBitmap);
mCanvas = newCanvas;
if (mBitmap != null) {
newCanvas.drawBitmap(mBitmap, 0, 0, null);
mBitmap.recycle();
} else {
newCanvas.drawColor(sigBackgroundColor);
}
mBitmap = newBitmap;
}
private void createFakeMotionEvents() {
MotionEvent downEvent = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis() + 100, MotionEvent.ACTION_DOWN, 1f, 1f, 0);
MotionEvent upEvent = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis() + 100, MotionEvent.ACTION_UP, 1f, 1f, 0);
onTouchEvent(downEvent);
onTouchEvent(upEvent);
downEvent.recycle();
upEvent.recycle();
}
#Override
protected void onDraw(Canvas canvas) {
modified = true;
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
canvas.drawPath(mPath, mPaint);
}
#SuppressLint("ClickableViewAccessibility")
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchDown(x, y);
break;
case MotionEvent.ACTION_MOVE:
touchMove(x, y);
break;
case MotionEvent.ACTION_UP:
touchUp();
break;
}
invalidate();
return true;
}
private void touchDown(float x, float y) {
mPath.reset();
mPath.moveTo(x, y);
curX = x;
curY = y;
}
private void touchMove(float x, float y) {
float dx = Math.abs(x - curX);
float dy = Math.abs(y - curY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(curX, curY, (x + curX) / 2, (y + curY) / 2);
curX = x;
curY = y;
}
}
private void touchUp() {
mPath.lineTo(curX, curY);
if (mCanvas == null) {
mCanvas = new Canvas();
mCanvas.setBitmap(mBitmap);
}
mCanvas.drawPath(mPath, mPaint);
mPath.reset();
}
}
I'm trying to implement a erase function on the Fingerpaint App in the Api Demo's in Android. I change some code based on my needs. At first the erase function works well. I used:
drawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
It works well when the path has been drawn in the bitmap. But when I implement the Undo and Redo function(the way I achieve it is by using two arraylist and save the path there), PorterDuffXfermode doesn't work. Please take a look at this:
At image 1 I draw some paths(stored in an arraylist. Not drawn in the bitmap canvas)
Then at 2 thats the time I pressed the erase icon. I dont know what causing all the paths to became black. Then at 3 I tried to erase the paths I made(PorterDuffXFermode some says its black). Then when I switch back to drawing mode. It seems that instead or erasing, all it did was draw another path.
Any solutions for this? or workarounds?
Here is the code when to my drawingClass:
//this is where the drawing takes place
public class DrawingClass 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;
private float brushSize,lastBrushSize;
private boolean erase = false;
//array list for paths
private ArrayList<PathPoints> paths = new ArrayList<PathPoints>();
private ArrayList<PathPoints> undonePaths = new ArrayList<PathPoints>();
public Context context;
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
public DrawingClass(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
this.context = context;
setupDrawing();
}
private void setupDrawing(){
setFocusable(true);
setFocusableInTouchMode(true);
//instantiate the variable to get the last brush size
brushSize = getResources().getInteger(R.integer.medium_size);
lastBrushSize = brushSize;
//get drawing area setup for interaction
drawCanvas = new Canvas();
drawPath = new Path();
drawPaint = new Paint();
drawPaint.setColor(paintColor);
//initial properties
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);
//paths.add(new PathPoints(drawPath, paintColor, brushSize));
}
public void setBrushSize(float newSize){
//update brush size
float pixelAmount = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
newSize, getResources().getDisplayMetrics());
brushSize = pixelAmount;
drawPaint.setStrokeWidth(brushSize);
invalidate();
}
public void setLastBrushSize(float lastSize){
//set the last brush size
lastBrushSize = lastSize;
invalidate();
}
public float getLastBrushSize(){
//return the last brush size
return lastBrushSize;
}
public void setErase(boolean isErase){
//set erase true or false
erase = isErase;
if (erase) {
//drawPaint.setColor(Color.WHITE);
drawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
else drawPaint.setXfermode(null);
}
public void startNew(){
//creates a new canvass
drawCanvas.drawColor(0,PorterDuff.Mode.CLEAR);
drawCanvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
setDrawingCacheEnabled(true);
String imgSaved = MediaStore.Images.Media.insertImage(
context.getContentResolver(), getDrawingCache(),
UUID.randomUUID().toString()+".png", "drawing");
destroyDrawingCache();
paths.clear();
undonePaths.clear();
invalidate();
}
#Override
protected void onSizeChanged(int w,int h,int oldw,int oldh){
//view given size
super.onSizeChanged(w, h, oldw, oldh);
canvasBitmap = Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888);
drawCanvas = new Canvas(canvasBitmap);
}
#Override
protected void onDraw(Canvas canvas){
//draw view
//save the strokes in the bitmap
//canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
for (PathPoints p : paths){
drawPaint.setColor(p.getColor());
drawPaint.setStrokeWidth(p.getBrush());
invalidate();
canvas.drawPath(p.getPath(), drawPaint);
}
}
private void touch_start(float x, float y){
drawPath.reset();
drawPath.moveTo(x, (float) (y+.1));
drawPath.lineTo(x, y);
drawPath.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) {
drawPath.quadTo(mX, mY, (x + mX)/2, (y+ mY)/2);
mX = x;
mY = y;
}
}
private void touch_up(){
drawPath.lineTo(mX, mY);
drawCanvas.drawPath(drawPath, drawPaint);
drawPath = new Path();
paths.add(new PathPoints(drawPath, paintColor, brushSize));
}
#Override
public boolean onTouchEvent(MotionEvent event){
//detect user touch
float touchX = event.getX();
float touchY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
invalidate();
touch_start(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
invalidate();
touch_move(touchX, touchY);
break;
case MotionEvent.ACTION_UP:
invalidate();
touch_up();
drawPath.reset();
break;
default:
return false;
}
invalidate();
return true;
}
public void undoPath(){
if (paths.size()>0) {
undonePaths.add(paths.remove(paths.size()-1));
} else {
Log.d("Undo", "No paths left");
}
invalidate();
}
public void redoPath(){
if (undonePaths.size()>0) {
paths.add(undonePaths.remove(undonePaths.size()-1));
} else {
Log.d("Redo", "No paths left");
}
invalidate();
}
public void setColor(String newColor){
//set color
paintColor = Color.parseColor(newColor);
drawPaint.setColor(paintColor);
invalidate();
}
class PathPoints{
private Path path;
private int color;
private float brushsize;
private int x, y;
//holds the paths data
public PathPoints(Path path, int color, float brushsize) {
this.path = path;
this.color = color;
this.brushsize = brushsize;
}
public Path getPath() {
return path;
}
public void setPath(Path path) {
this.path = path;
}
public float getBrush(){
return brushsize;
}
public void setBrush(float brushsize){
this.brushsize = brushsize;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
}
In my canvas application i want to use custom brushes like brushes in attached image.so please somebody help me fast how can i make custom brushes like attached image?
In my app i made doted line using following code:
mPaint.setPathEffect(new DashPathEffect(new float[] { 8, 8 }, 0));
and getting Blur and Emboss effect using following code:
mEmboss = new EmbossMaskFilter(new float[] { 1, 1, 1 }, 0.4f, 6, 3.5f);
mBlur = new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL);
As you can clearly see, no trivial shader effects / rectangles / circles can accomplish this.
Images / Bitmaps are used.
So simply repeatedly draw Bitmaps using canvas.drawBitmap. You draw the same Bitmap again and again while the finger moves.
For adding a custom color you can add a simple filter.
An Example
public class CanvasBrushDrawing extends View {
private Bitmap mBitmapBrush;
private Vector2 mBitmapBrushDimensions;
private List<Vector2> mPositions = new ArrayList<Vector2>(100);
private static final class Vector2 {
public Vector2(float x, float y) {
this.x = x;
this.y = y;
}
public final float x;
public final float y;
}
public CanvasBrushDrawing(Context context) {
super(context);
// load your brush here
mBitmapBrush = BitmapFactory.decodeResource(context.getResources(), R.drawable.splatter_brush);
mBitmapBrushDimensions = new Vector2(mBitmapBrush.getWidth(), mBitmapBrush.getHeight());
setBackgroundColor(0xffffffff);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (Vector2 pos : mPositions) {
canvas.drawBitmap(mBitmapBrush, pos.x, pos.y, null);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_MOVE:
final float posX = event.getX();
final float posY = event.getY();
mPositions.add(new Vector2(posX - mBitmapBrushDimensions.x / 2, posY - mBitmapBrushDimensions.y / 2));
invalidate();
}
return true;
}
}
Though it is too late i want to share something. This might help someone. Various brush techniques are discussed in the following link with JavaScript code for HTML canvas. All you have to do is convert JavaScript code to your expected one. It is pretty simple to covert JavaScript Canvas code to Android Canvas code.
Exploring canvas drawing techniques
I have converted "Multiple lines" technique to Java code for android; You can check the following android view code.
public class MultipleLines extends View {
private Bitmap bitmap;
private Canvas canvas;
private Paint mPaint;
public MultipleLines(Context context) {
super(context);
init();
}
private void init(){
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(0xFFFF0000);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(1);
}
#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
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 boolean isDrawing;
private List<PointF> points = new ArrayList<>();
private void touch_start(float touchX, float touchY) {
isDrawing = true;
points.add(new PointF(touchX, touchY));
canvas.save();
}
private void touch_move(float touchX, float touchY) {
if (!isDrawing) return;
canvas.drawColor(Color.TRANSPARENT);
points.add(new PointF(touchX, touchY));
stroke(offsetPoints(-10));
stroke(offsetPoints(-5));
stroke(points);
stroke(offsetPoints(5));
stroke(offsetPoints(10));
}
private void touch_up() {
isDrawing = false;
points.clear();
canvas.restore();
}
private List<PointF> offsetPoints(float val) {
List<PointF> offsetPoints = new ArrayList<>();
for (int i = 0; i < points.size(); i++) {
PointF point = points.get(i);
offsetPoints.add(new PointF(point.x + val, point.y + val));
}
return offsetPoints;
}
private void stroke(List<PointF> points) {
PointF p1 = points.get(0);
PointF p2 = points.get(1);
Path path = new Path();
path.moveTo(p1.x, p1.y);
for (int i = 1; i < points.size(); i++) {
// we pick the point between pi+1 & pi+2 as the
// end point and p1 as our control point
PointF midPoint = midPointBtw(p1, p2);
path.quadTo(p1.x, p1.y, midPoint.x, midPoint.y);
p1 = points.get(i);
if(i+1 < points.size()) p2 = points.get(i+1);
}
// Draw last line as a straight line while
// we wait for the next point to be able to calculate
// the bezier control point
path.lineTo(p1.x, p1.y);
canvas.drawPath(path,mPaint);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(bitmap, 0, 0, null);
}
private PointF midPointBtw(PointF p1, PointF p2) {
return new PointF(p1.x + (p2.x - p1.x) / 2.0f, p1.y + (p2.y - p1.y) / 2.0f);
}
}