Reputation: 2647
I have a custom view that allows me to draw a bitmap on it when it is created, and then the user can draw on it with their finger.
My issue is that the canvas fills the whole width/height of the available area in my layout, but I want it to simply match the size of the bitmap that is drawn so that the user can only draw on the picture. This seems like it needs to be done in onSizeChanged since I need to resize the bitmap if they go to landscape from portrait. etc.
My code:
class DrawingCameraPanel extends View implements OnTouchListener {
public Canvas mCanvas;
private Path mPath;
private Paint mPaint;
private ArrayList<Path> paths = new ArrayList<Path>();
private Bitmap background;
int width;
int height;
public Bitmap getOutputBitmap() {
Bitmap.Config config = Bitmap.Config.RGB_565;
Bitmap bitmap = Bitmap.createBitmap(width, height, config);
Canvas canvas = new Canvas(bitmap);
onDraw(canvas);
return bitmap;
}
public DrawingCameraPanel(Context context) {
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(6);
}
public DrawingCameraPanel(Context context, AttributeSet attrs) {
super(context, attrs);
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(6);
}
public DrawingCameraPanel(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(6);
}
public void setPaintColor(int color) {
mPaint.setColor(color);
}
public void setBackgroundPic(Bitmap bitmap) {
this.background = bitmap;
mCanvas = new Canvas();
mPath = new Path();
paths.add(mPath);
mCanvas.save();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
background = resizeImage(background, w, h);
}
@Override
protected void onDraw(Canvas canvas) {
if (background != null) {
canvas.drawBitmap(background, 0, 0, null);
}
for (Path p : paths) {
canvas.drawPath(p, mPaint);
}
}
public Bitmap resizeImage(Bitmap image, int maxWidth, int maxHeight) {
Bitmap resizedImage = null;
try {
int imageHeight = image.getHeight();
if (imageHeight > maxHeight)
imageHeight = maxHeight;
int imageWidth = (imageHeight * image.getWidth())
/ image.getHeight();
if (imageWidth > maxWidth) {
imageWidth = maxWidth;
imageHeight = (imageWidth * image.getHeight())
/ image.getWidth();
}
if (imageHeight > maxHeight)
imageHeight = maxHeight;
if (imageWidth > maxWidth)
imageWidth = maxWidth;
resizedImage = Bitmap.createScaledBitmap(image, imageWidth,
imageHeight, true);
} catch (OutOfMemoryError e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return resizedImage;
}
public void clear() {
this.paths.clear();
mPath = new Path();
paths.add(mPath);
}
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 = new Path();
paths.add(mPath);
}
@Override
public boolean onTouch(View arg0, 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;
}
Upvotes: 0
Views: 2072
Reputation: 16054
Of course, why shouldn't Canvas
take all space in the layout if its width and height is (most probably) set to match_parent
or fill_parent
? (Well, technically: View
- it's the View that's adjusting its bounds and the canvas just "follows" them).
I think you're reinventing the wheel here. Instead of writing all of the code yourself, I would suggest to:
ImageView
instead of View
.setAdjustViewBounds(true)
in the constructor. This is the important part. This will actually change the bounds of the view to match the contained image - it preserves aspect ratio and will ensure the image fits in the space the layout is offering.setBackgroundPic()
to simply call ImageView.setImageBitmap()
since the background on which the user draws is image source from ImageView
perspective.OnTouchListener
implementation.What is cool here is that the your custom view is now automatically resized. So, you don't have to worry about the bounds in which the user is allowed to paint because they will be set for you.
Upvotes: 1