How can I make the background of my custom brush transparent? - android

I wanted to draw on the canvas with the code below with my custom brush, but as you can see in the picture, the background of my brush is black, albeit without color.
Although I specified the brush color as Color.TRANSPARENT or Color.parseColor ("# 00000000"), the brush background still turns black.
How can I make the background color of my brush transparent?
click to see the picture
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.graphics.PathMeasure;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import androidx.annotation.ColorInt;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
import java.util.Stack;
public class BrushDrawingView extends View {
static final float DEFAULT_BRUSH_SIZE = 50.0f;
static final float DEFAULT_ERASER_SIZE = 50.0f;
static final int DEFAULT_OPACITY = 255;
private float mBrushSize = DEFAULT_BRUSH_SIZE;
private float mBrushEraserSize = DEFAULT_ERASER_SIZE;
private int mOpacity = DEFAULT_OPACITY;
private final Stack<BrushLinePath> mDrawnPaths = new Stack<>();
private final Stack<BrushLinePath> mRedoPaths = new Stack<>();
private final Paint mDrawPaint = new Paint();
private Canvas mDrawCanvas;
private boolean mBrushDrawMode;
private Bitmap brushBitmap;
private Path mPath;
private float mTouchX, mTouchY;
private static final float TOUCH_TOLERANCE = 4;
private BrushViewChangeListener mBrushViewChangeListener;
public BrushDrawingView(Context context) {
this(context, null);
}
public BrushDrawingView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BrushDrawingView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setupBrushDrawing();
}
private void setupBrushDrawing() {
//Caution: This line is to disable hardware acceleration to make eraser feature work properly
setupPathAndPaint();
setVisibility(View.GONE);
}
private void setupPathAndPaint() {
mPath = new Path();
mDrawPaint.setAntiAlias(true);
mDrawPaint.setStyle(Paint.Style.STROKE);
mDrawPaint.setStrokeJoin(Paint.Join.ROUND);
mDrawPaint.setStrokeCap(Paint.Cap.ROUND);
mDrawPaint.setStrokeWidth(mBrushSize);
mDrawPaint.setAlpha(mOpacity);
}
private void refreshBrushDrawing() {
mBrushDrawMode = true;
setupPathAndPaint();
}
void brushEraser() {
mBrushDrawMode = true;
mDrawPaint.setStrokeWidth(mBrushEraserSize);
mDrawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
public void setBrushDrawingMode(boolean brushDrawMode) {
this.mBrushDrawMode = brushDrawMode;
if (brushDrawMode) {
this.setVisibility(View.VISIBLE);
refreshBrushDrawing();
}
}
public Bitmap getBrushBitmap() {
return brushBitmap;
}
public void setBrushBitmap(Bitmap brushBitmap) {
this.brushBitmap = brushBitmap;
}
public void setOpacity(#IntRange(from = 0, to = 255) int opacity) {
this.mOpacity = (int) (opacity * 2.55f);
setBrushDrawingMode(true);
}
public int getOpacity() {
return mOpacity;
}
boolean getBrushDrawingMode() {
return mBrushDrawMode;
}
public void setBrushSize(float size) {
mBrushSize = 5 + (int) (size);
setBrushDrawingMode(true);
}
void setBrushColor(#ColorInt int color) {
mDrawPaint.setColor(color);
setBrushDrawingMode(true);
}
void setBrushEraserSize(float brushEraserSize) {
this.mBrushEraserSize = brushEraserSize;
setBrushDrawingMode(true);
}
void setBrushEraserColor(#ColorInt int color) {
mDrawPaint.setColor(color);
setBrushDrawingMode(true);
}
float getEraserSize() {
return mBrushEraserSize;
}
public float getBrushSize() {
return mBrushSize;
}
int getBrushColor() {
return mDrawPaint.getColor();
}
public void clearAll() {
mDrawnPaths.clear();
mRedoPaths.clear();
if (mDrawCanvas != null) {
mDrawCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
invalidate();
}
void setBrushViewChangeListener(BrushViewChangeListener brushViewChangeListener) {
mBrushViewChangeListener = brushViewChangeListener;
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
Bitmap canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mDrawCanvas = new Canvas(canvasBitmap);
}
#Override
protected void onDraw(Canvas canvas) {
for (BrushLinePath linePath : mDrawnPaths) {
canvas.drawPath(linePath.getDrawPath(), linePath.getDrawPaint());
}
canvas.drawPath(mPath, mDrawPaint);
/////
final Bitmap scaledBitmap = getScaledBitmap();
final float centerX = scaledBitmap.getWidth() / 2;
final float centerY = scaledBitmap.getHeight() / 2;
final PathMeasure pathMeasure = new PathMeasure(mPath, false);
float distance = scaledBitmap.getWidth() / 2;
float[] position = new float[2];
float[] slope = new float[2];
float slopeDegree;
while (distance < pathMeasure.getLength())
{
pathMeasure.getPosTan(distance, position, slope);
slopeDegree = (float)((Math.atan2(slope[1], slope[0]) * 180f) / Math.PI);
canvas.save();
canvas.translate(position[0] - centerX, position[1] - centerY);
canvas.rotate(slopeDegree, centerX, centerY);
canvas.drawBitmap(scaledBitmap, 0, 0, mDrawPaint);
canvas.restore();
distance += scaledBitmap.getWidth() + 10;
}
}
/////
private Bitmap getScaledBitmap()
{
// width / height of the bitmap[
float width = brushBitmap.getWidth();
float height = brushBitmap.getHeight();
// ratio of the bitmap
float ratio = width / height;
// set the height of the bitmap to the width of the path (from the paint object).
float scaledHeight = mDrawPaint.getStrokeWidth();
// to maintain aspect ratio of the bitmap, use the height * ratio for the width.
float scaledWidth = scaledHeight * ratio;
// return the generated bitmap, scaled to the correct size.
return Bitmap.createScaledBitmap(brushBitmap, (int)scaledWidth, (int)scaledHeight, true);
}
/**
* Handle touch event to draw paint on canvas i.e brush drawing
*
* #param event points having touch info
* #return true if handling touch events
*/
#SuppressLint("ClickableViewAccessibility")
#Override
public boolean onTouchEvent(#NonNull MotionEvent event) {
if (mBrushDrawMode) {
float touchX = event.getX();
float touchY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchStart(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
touchMove(touchX, touchY);
break;
case MotionEvent.ACTION_UP:
touchUp();
break;
}
invalidate();
return true;
} else {
return false;
}
}
boolean undo() {
if (!mDrawnPaths.empty()) {
mRedoPaths.push(mDrawnPaths.pop());
invalidate();
}
if (mBrushViewChangeListener != null) {
mBrushViewChangeListener.onViewRemoved(this);
}
return !mDrawnPaths.empty();
}
boolean redo() {
if (!mRedoPaths.empty()) {
mDrawnPaths.push(mRedoPaths.pop());
invalidate();
}
if (mBrushViewChangeListener != null) {
mBrushViewChangeListener.onViewAdd(this);
}
return !mRedoPaths.empty();
}
private void touchStart(float x, float y) {
mRedoPaths.clear();
mPath.reset();
mPath.moveTo(x, y);
mTouchX = x;
mTouchY = y;
if (mBrushViewChangeListener != null) {
mBrushViewChangeListener.onStartDrawing();
}
}
private void touchMove(float x, float y) {
float dx = Math.abs(x - mTouchX);
float dy = Math.abs(y - mTouchY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mTouchX, mTouchY, (x + mTouchX) / 2, (y + mTouchY) / 2);
mTouchX = x;
mTouchY = y;
}
}
private void touchUp() {
mPath.lineTo(mTouchX, mTouchY);
// Commit the path to our offscreen
mDrawCanvas.drawPath(mPath, mDrawPaint);
// kill this so we don't double draw
mDrawnPaths.push(new BrushLinePath(mPath, mDrawPaint));
/////
final Bitmap scaledBitmap = getScaledBitmap();
final float centerX = scaledBitmap.getWidth() / 2;
final float centerY = scaledBitmap.getHeight() / 2;
final PathMeasure pathMeasure = new PathMeasure(mPath, false);
float distance = scaledBitmap.getWidth() / 2;
float[] position = new float[2];
float[] slope = new float[2];
float slopeDegree;
while (distance < pathMeasure.getLength())
{
pathMeasure.getPosTan(distance, position, slope);
slopeDegree = (float)((Math.atan2(slope[1], slope[0]) * 180f) / Math.PI);
mDrawCanvas.save();
mDrawCanvas.translate(position[0] - centerX, position[1] - centerY);
mDrawCanvas.rotate(slopeDegree, centerX, centerY);
mDrawCanvas.drawBitmap(scaledBitmap, 0, 0, mDrawPaint);
mDrawCanvas.restore();
distance += scaledBitmap.getWidth() + 10;
}
/////
mPath = new Path();
if (mBrushViewChangeListener != null) {
mBrushViewChangeListener.onStopDrawing();
mBrushViewChangeListener.onViewAdd(this);
}
}
#VisibleForTesting
Paint getDrawingPaint() {
return mDrawPaint;
}
#VisibleForTesting
Pair<Stack<BrushLinePath>, Stack<BrushLinePath>> getDrawingPath() {
return new Pair<>(mDrawnPaths, mRedoPaths);
}
}
public interface BrushViewChangeListener {
void onViewAdd(BrushDrawingView brushDrawingView);
void onViewRemoved(BrushDrawingView brushDrawingView);
void onStartDrawing();
void onStopDrawing();
}
class BrushLinePath {
private final Paint mDrawPaint;
private final Path mDrawPath;
BrushLinePath(final Path drawPath, final Paint drawPaints) {
mDrawPaint = new Paint(drawPaints);
mDrawPath = new Path(drawPath);
}
Paint getDrawPaint() {
return mDrawPaint;
}
Path getDrawPath() {
return mDrawPath;
}
}

The reason it happens is because Paint doesn't have an alpha composing mode set by default. Thus, when you're trying to paint a bitmap over your canvas it will replace the destination pixels with your brush pixels, which in your case is #00000000. And that will result in pixel being displayed as black. Have a look into this documentation: https://developer.android.com/reference/android/graphics/PorterDuff.Mode
By the first glance it seems you're looking for PorterDuff.Mode.SRC_OVER or PorterDuff.Mode.SRC_ATOP - this way transparent pixels from your source image (your brush) will not over-draw the pixels from your destination (canvas). In case your background is always non-transparent, you will see no difference between SRC_OVER and SRC_ATOP, but if it isn't - choose the one which fits your needs. Then you can modify setupPathAndPaint method by adding this line to its end:
mDrawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));

Related

creating scratch card android

I'm trying to implement a simple "scratch card". The java code is taken by an android demo about painting (WHERE to find Android "Fingerpaint" demo? (Android Studio era)) and what i want is simply to show the image below the surfaceview.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="#drawable/android"
android:scaleType="fitXY"
/>
<com.example.jammtup.ErasableView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/></RelativeLayout>
the java code :
public class ErasableView extends SurfaceView {
private Bitmap mBitmap;
private Canvas mCanvas;
private Paint mPaint;
private Path mPath;
private Paint mBitmapPaint;
public ErasableView(Context context) {
super(context);
mPaint = new Paint();
mPath = new Path();
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
}
public void init() {
setFocusableInTouchMode(true);
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(Color.TRANSPARENT);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(64);
mPaint.setColor(0xFFFF0000);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
canvas.drawPath(mPath, mPaint);
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
private void touch_start(float x, float y) {
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touch_move(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
private void touch_up() {
mPath.lineTo(mX, mY);
// commit the path to our offscreen
mCanvas.drawPath(mPath, mPaint);
// kill this so we don't double draw
mPath.reset();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
} }
what i get is a black view not erasable. That does not allow to show the image below.
i need your help !!
Try to use this instead of your view If you want the whole project just check out my Github repository https://github.com/amarvadla/ScratchCoupon
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.os.AsyncTask;
import android.support.v4.content.ContextCompat;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.widget.TextView;
public class ScratchTextView extends TextView {
Bitmap scratchBitmap;
public interface IRevealListener {
public void onRevealed(ScratchTextView tv);
public void onRevealPercentChangedListener(ScratchTextView stv, float percent);
}
public static final float STROKE_WIDTH = 12f;
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
/**
* Bitmap holding the scratch region.
*/
private Bitmap mScratchBitmap;
/**
* Drawable canvas area through which the scratchable area is drawn.
*/
public static Canvas mCanvas;
/**
* Path holding the erasing path done by the user.
*/
private Path mErasePath;
/**
* Path to indicate where the user have touched.
*/
private Path mTouchPath;
/**
* Paint properties for drawing the scratch area.
*/
private Paint mBitmapPaint;
/**
* Paint properties for erasing the scratch region.
*/
private Paint mErasePaint;
/**
* Gradient paint properties that lies as a background for scratch region.
*/
private Paint mGradientBgPaint;
/**
* Sample Drawable bitmap having the scratch pattern.
*/
private BitmapDrawable mDrawable;
/**
* Listener object callback reference to send back the callback when the text has been revealed.
*/
private IRevealListener mRevealListener;
/**
* Reveal percent value.
*/
private float mRevealPercent;
/**
* Thread Count
*/
private int mThreadCount = 0;
public ScratchTextView(Context context) {
super(context);
init();
}
public ScratchTextView(Context context, AttributeSet set) {
super(context, set);
init();
}
public ScratchTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* Set the strokes width based on the parameter multiplier.
* #param multiplier can be 1,2,3 and so on to set the stroke width of the paint.
*/
public void setStrokeWidth(int multiplier) {
mErasePaint.setStrokeWidth(multiplier * STROKE_WIDTH);
}
/**
* Initialises the paint drawing elements.
*/
private void init() {
mTouchPath = new Path();
mErasePaint = new Paint();
mErasePaint.setAntiAlias(true);
mErasePaint.setDither(true);
mErasePaint.setColor(0xFFFF0000);
mErasePaint.setStyle(Paint.Style.STROKE);
mErasePaint.setStrokeJoin(Paint.Join.BEVEL);
mErasePaint.setStrokeCap(Paint.Cap.ROUND);
mErasePaint.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.CLEAR));
setStrokeWidth(6);
mGradientBgPaint = new Paint();
mErasePath = new Path();
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
scratchBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.spiderman);
mDrawable = new BitmapDrawable(getResources(), scratchBitmap);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mScratchBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mScratchBitmap);
Rect rect = new Rect(0, 0, mScratchBitmap.getWidth(), mScratchBitmap.getHeight());
mDrawable.setBounds(rect);
int startGradientColor = ContextCompat.getColor(getContext(), R.color.colorPrimary);
int endGradientColor = ContextCompat.getColor(getContext(), R.color.colorAccent);
mCanvas.drawRect(rect, mGradientBgPaint);
mDrawable.draw(mCanvas);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(mScratchBitmap, 0, 0, mBitmapPaint);
canvas.drawPath(mErasePath, mErasePaint);
}
private void touch_start(float x, float y) {
mErasePath.reset();
mErasePath.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) {
mErasePath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
drawPath();
}
mTouchPath.reset();
mTouchPath.addCircle(mX, mY, 30, Path.Direction.CW);
}
private void drawPath() {
mErasePath.lineTo(mX, mY);
// commit the path to our offscreen
mCanvas.drawPath(mErasePath, mErasePaint);
// kill this so we don't double draw
mTouchPath.reset();
mErasePath.reset();
mErasePath.moveTo(mX, mY);
checkRevealed();
}
/**
* Reveals the hidden text by erasing the scratch area.
*/
public void reveal() {
int[] bounds = getTextBounds(1.5f);
int left = bounds[0];
int top = bounds[1];
int right = bounds[2];
int bottom = bounds[3];
Paint paint = new Paint();
paint.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.CLEAR));
mCanvas.drawRect(left, top, right, bottom, paint);
checkRevealed();
invalidate();
}
#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:
drawPath();
invalidate();
break;
default:
break;
}
return true;
}
public int getColor() {
return mErasePaint.getColor();
}
public void setRevealListener(IRevealListener listener) {
this.mRevealListener = listener;
}
public boolean isRevealed() {
return mRevealPercent == 1;
}
private void checkRevealed() {
if(! isRevealed() && mRevealListener != null) {
int[] bounds = getTextBounds();
int left = bounds[0];
int top = bounds[1];
int width = bounds[2] - left;
int height = bounds[3] - top;
// Do not create multiple calls to compare.
if(mThreadCount > 1) {
return;
}
mThreadCount++;
new AsyncTask<Integer, Void, Float>() {
#Override
protected Float doInBackground(Integer... params) {
try {
int left = params[0];
int top = params[1];
int width = params[2];
int height = params[3];
Bitmap croppedBitmap = Bitmap.createBitmap(mScratchBitmap, left, top, width, height);
return BitmapUtils.getTransparentPixelPercent(croppedBitmap);
} finally {
mThreadCount--;
}
}
public void onPostExecute(Float percentRevealed) {
// check if not revealed before.
if( ! isRevealed()) {
float oldValue = mRevealPercent;
mRevealPercent = percentRevealed;
if(oldValue != percentRevealed) {
mRevealListener.onRevealPercentChangedListener(ScratchTextView.this, percentRevealed);
}
// if now revealed.
if( isRevealed()) {
mRevealListener.onRevealed(ScratchTextView.this);
}
}
}
}.execute(left, top, width, height);
}
}
private static int[] getTextDimens(String text, Paint paint) {
int end = text.length();
Rect bounds = new Rect();
paint.getTextBounds(text, 0, end, bounds);
int width = bounds.left + bounds.width();
int height = bounds.bottom + bounds.height();
return new int[] { width, height};
}
private int[] getTextBounds() {
return getTextBounds(1f);
}
private int[] getTextBounds(float scale) {
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
int vwidth = getWidth();
int vheight = getHeight();
int centerX = vwidth/2;
int centerY = vheight/2;
TextPaint paint = getPaint();
String text = getText().toString();
int[] dimens = getTextDimens(text, paint);
int width = dimens[0];
int height = dimens[1];
int lines = getLineCount();
height = height * lines;
width = width / lines;
int left = 0;
int top = 0;
if(height > vheight) {
height = vheight - ( paddingBottom + paddingTop);
}
else {
height = (int) (height * scale);
}
if(width > vwidth) {
width = vwidth - (paddingLeft + paddingRight);
}
else {
width = (int) (width * scale);
}
int gravity = getGravity();
//todo Gravity.START
if((gravity & Gravity.LEFT) == Gravity.LEFT) {
left = paddingLeft;
}
//todo Gravity.END
else if((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
left = (vwidth - paddingRight) - width;
}
else if((gravity & Gravity.CENTER_HORIZONTAL) == Gravity.CENTER_HORIZONTAL) {
left = centerX - width / 2;
}
if((gravity & Gravity.TOP) == Gravity.TOP) {
top = paddingTop;
}
else if((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
top = (vheight - paddingBottom) - height;
}
else if((gravity & Gravity.CENTER_VERTICAL) == Gravity.CENTER_VERTICAL) {
top = centerY - height / 2;
}
return new int[] {left, top, left + width, top + height};
}

Customize a ProgressBar to become a Thermometer

How to customize a ProgressBar to look like a Thermometer ? with the possibility to change color.
My suggestion was to rotate the progressBar 90° to become vertical then have it overlay an image of an empty Thermometer but it's bad and messy solution.
I Think the best will be to either to extends View or ProgressBar class and customize the draw method but I have no idea how to draw Thermometer, any Help would be appreciated.
I created something like this for a project
package com.janslab.thermometer.widgets;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.Scroller;
import com.janslab.thermometer.R;
public class DummyThermometer extends View {
private Paint mInnerCirclePaint;
private Paint mOuterCirclePaint;
private Paint mFirstOuterCirclePaint;
//thermometer arc paint
private Paint mFirstOuterArcPaint;
//thermometer lines paints
private Paint mInnerLinePaint;
private Paint mOuterLinePaint;
private Paint mFirstOuterLinePaint;
//thermometer radii
private int mOuterRadius;
private int mInnerRadius;
private int mFirstOuterRadius;
//thermometer colors
private int mThermometerColor = Color.rgb(200, 115, 205);
//circles and lines variables
private float mLastCellWidth;
private int mStageHeight;
private float mCellWidth;
private float mStartCenterY; //center of first cell
private float mEndCenterY; //center of last cell
private float mStageCenterX;
private float mXOffset;
private float mYOffset;
// I 1st Cell I 2nd Cell I 3rd Cell I
private static final int NUMBER_OF_CELLS = 3; //three cells in all ie.stageHeight divided into 3 equal cells
//animation variables
private float mIncrementalTempValue;
private boolean mIsAnimating;
private Animator mAnimator;
public DummyThermometer(Context context) {
this(context, null);
}
public DummyThermometer(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DummyThermometer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (attrs != null) {
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Thermometer, defStyle, 0);
mThermometerColor = a.getColor(R.styleable.Thermometer_therm_color, mThermometerColor);
a.recycle();
}
init();
}
private void init() {
mInnerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mInnerCirclePaint.setColor(mThermometerColor);
mInnerCirclePaint.setStyle(Paint.Style.FILL);
mInnerCirclePaint.setStrokeWidth(17f);
mOuterCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mOuterCirclePaint.setColor(Color.WHITE);
mOuterCirclePaint.setStyle(Paint.Style.FILL);
mOuterCirclePaint.setStrokeWidth(32f);
mFirstOuterCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mFirstOuterCirclePaint.setColor(mThermometerColor);
mFirstOuterCirclePaint.setStyle(Paint.Style.FILL);
mFirstOuterCirclePaint.setStrokeWidth(60f);
mFirstOuterArcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mFirstOuterArcPaint.setColor(mThermometerColor);
mFirstOuterArcPaint.setStyle(Paint.Style.STROKE);
mFirstOuterArcPaint.setStrokeWidth(30f);
mInnerLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mInnerLinePaint.setColor(mThermometerColor);
mInnerLinePaint.setStyle(Paint.Style.FILL);
mInnerLinePaint.setStrokeWidth(17f);
mOuterLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mOuterLinePaint.setColor(Color.WHITE);
mOuterLinePaint.setStyle(Paint.Style.FILL);
mFirstOuterLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mFirstOuterLinePaint.setColor(mThermometerColor);
mFirstOuterLinePaint.setStyle(Paint.Style.FILL);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mStageCenterX = getWidth() / 2;
mStageHeight = getHeight();
mCellWidth = mStageHeight / NUMBER_OF_CELLS;
//center of first cell
mStartCenterY = mCellWidth / 2;
//move to 3rd cell
mLastCellWidth = (NUMBER_OF_CELLS * mCellWidth);
//center of last(3rd) cell
mEndCenterY = mLastCellWidth - (mCellWidth / 2);
// mOuterRadius is 1/4 of mCellWidth
mOuterRadius = (int) (0.25 * mCellWidth);
mInnerRadius = (int) (0.656 * mOuterRadius);
mFirstOuterRadius = (int) (1.344 * mOuterRadius);
mFirstOuterLinePaint.setStrokeWidth(mFirstOuterRadius);
mOuterLinePaint.setStrokeWidth(mFirstOuterRadius / 2);
mFirstOuterArcPaint.setStrokeWidth(mFirstOuterRadius / 4);
mXOffset = mFirstOuterRadius / 4;
mXOffset = mXOffset / 2;
//get the d/f btn firstOuterLine and innerAnimatedline
mYOffset = (mStartCenterY + (float) 0.875 * mOuterRadius) - (mStartCenterY + mInnerRadius);
mYOffset = mYOffset / 2;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawFirstOuterCircle(canvas);
drawOuterCircle(canvas);
drawInnerCircle(canvas);
drawFirstOuterLine(canvas);
drawOuterLine(canvas);
animateInnerLine(canvas);
drawFirstOuterCornerArc(canvas);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//take care of paddingTop and paddingBottom
int paddingY = getPaddingBottom() + getPaddingTop();
//get height and width
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
height += paddingY;
setMeasuredDimension(width, height);
}
private void drawInnerCircle(Canvas canvas) {
drawCircle(canvas, mInnerRadius, mInnerCirclePaint);
}
private void drawOuterCircle(Canvas canvas) {
drawCircle(canvas, mOuterRadius, mOuterCirclePaint);
}
private void drawFirstOuterCircle(Canvas canvas) {
drawCircle(canvas, mFirstOuterRadius, mFirstOuterCirclePaint);
}
private void drawCircle(Canvas canvas, float radius, Paint paint) {
canvas.drawCircle(mStageCenterX, mEndCenterY, radius, paint);
}
private void drawOuterLine(Canvas canvas) {
float startY = mEndCenterY - (float) (0.875 * mOuterRadius);
float stopY = mStartCenterY + (float) (0.875 * mOuterRadius);
drawLine(canvas, startY, stopY, mOuterLinePaint);
}
private void drawFirstOuterLine(Canvas canvas) {
float startY = mEndCenterY - (float) (0.875 * mFirstOuterRadius);
float stopY = mStartCenterY + (float) (0.875 * mOuterRadius);
drawLine(canvas, startY, stopY, mFirstOuterLinePaint);
}
private void drawLine(Canvas canvas, float startY, float stopY, Paint paint) {
canvas.drawLine(mStageCenterX, startY, mStageCenterX, stopY, paint);
}
//simulate temperature measurement for now
private void animateInnerLine(Canvas canvas) {
if (mAnimator == null)
measureTemperature();
if (!mIsAnimating) {
mIncrementalTempValue = mEndCenterY + (float) (0.875 * mInnerRadius);
mIsAnimating = true;
} else {
mIncrementalTempValue = mEndCenterY + (float) (0.875 * mInnerRadius) - mIncrementalTempValue;
}
if (mIncrementalTempValue > mStartCenterY + mInnerRadius) {
float startY = mEndCenterY + (float) (0.875 * mInnerRadius);
drawLine(canvas, startY, mIncrementalTempValue, mInnerCirclePaint);
} else {
float startY = mEndCenterY + (float) (0.875 * mInnerRadius);
float stopY = mStartCenterY + mInnerRadius;
drawLine(canvas, startY, stopY, mInnerCirclePaint);
mIsAnimating = false;
stopMeasurement();
}
}
private void drawFirstOuterCornerArc(Canvas canvas) {
float y = mStartCenterY - (float) (0.875 * mFirstOuterRadius);
RectF rectF = new RectF(mStageCenterX - mFirstOuterRadius / 2 + mXOffset, y + mFirstOuterRadius, mStageCenterX + mFirstOuterRadius / 2 - mXOffset, y + (2 * mFirstOuterRadius) + mYOffset);
canvas.drawArc(rectF, -180, 180, false, mFirstOuterArcPaint);
}
public void setThermometerColor(int thermometerColor) {
this.mThermometerColor = thermometerColor;
mInnerCirclePaint.setColor(mThermometerColor);
mFirstOuterCirclePaint.setColor(mThermometerColor);
mFirstOuterArcPaint.setColor(mThermometerColor);
mInnerLinePaint.setColor(mThermometerColor);
mFirstOuterLinePaint.setColor(mThermometerColor);
invalidate();
}
//simulate temperature measurement for now
private void measureTemperature() {
mAnimator = new Animator();
mAnimator.start();
}
private class Animator implements Runnable {
private Scroller mScroller;
private final static int ANIM_START_DELAY = 1000;
private final static int ANIM_DURATION = 4000;
private boolean mRestartAnimation = false;
public Animator() {
mScroller = new Scroller(getContext(), new AccelerateDecelerateInterpolator());
}
public void run() {
if (mAnimator != this)
return;
if (mRestartAnimation) {
int startY = (int) (mStartCenterY - (float) (0.875 * mInnerRadius));
int dy = (int) (mEndCenterY + mInnerRadius);
mScroller.startScroll(0, startY, 0, dy, ANIM_DURATION);
mRestartAnimation = false;
}
boolean isScrolling = mScroller.computeScrollOffset();
mIncrementalTempValue = mScroller.getCurrY();
if (isScrolling) {
invalidate();
post(this);
} else {
stop();
}
}
public void start() {
mRestartAnimation = true;
postDelayed(this, ANIM_START_DELAY);
}
public void stop() {
removeCallbacks(this);
mAnimator = null;
}
}
private void stopMeasurement() {
if (mAnimator != null)
mAnimator.stop();
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
measureTemperature();
}
#Override
protected void onDetachedFromWindow() {
stopMeasurement();
super.onDetachedFromWindow();
}
#Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
switch (visibility) {
case View.VISIBLE:
measureTemperature();
break;
default:
stopMeasurement();
break;
}
}
}
attrs.xml file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Thermometer">
<attr name="therm_color" format="color" />
</declare-styleable>
</resources>
First I would provide 2 setters, one for color and one for the temperature value, normalized from 0 ... 1, where 0 means no visible bar, and 1 means a fully visible bar.
public void setColor(int color) {
mColor = color;
invalidate(); // important, this triggers onDraw
}
public void setValue(float value) {
mValue = -(value - 1);
invalidate(); // important, this triggers onDraw
}
Notice for value, I reverse the value, since we draw the bar from bottom up, instead from top down. It makes sense in the canvas.drawRect method.
If your CustomView may have custom sizes, set your size of the progressBar (I refer to the inner bar as progressBar) in onSizeChanged, as this gets called when the View has changed it's size.
If it is a fixed size, you can just provide those values statically in an init function or the constructor.
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mProgressRect = new Rect(
/*your bar left offset relative to base bitmap*/,
/*your bar top offset relative to base bitmap*/,
/*your bar total width*/,
/*your max bar height*/
);
}
Then in ondraw, take these values into account and draw accordingly.
First draw the Bitmap, depending on your selected color (I would provide the thermometer base as a Bitmap, as long as it does not have to be completely dynamically drawn (special requirements)
Then draw the progress bar, with an height based on mValue * totalHeight of the bar, using the color provided in the setter.
For example:
#Override
protected void onDraw(Canvas canvas) {
// draw your thermometer base, bitmap based on color value
canvas.drawBitmap( /*your base thermometer bitmap here*/ );
// draw the "progress"
canvas.drawRect(mProgressRect.left, mProgressRect.top + (mValue * mProgressRect.bottom - mProgressRect.top), mProgressRect.right, mProgressRect.bottom, mPaint);
}
Hope that helps.
P.S.:
If you want to have the thermometer base image also dynamically drawn, it's a slightly different story, it would involve creating a path first and draw it with a Paint object, instead of drawing the bitmap.
EDIT:
Even better, if you want a simple solution for the "roundness" of the bar, draw a line instead a rect.
Define a line paint object like this:
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(20); // thickness of your bar
then in onDraw, instead drawRect:
// draw the "progress"
canvas.drawLine(mProgressRect.left, mProgressRect.top + (mValue * mProgressRect.bottom - mProgressRect.top), mProgressRect.left, mProgressRect.bottom, mPaint);
Be sure to adjust your mProgressRectaccordingly.

Android canvas scroll with boundaries when zoom in

I have a canvas and I am able to zoom out and scroll with boundaries, the code for that is here
package com.mypackage.ui.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.provider.SyncStateContract;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
/**
*
*/
public class MyDrawingView extends View {
private static final int INVALID_POINTER_ID = -1;
private int state = 0; //0 se dibuja - 1 se mueve
//drawing path
private Path drawPath;
//drawing and canvas paint
private Paint drawPaint, canvasPaint;
//canvas
private Canvas drawCanvas;
//canvas bitmap
private Bitmap bitmap;
protected String tool;
protected String color;
protected float brushSize;
//erase flag
private boolean erase=false;
public String getTool() {
return tool;
}
public void setTool(String tool) {
this.tool = tool;
}
public String getColor() {
return color;
}
public float getBrushSize() {
return brushSize;
}
public boolean isErase() {
return erase;
}
private String drawId;
// public OnSaveActionListener listener;
private float mPosX;
private float mPosY;
public float getMPosX() { return mPosX; }
public float getMPosY() { return mPosY; }
private float mLastTouchX;
private float mLastTouchY;
private int mActivePointerId = INVALID_POINTER_ID;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
private float viewHeight;
private float viewWidth;
float canvasWidth, canvasHeight;
private float minScaleFactor;
private boolean panEnabled = true;
private boolean zoomEnabled = true;
public MyDrawingView(Context context) {
super(context);
setup();
}
public MyDrawingView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setup();
}
public MyDrawingView(Context context, AttributeSet attrs){
super(context, attrs);
setup();
}
//setup drawing
private void setup(){
mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
//prepare for drawing and setup paint stroke properties
drawPath = new Path();
drawPaint = new Paint();
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
public void setState(int state) {
this.state = state;
}
public void setImageBitmap(Bitmap bmp) {
bitmap = bmp;
resetZoom();
resetPan();
invalidate();
}
public void setImageDrawable(Drawable drawable) {
setImageBitmap(((BitmapDrawable) drawable).getBitmap());
}
public void setBitmap(Bitmap bmp) {
setImageBitmap(bmp);
}
public void resetZoom() {
mScaleFactor = 1.0f;
}
public void resetPan() {
mPosX = 0f;
mPosY = 0f;
}
public BitmapDrawable getImageDrawable() {
BitmapDrawable bd = new BitmapDrawable(getContext().getResources(), bitmap);
return bd;
}
public BitmapDrawable getDrawable() {
return getImageDrawable();
}
//size assigned to view
#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);
drawCanvas = new Canvas(bitmap);
}
//draw the view - will be called after touch event
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mScaleFactor = Math.max(mScaleFactor, minScaleFactor);
canvasHeight = canvas.getHeight();
canvasWidth = canvas.getWidth();
//Save the canvas without translating (panning) or scaling (zooming)
//After each change, restore to this state, instead of compounding
//changes upon changes
canvas.save();
int maxX, minX, maxY, minY;
//Regardless of the screen density (HDPI, MDPI) or the scale factor,
//The image always consists of bitmap width divided by 2 pixels. If an image
//is 200 pixels wide and you scroll right 100 pixels, you just scrolled the image
//off the screen to the left.
minX = (int) (((viewWidth / mScaleFactor) - bitmap.getWidth()) / 2);
maxX = 0;
//How far can we move the image vertically without having a gap between image and frame?
minY = (int) (((viewHeight / mScaleFactor) - bitmap.getHeight()) / 2);
maxY = 0;
//Do not go beyond the boundaries of the image
if (mPosX > maxX) {
mPosX = maxX;
}
if (mPosX < minX) {
mPosX = minX;
}
if (mPosY > maxY) {
mPosY = maxY;
}
if (mPosY < minY) {
mPosY = minY;
}
if (state == 0) {
} else if (state == 1) {
canvas.scale(this.mScaleFactor, this.mScaleFactor, this.mScaleDetector.getFocusX(), this.mScaleDetector.getFocusY());
}
canvas.translate(mPosX, mPosY);
canvas.drawBitmap(bitmap, 0, 0, canvasPaint);
canvas.drawPath(drawPath, drawPaint);
canvas.restore();
invalidate();
}
//register user touches as drawing action
#Override
public boolean onTouchEvent(MotionEvent event) {
mScaleDetector.onTouchEvent(event);
float touchX = event.getX();
float touchY = event.getY();
if(isErase()){
drawCanvas.drawPath(drawPath, drawPaint);
invalidate();
}
//respond to down, move and up events
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastTouchX = touchX;
mLastTouchY = touchY;
mActivePointerId = event.getPointerId(0);
break;
case MotionEvent.ACTION_MOVE:
if (state == 0) {
} else if (state == 1) {
final int pointerIndex = event.findPointerIndex(mActivePointerId);
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
// Only move if the ScaleGestureDetector isn't processing a gesture.
if (!mScaleDetector.isInProgress()) {
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
mPosX += dx;
mPosY += dy;
invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
}
break;
case MotionEvent.ACTION_UP:
mActivePointerId = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_CANCEL:
mActivePointerId = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_UP:
final int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastTouchX = event.getX(newPointerIndex);
mLastTouchY = event.getY(newPointerIndex);
mActivePointerId = event.getPointerId(newPointerIndex);
}
break;
default:
return false;
}
return true;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
invalidate();
return true;
}
}
}
The code is above is an adaptation that worked for me from the answer here
The problem I have is when I zoom in I can't scroll to any place of the image, is like the corners are unreacheable. I know I am making a mistake with the boundaries but I can't realize how to solve it
Thank you

Changing background color of SignatureView

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();
}
}

How to set ontouch listener for something drawn using canvas: Android

I have a custom view in which i am drawing one big circle and a small circle on the edge of this big circle.
I would like to move the small circle and so would like to have a ontouch listener only for the small circle.
Could some please tell me how to set the ontouch listener for only the small circle.
public class ThermoView extends View{
private ImageView mThermostatBgrd;
private ImageView mCurTempArrow;
private ImageView mSetPointIndicator;
public static final int THEMROSTAT_BACKGROUND = 0;
public static final int THEMROSTAT_CURR_TEMP = 1;
public static final int THEMROSTAT_SET_POINT = 2;
private float mViewCentreX;
private float mViewCentreY;
private float mThermostatRadius;
private Canvas mCanvas;
private Paint mPaint;
private Paint mPaintCurTemp;
private Paint mPaintSetTemp;
private Paint mPaintOverrideTemp;
private Paint mPaintCurTempIndicator;
private Boolean mManualOverride = false;
private double mManualOverrideAngle;
private int mMaxTemp = 420;
private int mMinTemp = 120;
private RectF mCurrTempBox;
private float mCurTempCircleX;
private float mCurTempCircleY;
private Matrix mMatrix;
public double getManualOverrideAngle() {
return mManualOverrideAngle;
}
public void setManualOverrideAngle(double mManualOverrideAngle) {
this.mManualOverrideAngle = mManualOverrideAngle;
}
public RectF getCurrTempBox() {
if (mCurrTempBox == null){
mCurrTempBox = new RectF();
}
return mCurrTempBox;
}
public void setCurrTempBox(RectF mCurrTempBox) {
this.mCurrTempBox = mCurrTempBox;
}
public Boolean getManualOverride() {
return mManualOverride;
}
public void setManualOverride(Boolean mManualOverride) {
this.mManualOverride = mManualOverride;
}
public ThermoView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Path smallCirle = new Path();
int viewWidth = getMeasuredWidth();
int viewHeight = getMeasuredHeight();
mViewCentreX = viewWidth/2;
mViewCentreY = viewHeight/2;
float paddingPercent = 0.2f;
int thermostatThickness = 20;
mThermostatRadius = (int) ((Math.min(mViewCentreX, mViewCentreY)*(1- paddingPercent)));
if (mPaint == null){
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(thermostatThickness);
mPaint.setColor(0xffff0000);
Path arcPath = new Path();
RectF container = new RectF();
container.set(mViewCentreX - mThermostatRadius, mViewCentreY - mThermostatRadius,
mViewCentreX + mThermostatRadius, mViewCentreY + mThermostatRadius);
arcPath.addArc(container, 120, 300);
canvas.drawPath(arcPath, mPaint);
int dummyCurTemp = 200;
if (mPaintCurTemp == null){
mPaintCurTemp = new Paint(Paint.ANTI_ALIAS_FLAG);
}
mPaintCurTemp.setTextAlign(Align.CENTER);
mPaintCurTemp.setTextSize(100);
canvas.drawText(String.valueOf(dummyCurTemp), mViewCentreX, mViewCentreY, mPaintCurTemp);
if (this.mManualOverride == false){
double angle = (360-120-(300/(mMaxTemp - mMinTemp))*(dummyCurTemp-mMinTemp))*(Math.PI/180);
this.mCurTempCircleX = (float) (mViewCentreX + mThermostatRadius*(Math.cos(angle)));
this.mCurTempCircleY = (float) (mViewCentreY - mThermostatRadius*(Math.sin(angle)));
if (mCurrTempBox == null){
mCurrTempBox = new RectF();
}
if (mPaintCurTempIndicator == null){
mPaintCurTempIndicator = new Paint(Paint.ANTI_ALIAS_FLAG);
}
mPaintCurTempIndicator.setStyle(Paint.Style.STROKE);
mPaintCurTempIndicator.setStrokeWidth(thermostatThickness/2);
mPaintCurTempIndicator.setColor(Color.GREEN);
mCurrTempBox.set(mCurTempCircleX-50, mCurTempCircleY-50, mCurTempCircleX+50, mCurTempCircleY+50);
smallCirle.addCircle(mCurTempCircleX, mCurTempCircleY, 50, Direction.CW);
canvas.drawPath(smallCirle, mPaintCurTempIndicator);
}else{
if (mCurrTempBox == null){
mCurrTempBox = new RectF();
}
if (mPaintCurTempIndicator == null){
mPaintCurTempIndicator = new Paint(Paint.ANTI_ALIAS_FLAG);
}
if (mMatrix == null){
mMatrix = new Matrix();
}
//mMatrix.reset();
mMatrix.postRotate((float) (mManualOverrideAngle), mViewCentreX,mViewCentreY);
//mMatrix.postTranslate(mViewCentreX, mViewCentreY);
mPaintCurTempIndicator.setStyle(Paint.Style.STROKE);
mPaintCurTempIndicator.setStrokeWidth(thermostatThickness/2);
mPaintCurTempIndicator.setColor(Color.GREEN);
canvas.concat(mMatrix);
smallCirle.addCircle(mCurTempCircleX, mCurTempCircleY, 50, Direction.CW);
canvas.drawPath(smallCirle, mPaintCurTempIndicator);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mInitialX = event.getX();
mInitialY = event.getY();
RectF touchedAt = new RectF(mInitialX-10, mInitialY-10, mInitialX+10, mInitialY+10);
RectF indicatorAt = mThermoStatView.getCurrTempBox();
if (RectF.intersects(indicatorAt, touchedAt)){
this.isIndicatorSelected = true;
mThermoStatView.setManualOverride(true);
}
break;
case MotionEvent.ACTION_MOVE:
if (this.isIndicatorSelected == true){
float angle = (float) (180*Math.atan2(event.getY() - mThermostatHeight/2, event.getX() - mThermostatWidth/2) / Math.PI);
mThermoStatView.setManualOverrideAngle(angle);
mThermoStatView.invalidate();
//mThermoStatView.requestLayout();
}
break;
case MotionEvent.ACTION_UP:
if (this.isIndicatorSelected == true){
this.isIndicatorSelected = false;
}
break;
}
return true;
}
}
try this (this is a little modified version of MyView i already posted as an answer for your previous question):
public class MyView extends View {
private final static String TAG = "Main.MyView";
private static final float CX = 0;
private static final float CY = 0;
private static final float RADIUS = 20;
private static final float BIGRADIUS = 50;
private static final int NORMAL_COLOR = 0xffffffff;
private static final int PRESSED_COLOR = 0xffff0000;
private Paint mPaint;
private Path mSmallCircle;
private Path mCircle;
private Matrix mMatrix;
private float mAngle;
private int mSmallCircleColor;
public MyView(Context context) {
super(context);
mPaint = new Paint();
mSmallCircle = new Path();
mSmallCircle.addCircle(BIGRADIUS + RADIUS + CX, CY, RADIUS, Direction.CW);
mSmallCircleColor = NORMAL_COLOR;
mCircle = new Path();
mCircle.addCircle(0, 0, BIGRADIUS, Direction.CW);
mMatrix = new Matrix();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_UP) {
mSmallCircleColor = NORMAL_COLOR;
invalidate();
return false;
}
float w2 = getWidth() / 2f;
float h2 = getHeight() / 2f;
float r = 0;
if (action == MotionEvent.ACTION_DOWN) {
float[] pts = {
BIGRADIUS + RADIUS + CX, CY
};
mMatrix.mapPoints(pts);
r = (float) Math.hypot(event.getX() - pts[0], event.getY() - pts[1]);
}
if (r < RADIUS) {
mSmallCircleColor = PRESSED_COLOR;
mAngle = (float) (180 * Math.atan2(event.getY() - h2, event.getX() - w2) / Math.PI);
invalidate();
return true;
}
return false;
}
#Override
protected void onDraw(Canvas canvas) {
float w2 = getWidth() / 2f;
float h2 = getHeight() / 2f;
mMatrix.reset();
mMatrix.postRotate(mAngle);
mMatrix.postTranslate(w2, h2);
canvas.concat(mMatrix);
mPaint.setColor(0x88ffffff);
canvas.drawPath(mCircle, mPaint);
mPaint.setColor(mSmallCircleColor);
canvas.drawPath(mSmallCircle, mPaint);
}
}
You can get the rectangle of your touch point and the rectangle(position) of your inner circle and check if they cross over with the Intersects method.
http://docs.oracle.com/javase/7/docs/api/java/awt/Rectangle.html#intersects(java.awt.Rectangle)
Your canvas onTouchListener can do whatever it needs to do if the touchpoint intersect your circle.
e.g:
// Create a rectangle from the point of touch
Rect touchpoint = new Rect(x,y,10,10);
// Create a rectangle from the postion of the circle.
Rect myCircle=new Rect(10,10,20,20);
if (Rect.intersects(myCircle,touchpoint)){
Log.d("The circle was touched");
}

Categories

Resources