I am working on an application similar to Amaziograph of iPhone also known as kaleidoscope or mandala.
Till now I have tried and have made one particular layout of that app
I have extended the canvas and have made a custom canvas, where I have divided canvas in 9 parts similar to image and in draw method I have rotated the canvas and copied its content in for loop. Here is my canvas class code for above circular division shape
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.madala.mandaladrawing.R;
import com.madala.mandaladrawing.model.DrawingEvent;
import com.madala.mandaladrawing.utils.Common;
public class CanvasView extends View {
private final Context context;
private Bitmap bitmap;
private Canvas bitmapCanvas;
private Paint bitmapPaint;
private Path path = new Path();
private Paint brushPaint;
private int numberOfMirror = 5;
private int cx, cy;
public CanvasView(Context context) {
super(context);
this.context = context;
init();
}
public CanvasView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
init();
}
public CanvasView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
init();
}
private void init() {
brushPaint = createPaint();
brushPaint.setColor(0xffffffff);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(bitmap, 0, 0, bitmapPaint);
canvas.drawPath(path, brushPaint);
for (int i = 1; i < numberOfMirror; i++) {
canvas.rotate(360f / numberOfMirror, cx, cy);
canvas.drawPath(path, brushPaint);
}
}
public void clearCanvas(){
bitmapCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
invalidate();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
setLayerType(View.LAYER_TYPE_HARDWARE, null);
bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
bitmapCanvas = new Canvas(bitmap);
bitmapPaint = new Paint(Paint.DITHER_FLAG);
cx = w / 2;
cy = h / 2;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
path.lineTo(x, y);
break;
case MotionEvent.ACTION_UP:
path.lineTo(x, y);
drawToCanvas(path, brushPaint);
path.reset();
break;
default:
return false;
}
invalidate();
return true;
}
private void drawToCanvas(Path path, Paint brushPaint) {
bitmapCanvas.drawPath(path, brushPaint);
for (int i = 1; i < numberOfMirror; i++) {
bitmapCanvas.rotate(360f / numberOfMirror, cx, cy);
bitmapCanvas.drawPath(path, brushPaint);
}
}
public int getCurrentBrushColor() {
return brushPaint.getColor();
}
public void setCurrentBrushColor(int color) {
brushPaint.setColor(color);
}
private Paint createPaint() {
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setStrokeWidth(8f);
p.setStyle(Paint.Style.STROKE);
p.setStrokeJoin(Paint.Join.ROUND);
p.setStrokeCap(Paint.Cap.ROUND);
return p;
}
}
I am not able to achieve the functionality for below figure
I want to draw in one box and it should be copied in the other boxes. How could this be achieved a small guide will also be helpful?
Maybe you can save traces into array, and paste it to other drawing views
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//save initial x,y into array or send it to other canvas
/*public general variable*/
String Drawing +="[[x,y]";
path.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
//save middle x,y into array or send it to other canvas
String Drawing +=",[x,y]";
path.lineTo(x, y);
break;
case MotionEvent.ACTION_UP:
//save last point into array or send it to other canvas
String Drawing +=",[x,y]]";
path.lineTo(x, y);
drawToCanvas(path, brushPaint);
path.reset();
break;
default:
return false;
}
invalidate();
return true;
}
the string resultant should be all user traces
[[x,y],[x,y],[x,y],[x,y],[x,y]], trace 1
[[x,y],[x,y],[x,y],[x,y],[x,y]], trace 2
[[x,y],[x,y],[x,y],[x,y],[x,y]], trace 3
etc..
trace1 + trace2 +trace 3 = thing drawed by user.
when you want or maybe in real time, you can send the points drawed on this view to other view... or when users end writing send the string and extract the points...
Well it's just an idea, Hope i helped ;)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- this has changed from first answer -->
<dimen name="translation_size">30dp</dimen>
</resources>
...
int size = getResources().getDimensionPixelSize(R.dimen.translation_size);
...
private void drawToCanvas(Path path, Paint brushPaint) {
bitmapCanvas.drawPath(path, brushPaint); // just render normally
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(path, brushPaint);
int xMax = getWidth() / size;
int yMax = getHeight() / size;
int xMin = -xMax;
int yMin = -yMax;
for (int x = xMin; x <= xMax; x++) {
for (int y = yMin; y <= yMax; y++) {
if ((Math.abs(x % 6) == 0 && Math.abs(y % 4) == 0) ||
(Math.abs(x % 6) == 3 && Math.abs(y % 4) == 2)) {
int xs = x * size;
int ys = y * size;
canvas.translate(xs, ys);
canvas.drawBitmap(bitmap, 0, 0, bitmapPaint);
canvas.translate(-xs, -ys);
}
}
}
}
You don't have to use resources like I did, but if you hard-code a size into your app, make sure you multiply by the screen density to get the correct pixel offsets.
Related
I need to implement a custom signature view inside a scroll view. The structure of my xml file is like this
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
>
<ScrollView>
<androidx.constraintlayout.widget.ConstraintLayout>
//bunch of linear layouts and textviews
<LinearLayout
android:id="#+id/linearLayoutSignature"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginTop="10dp"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="#+id/imageView3"
tools:ignore="MissingConstraints">
</LinearLayout>
//some buttons and textfields
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
Now, when I move right or left inside my signature view canvas, it's all good. But when I move up or down, the scroll moves, and I can't draw. Is there a way to implement this without moving my signature view outside of scroll view? Is there a way to freeze signature layout when it is active, so it wont scroll up or down while I draw a signature?
CaptureSignature.java
package com.example.app_ptt.services;
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.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import java.io.ByteArrayOutputStream;
public class CaptureSignature extends View {
private Bitmap _Bitmap;
private Canvas _Canvas;
private Path _Path;
private Paint _BitmapPaint;
private Paint _paint;
private float _mX;
private float _mY;
private float TouchTolerance = 4;
private float LineThickness = 4;
public CaptureSignature(Context context, AttributeSet attr) {
super(context, attr);
_Path = new Path();
_BitmapPaint = new Paint(Paint.DITHER_FLAG);
_paint = new Paint();
_paint.setAntiAlias(true);
_paint.setDither(true);
_paint.setColor(Color.argb(255, 0, 0, 0));
_paint.setStyle(Paint.Style.STROKE);
_paint.setStrokeJoin(Paint.Join.ROUND);
_paint.setStrokeCap(Paint.Cap.ROUND);
_paint.setStrokeWidth(LineThickness);
}
public CaptureSignature(Context context) {
super(context);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
_Bitmap = Bitmap.createBitmap(w, (h > 0 ? h : ((View) this.getParent()).getHeight()), Bitmap.Config.ARGB_8888);
_Canvas = new Canvas(_Bitmap);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(_Bitmap, 0, 0, _BitmapPaint);
canvas.drawPath(_Path, _paint);
}
private void TouchStart(float x, float y) {
_Path.reset();
_Path.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 >= TouchTolerance || dy >= TouchTolerance) {
_Path.quadTo(_mX, _mY, (x + _mX) / 2, (y + _mY) / 2);
_mX = x;
_mY = y;
}
}
private void TouchUp() {
if (!_Path.isEmpty()) {
_Path.lineTo(_mX, _mY);
_Canvas.drawPath(_Path, _paint);
} else {
_Canvas.drawPoint(_mX, _mY, _paint);
}
_Path.reset();
}
#Override
public boolean onTouchEvent(MotionEvent e) {
super.onTouchEvent(e);
float x = e.getX();
float y = e.getY();
switch (e.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;
}
public void ClearCanvas() {
_Canvas.drawColor(Color.WHITE);
invalidate();
}
public byte[] getBytes() {
Bitmap b = getBitmap();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
b.compress(Bitmap.CompressFormat.PNG, 100, baos);
return baos.toByteArray();
}
public Bitmap getBitmap() {
View v = (View) this.getParent();
Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
v.draw(c);
return b;
}
}
try this Override method copy in your CaptureSignature class and check
#Override
public boolean onTouchEvent(final MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_UP:
getParent().getParent().requestDisallowInterceptTouchEvent(false);
break;
default:
break;
}
return super.onTouchEvent(event);
}
I am hand drawing images on a canvas and then converting them to a bitmap. Then, I am dragging this on screen. That's all fine, What I want is onTouchEvent to draw a rectangle round this image/s but NOT the whole canvas. So I'm wondering if I can measure the drawing width and height and subtract this from canvas width and height but I'm struggling and not sure How can I achieve this? Unless there's a better idea someone can suggest.
private static class DrawingView extends View {
float x = 0f,y = 0f;
float dX,dY;
Paint paint;
public DrawingView(Context context){
super(context);
bitmap = BitmapFactory.decodeResource(context.getResources(), R.id.main);
paint = new Paint();
}
public boolean onTouchEvent(MotionEvent event){
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
dX = this.getX() - event.getRawX();
dY = this.getY() - event.getRawY();
invalidate();
break;
case MotionEvent.ACTION_MOVE:
this.setX(event.getRawX() + dX);
this.setY(event.getRawY() + dY);
invalidate();
break;
case MotionEvent.ACTION_UP:
invalidate();
break;
}
return true;
}
#Override
public void onDraw(Canvas canvas) {
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.BLACK);
canvas.drawBitmap(bitmap, x, y, paint);
float left = (x + (bitmap.getWidth()/2));
float top = (y + (bitmap.getHeight()/2));
float right = bitmap.getWidth() - left;
float bottom = bitmap.getHeight() - top;
canvas.drawRect(left, top, right, bottom, paint);
}
}//End of inner class
My drawing class
public class DrawView extends View {
private Paint drawPaint, canvasPaint;
private Canvas drawCanvas;
private Bitmap canvasBitmap;
private SparseArray<Path> paths;
public DrawView(Context context) {
super(context);
setupDrawing();
}
public DrawView(Context context, AttributeSet attrs) {
super(context, attrs);
setupDrawing();
}
public DrawView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setupDrawing();
}
private void setupDrawing() {
paths = new SparseArray<>();
drawPaint = new Paint();
drawPaint.setColor(Color.BLACK);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(9);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
#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);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
for (int i = 0; i < paths.size(); i++) {
canvas.drawPath(paths.valueAt(i), drawPaint);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int index = event.getActionIndex();
int id = event.getPointerId(index);
final Point p = new Point();
Path path;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
path = new Path();
path.moveTo(event.getX(index), event.getY(index));
paths.put(id, path);
break;
case MotionEvent.ACTION_MOVE:
for (int i=0; i<event.getPointerCount(); i++) {
id = event.getPointerId(i);
path = paths.get(id);
if (path != null) path.lineTo(event.getX(i), event.getY(i));
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
path = paths.get(id);
if (path != null) {
drawCanvas.drawPath(path, drawPaint);
paths.remove(id);
}
setPenEnabled(true);
break;
default:
return false;
}
invalidate();
return true;
}
/**
* change path color here
*/
public void setPathColor(int color) {
drawPaint.setColor(color);
}
//Clear screen
public void clear() {
canvasBitmap.eraseColor(Color.TRANSPARENT);
paths.clear();
invalidate();
System.gc();
}
}//End of Class
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)
I am creating a pixel-hunting game. So my activity shows an ImageView. And I want to create a hint "show me where is the object". For this I need to blur whole image except a circle around a point where the object is located. Instead of blur I can show a just semitransparent black background.
There is there is no problem to draw a semitransparent rectangle on the Canvas.
But I don't know how to crop a transparent circle from it.
The result should look like like this:
Please help me to achieve same result on Android SDK.
So finally I managed to do this.
Firstly I draw a semitransparent black rectangle on whole view.
After that using PorterDuff.Mode.CLEAR I cut a transparent circle to show cat's position.
I had problem with PorterDuff.Mode.CLEAR: firstly I was getting a black circle instead of a transparent one.
Thanks to Romain Guy's comments here: comment here I understood that my window is opaque and I should draw on another bitmap. And only after draw on View's canvas.
Here is my onDraw method:
private Canvas temp;
private Paint paint;
private Paint p = new Paint();
private Paint transparentPaint;
private void init(){
Bitmap bitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
temp = new Canvas(bitmap);
paint = new Paint();
paint.setColor(0xcc000000);
transparentPaint = new Paint();
transparentPaint.setColor(getResources().getColor(android.R.color.transparent));
transparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
protected void onDraw(Canvas canvas) {
temp.drawRect(0, 0, temp.getWidth(), temp.getHeight(), paint);
temp.drawCircle(catPosition.x + radius / 2, catPosition.y + radius / 2, radius, transparentPaint);
canvas.drawBitmap(bitmap, 0, 0, p);
}
I found a solution without bitmap drawing and creation. Here is the result of my implementation:
You need to create a custom FrameLayout and drawCircle with Clear paint:
mBackgroundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
Also do not forget to disable Hardware acceleration and call setWillNotDraw(false) because we will override onDraw method
setWillNotDraw(false);
setLayerType(LAYER_TYPE_HARDWARE, null);
The full example is here:
public class TutorialView extends FrameLayout {
private static final float RADIUS = 200;
private Paint mBackgroundPaint;
private float mCx = -1;
private float mCy = -1;
private int mTutorialColor = Color.parseColor("#D20E0F02");
public TutorialView(Context context) {
super(context);
init();
}
public TutorialView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public TutorialView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public TutorialView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
setWillNotDraw(false);
setLayerType(LAYER_TYPE_HARDWARE, null);
mBackgroundPaint = new Paint();
mBackgroundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
#Override
public boolean onTouchEvent(MotionEvent event) {
mCx = event.getX();
mCy = event.getY();
invalidate();
return true;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(mTutorialColor);
if (mCx >= 0 && mCy >= 0) {
canvas.drawCircle(mCx, mCy, RADIUS, mBackgroundPaint);
}
}
}
PS: This implementation just draws hole inside itself, you need to put background in your layout and add this TutorialView on top.
I have done this way by creating custom LinearLayout:
Check the Screenshot:
CircleOverlayView.java
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
import android.widget.LinearLayout;
/**
* Created by hiren on 10/01/16.
*/
public class CircleOverlayView extends LinearLayout {
private Bitmap bitmap;
public CircleOverlayView(Context context) {
super(context);
}
public CircleOverlayView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CircleOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public CircleOverlayView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (bitmap == null) {
createWindowFrame();
}
canvas.drawBitmap(bitmap, 0, 0, null);
}
protected void createWindowFrame() {
bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas osCanvas = new Canvas(bitmap);
RectF outerRectangle = new RectF(0, 0, getWidth(), getHeight());
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(getResources().getColor(R.color.colorPrimary));
paint.setAlpha(99);
osCanvas.drawRect(outerRectangle, paint);
paint.setColor(Color.TRANSPARENT);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
float centerX = getWidth() / 2;
float centerY = getHeight() / 2;
float radius = getResources().getDimensionPixelSize(R.dimen.radius);
osCanvas.drawCircle(centerX, centerY, radius, paint);
}
#Override
public boolean isInEditMode() {
return true;
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
bitmap = null;
}
}
CircleDrawActivity.java:
public class CircleDrawActivity extends AppCompatActivity{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_circle_draw);
}
}
activity_circle_draw.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rlParent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/lighthouse"
android:scaleType="fitXY" />
<common.customview.CircleOverlayView
android:id="#+id/cicleOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent">
</common.customview.CircleOverlayView>
</RelativeLayout>
colors.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>
dimens.xml:
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="nav_header_vertical_spacing">16dp</dimen>
<dimen name="nav_header_height">160dp</dimen>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<dimen name="radius">50dp</dimen>
</resources>
Hope this will help you.
#Robert's answer actually showed me how to solve this problem but his code doesn't work. So I have updated his solution and made it work:
public class CaptureLayerView extends View {
private Bitmap bitmap;
private Canvas cnvs;
private Paint p = new Paint();
private Paint transparentPaint = new Paint();;
private Paint semiTransparentPaint = new Paint();;
private int parentWidth;
private int parentHeight;
private int radius = 100;
public CaptureLayerView(Context context) {
super(context);
init();
}
public CaptureLayerView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
transparentPaint.setColor(getResources().getColor(android.R.color.transparent));
transparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
semiTransparentPaint.setColor(getResources().getColor(R.color.colorAccent));
semiTransparentPaint.setAlpha(70);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
bitmap = Bitmap.createBitmap(parentWidth, parentHeight, Bitmap.Config.ARGB_8888);
cnvs = new Canvas(bitmap);
cnvs.drawRect(0, 0, cnvs.getWidth(), cnvs.getHeight(), semiTransparentPaint);
cnvs.drawCircle(parentWidth / 2, parentHeight / 2, radius, transparentPaint);
canvas.drawBitmap(bitmap, 0, 0, p);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
parentWidth = MeasureSpec.getSize(widthMeasureSpec);
parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth, parentHeight);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
And now use this view in any layout like this:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
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">
<SurfaceView
android:id="#+id/surfaceView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">
</SurfaceView>
<com.example.myapp.CaptureLayerView
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="#+id/btnTakePicture"
android:layout_width="match_parent"
android:layout_height="80dp"
android:onClick="onClickPicture"
android:text="#string/take_picture">
</Button>
Here I wanted a semi transparent layer on the SurfaceView with transparent circle in the center.
P.S. This code is not optimized one because it creates Bitmap in onDraw method, it is because I couldn't get parent view's width and height in init method, so I only could know them in onDraw.
A simple 2020 kotlin solution without any warnings.
1. Code for custom view with hole
class HolePosition(var x: Float, var y: Float, var r: Float)
class HoleView #JvmOverloads constructor(
context: Context?,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
): View(context, attrs, defStyleAttr) {
private val paint: Paint = Paint()
private var holePaint: Paint = Paint()
private var bitmap: Bitmap? = null
private var layer: Canvas? = null
//position of hole
var holePosition: HolePosition = HolePosition(0.0f, 0.0f, 0.0f)
set(value) {
field = value
//redraw
this.invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (bitmap == null) { configureBitmap() }
//draw background
layer?.drawRect(0.0f, 0.0f, width.toFloat(), height.toFloat(), paint)
//draw hole
layer?.drawCircle(holePosition.x, holePosition.y, holePosition.r, holePaint);
//draw bitmap
canvas.drawBitmap(bitmap!!, 0.0f, 0.0f, paint);
}
private fun configureBitmap() {
//create bitmap and layer
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
layer = Canvas(bitmap!!)
}
init {
//configure background color
val backgroundAlpha = 0.8
paint.color = ColorUtils.setAlphaComponent(resources.getColor(R.color.mainDark, null), (255 * backgroundAlpha).toInt() )
//configure hole color & mode
holePaint.color = resources.getColor(android.R.color.transparent, null)
holePaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}
}
2. Layout example
<com.your_company.package.HoleView
android:id="#+id/hole_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.your_company.package.HoleView>
3. Hole set usage
val holeView = findViewById<HoleView>(R.id.hole_view)
holeView.holePosition = HolePosition(x = 350.0f, y = 350.0f, r = 180.0f)
4. Results
I don't have much to add to your answer, But if anyone's interested I moved the bitmap allocation and all to onSizeChanged so it's better performance wise.
Here you can find a FrameLayout with a "Hole" in the middle of it ;)
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.FrameLayout;
/**
* Created by blackvvine on 1/1/16.
*/
public class SteroidFrameLayout extends FrameLayout {
private Paint transPaint;
private Paint defaultPaint;
private Bitmap bitmap;
private Canvas temp;
public SteroidFrameLayout(Context context) {
super(context);
__init__();
}
public SteroidFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
__init__();
}
public SteroidFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
__init__();
}
private void __init__() {
transPaint = new Paint();
defaultPaint = new Paint();
transPaint.setColor(Color.TRANSPARENT);
transPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
setWillNotDraw(false);
}
#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);
temp = new Canvas(bitmap);
}
#Override
protected void dispatchDraw(Canvas canvas) {
temp.drawColor(Color.TRANSPARENT);
super.dispatchDraw(temp);
temp.drawCircle(cx, cy, getWidth()/4, transPaint);
canvas.drawBitmap(bitmap, 0, 0, defaultPaint);
if (p < 1)
invalidate();
else
animRunning = false;
}
}
p.s: although it's much more efficient than the original answer it's still a relatively heavy task to do in a draw() method, so if you're using this technique in an animation like me, don't expect a 60.0fps smooth one
public class CircleBlur extends Activity implements View.OnTouchListener {
SeekBar seekBar;
ImageView image,image1;
private Paint paint;
Bitmap circle,blurimg;
private Matrix matrix = new Matrix();
private Matrix savedMatrix = new Matrix();
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private int mode = NONE;
private PointF start = new PointF();
private PointF mid = new PointF();
private float oldDist = 1f;
float newRot = 0f;
private float d = 0f;
private float[] lastEvent = null;
private float radius=12;
Bitmap blurbitmap;
#RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_blurimage);
image=findViewById(R.id.image);
seekBar=findViewById(R.id.seekbar);
image1=findViewById(R.id.image1);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
//here your image bind to Imageview
image.setImageResource(R.drawable.nas1);
image1.setOnTouchListener(this);
seekBar.setProgress(12);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
radius = (float) CircleBlur.this.seekBar.getProgress();
blurbitmap=createBlurBitmap(blurimg, radius);
CircleBlur();
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
#RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
private Bitmap createBlurBitmap(Bitmap src, float r) {
if (r <= 0) {
r = 0.1f;
} else if (r > 25) {
r = 25.0f;
}
Bitmap bitmap = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888);
RenderScript renderScript = RenderScript.create(this);
Allocation blurInput = Allocation.createFromBitmap(renderScript, src);
Allocation blurOutput = Allocation.createFromBitmap(renderScript, bitmap);
ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
blur.setInput(blurInput);
blur.setRadius(r);
blur.forEach(blurOutput);
blurOutput.copyTo(bitmap);
renderScript.destroy();
return bitmap;
}
#RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
public void CircleBlur()
{
Bitmap result;
// your circle image
circle = BitmapFactory.decodeResource(getResources(),R.drawable.cicleouter);
result = Bitmap.createBitmap(blurimg.getWidth(), blurimg.getHeight(), Bitmap.Config.ARGB_8888);
Canvas mCanvas = new Canvas(result);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
paint.setDither(true);
mCanvas.drawBitmap(blurimg,0,0, null);
mCanvas.drawBitmap(circle, matrix, paint);
paint.setXfermode(null);
image1.setImageBitmap(result);
}
#RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
#Override
public boolean onTouch(View v, MotionEvent event) {
image1 = (ImageView) v;
float x = event.getX(), y = event.getY();
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_DOWN:
savedMatrix.set(matrix);
start.set(x, y);
mode = DRAG;
lastEvent = null;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
if (oldDist > 10f) {
savedMatrix.set(matrix);
midPoint(mid, event);
mode = ZOOM;
}
lastEvent = new float[4];
lastEvent[0] = event.getX(0);
lastEvent[1] = event.getX(1);
lastEvent[2] = event.getY(0);
lastEvent[3] = event.getY(1);
d = rotation(event);
break;
// case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
lastEvent = null;
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
matrix.set(savedMatrix);
float dx = x - start.x;
float dy = y - start.y;
matrix.postTranslate(dx, dy);
} else if (mode == ZOOM) {
float newDist = spacing(event);
if (newDist > 10f) {
matrix.set(savedMatrix);
float scale = (newDist / oldDist);
matrix.postScale(scale, scale, mid.x, mid.y);
}
if (lastEvent != null && event.getPointerCount() == 2 || event.getPointerCount() == 3) {
newRot = rotation(event);
float r = newRot - d;
float[] values = new float[9];
matrix.getValues(values);
float tx = values[2];
float ty = values[5];
float sx = values[0];
float xc = (image.getWidth() / 2) * sx;
float yc = (image.getHeight() / 2) * sx;
matrix.postRotate(r, tx + xc, ty + yc);
}
}
break;
}
CircleBlur();
return true;
}
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
float s=x * x + y * y;
return (float)Math.sqrt(s);
}
private void midPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
private float rotation(MotionEvent event) {
double delta_x = (event.getX(0) - event.getX(1));
double delta_y = (event.getY(0) - event.getY(1));
double radians = Math.atan2(delta_y, delta_x);
return (float) Math.toDegrees(radians);
}
}
This worked for me:
canvas.drawCircle(x,y,radius,new Paint(Color.TRANSPARENT))
If you're having trouble getting a transparent circle cutout over a view with an opaque background, see this answer. To get it to work, I set my custom layout view to have a transparent background in XML, then drew on the background color I wanted for the layout with the line
cv.drawColor(Color.BLUE); //replace with your desired background color
The full OnDraw method from the answer I linked above:
#Override
protected void onDraw(Canvas canvas) {
int w = getWidth();
int h = getHeight();
int radius = w > h ? h / 2 : w / 2;
bm.eraseColor(Color.TRANSPARENT);
cv.drawColor(Color.BLUE);
cv.drawCircle(w / 2, h / 2, radius, eraser);
canvas.drawBitmap(bm, 0, 0, null);
super.onDraw(canvas);
}
Kotlin version without Bitmap allocation. But instead of a circle, a rounded rectangle is drawn here. I'm sure you can adapt it to circle by yourself.
private val scanAreaBorder = Paint().apply {
color = ContextCompat.getColor(context, R.color.postyellow)
style = Paint.Style.STROKE
strokeWidth = dpToPx(context, resources.getDimension(R.dimen.scan_area_border_stroke_width).toInt())
}
private val scanArea = Paint().apply {
color = ContextCompat.getColor(context, android.R.color.transparent)
xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}
private val background = Paint().apply {
color = ContextCompat.getColor(context, R.color.scanner_area)
}
init {
setLayerType(LAYER_TYPE_HARDWARE, null)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.run {
drawRect(0f, 0f, width.toFloat(), height.toFloat(), background)
val top = (height / 2f) - dpToPx(context, resources.getDimension(R.dimen.scan_area_height).toInt())
val bottom = (height / 2f) + dpToPx(context, resources.getDimension(R.dimen.scan_area_height).toInt())
val margin = dpToPx(context, resources.getDimension(R.dimen.scan_area_margin).toInt())
val cornerRadius = dpToPx(context, resources.getDimension(R.dimen.scan_area_corner_radius).toInt())
drawRoundRect(margin, top, width.toFloat() - margin, bottom, cornerRadius, cornerRadius, scanArea)
drawRoundRect(margin, top, width.toFloat() - margin, bottom, cornerRadius, cornerRadius, scanAreaBorder)
}
}
This can be actually done in a much more efficient way, and with much less code using a Path object.
code:
In the constructor of a View overlay class:
path.addCircle(600,1000, 200, Path.Direction.CW);
path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
paint.setColor(0x55_00_00_00);
(The numbers for x, y and radius above should be replaces with whatever you need).
And override the view's onDraw(Canvas):
#Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(path,paint);
}
That's it...
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();
}
}