I am working on a drawing app. And I already create a draw view that allow user to draw on the view.
The problem is , when I draw on it , it exceed the area of the image (please refer to the picture, the yellow line is exceeed the actual photo area), how can I stick the canvas size to the actual image ?
And how the imageview be zoomable? That means when user click on pen button, it draw, when user click again then it become zoom function.
Thanks. The zoom function can be tackle later and the problem of exceed area need to fix first
Here is the custom draw view in xml
<com.example.tool.DrawView
android:id="#+id/draw"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Here is the java
public class DrawView extends ImageView {
private int color = Color.BLACK;
private float width = 4f;
private List<Holder> holderList = new ArrayList<Holder>();
private class Holder {
Path path;
Paint paint;
Holder(int color, float width) {
path = new Path();
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(width);
paint.setColor(color);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
}
}
public DrawView(Context context) {
super(context);
init();
}
public DrawView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DrawView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
holderList.add(new Holder(color, width));
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (Holder holder : holderList) {
canvas.drawPath(holder.path, holder.paint);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float eventX = event.getX();
float eventY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
holderList.add(new Holder(color,width));
holderList.get(holderList.size() - 1).path.moveTo(eventX, eventY);
return true;
case MotionEvent.ACTION_MOVE:
holderList.get(holderList.size() - 1).path.lineTo(eventX, eventY);
break;
case MotionEvent.ACTION_UP:
break;
default:
return false;
}
invalidate();
return true;
}
public void resetPaths() {
for (Holder holder : holderList) {
holder.path.reset();
}
invalidate();
}
public void setBrushColor(int color) {
this.color = color;
}
public void setWidth(float width) {
this.width = width;
}
}
And I call it by:
DrawView pic = findViewById(R.id.draw);
pic.setImageBitmap(bitmap);
Thanks a lot for helping
Calculate the displayed image size first and then clamp the eventX and eventY to the bound of the displayed image. Code as below:
int imageViewH=imageView.getMeasuredHeight();//height of imageView
int imageViewW =imageView.getMeasuredWidth();//width of imageView
int drawableH =imageView.getDrawable().getIntrinsicHeight();//original height of underlying image
int drawableW=imageView.getDrawable().getIntrinsicWidth();//original width of underlying image
int displayH, displayW; // the shown height and width of the picture.
int leftX, rightX, topY, bottomY; // the shown edges of the picture.
if (imageViewH/drawableH <= imageViewW/drawableW){
displayW = drawableW*imageViewH/drawableH;//rescaled width of image within ImageView
displayH = imageViewH;
leftX = (imageViewW - displayW)/2; // left edge of the displayed image.
rightX = leftX + displayW; // right edge of the displayed image.
topY = 0; // top edg
bottomY = displayH; // bottom edge.
}else{
displayH = drawableH*imageViewW/drawableH;//rescaled height of image within ImageView
displayW = imageViewW;
leftX = 0; // left edge of the displayed image.
rightX = displayW; // right edge of the displayed image.
topY = (imageViewH - displayH)/2; // top edg
bottomY = topY + displayH; // bottom edge.
}
//TODO: clamp the eventX and eventY to the bound of leftX, rightX, topY, bottomY
Note: the code should be used only after your ImageView has its measured height and a drawable.
You can set a width of your DrawView programmatically to ImageView's width, if in ImageView's properties there's a true attribute adjustViewBounds.
Related
I implemented custom ImageView. Because my image is large I'm using ScrollView and HorizontalScrollView to scrolling image. I want to draw circle in onTouch event, but its not work in all cases. When I scroll image and fire touch event the x,y coordinates apply to current visible sector of image ( in top right corner its e.g. 0x0, but when I scroll somwhere and in visible top corner it will be 0x0 too), but circle is drawing according to image size.
Another problem is that app could be used on various screen size.
I know that it xamarin c# but in native Android it should be the same. Anyone knows how to draw/get coordinates properly?
public class DrawViewInside : ImageView
{
public static Bitmap bitmapInside;
private Paint paint = new Paint();
private Point point = new Point();
private Canvas mCanvas;
private static Bitmap mutableBitmap;
private Context mContext;
public static Bitmap b;
public void SetCarId(int id)
{
carId = id;
}
public DrawViewInside(Context context, IAttributeSet attrs) : base(context, attrs)
{
mContext = context;
setDefault(drawable);
}
public void SetBitmap(int drawableId)
{
setDefault(drawableId);
Invalidate();
}
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
base.OnMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.GetSize(widthMeasureSpec);
int height = width;
var metrics = Resources.DisplayMetrics;
if (b != null)
{
SetMeasuredDimension(b.Width, b.Height);
}
}
public async void setDefault(int drawableId)
{
BitmapFactory.Options options = await GetBitmapOptionsOfImage(drawableId);
paint.Color = Color.Red;
paint.StrokeWidth = 15;
paint.SetStyle(Paint.Style.Stroke);
var metrics = Resources.DisplayMetrics;
var widthInDp = ConvertPixelsToDp(metrics.WidthPixels);
var heightInDp = ConvertPixelsToDp(metrics.HeightPixels);
b = BitmapFactory.DecodeResource(Resources, drawableId);esources, drawableId);
mutableBitmap = b.Copy(Bitmap.Config.Argb8888, true);
mCanvas = new Canvas(mutableBitmap);
mCanvas.Save();
}
protected override void OnDraw(Canvas canvas)
{
DrawCircle(canvas);
}
private void DrawCircle(Canvas canvas)
{
if (mCanvas == null)
{
setDefault(drawable);
}
else
{
mCanvas.Restore();
mCanvas.DrawCircle(point.x, point.y, 10, paint);
mCanvas.Save();
canvas.DrawBitmap(mutableBitmap, 0, 0, paint);
bitmapInside = mutableBitmap;
}
}
public override bool OnTouchEvent(MotionEvent e)
{
switch (e.Action)
{
case MotionEventActions.Down:
break;
case MotionEventActions.Up:
Activity act = (Activity)mContext;
float viewX = e.RawX - this.Left;
float viewY = e.RawY - this.Top;
point.x = viewX;// e.RawX;
point.y = viewY;// e.RawY;
break;
}
Invalidate();
return true;
}
}
public class Point
{
public float x, y;
}
I found solution. Just replace
float viewX = e.RawX - this.Left;
float viewY = e.RawY - this.Top;
point.x = viewX;// e.RawX;
point.y = viewY;// e.RawY;
with
float viewX = e.RawX;// - this.Left;
float viewY = e.RawY;// - this.Top;
int[] viewCoords = new int[2];
this.GetLocationOnScreen(viewCoords);
point.x = viewX - viewCoords[0];// e.RawX;
point.y = viewY - viewCoords[1];// e.RawY;
I am writing a program that draws a circle at a random location on the phone screen. What it is supposed to do is when the circle is touched, it removes that circle and moves it to another random location.
Currently, when the screen is tapped the old circle is removed, and redrawn at another random location on the screen. It is very close, but I was wondering how I get over this hurdle.
public class Circle extends View {
private float x = 300;
private float y = 300;
private int r = 150;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Random random = new Random();
// draws circle
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.RED);
canvas.drawCircle(x, y, r, mPaint);
}
// constructors
public Circle(Context context) {
super(context);
init();
}
public Circle(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public Circle(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
void init() {
// might be used later for some data collection
}
// gets random number,,
void generateRandom() {
int w = getWidth() - r - 50;
int h = getHeight()- r - 50;
int border = r + 50;
this.x = border + random.nextInt(w-border);
this.y = border + random.nextInt(h-border);
}
// when screen is tapped, old circle removed, new circle drawn
#Override
public boolean onTouchEvent(MotionEvent event) {
generateRandom();
invalidate();
return super.onTouchEvent(event);
}
}
Change the code like this ,
#Override
public boolean onTouchEvent(MotionEvent event) {
if (isInsideCircle(event.getX(), event.getY())) {
generateRandom();
invalidate();
}
return super.onTouchEvent(event);
}
boolean isInsideCircle(float xPoint, float yPoint) {
float dx = (x - xPoint);
float dxPow = (float) Math.pow(dx, 2);
float dy = (y - yPoint);
float dyPow = (float) Math.pow(dy, 2);
float radPow = (float) Math.pow(r, 2);
return (dxPow + dyPow) < radPow || (dxPow + dyPow == radPow);
}
I'm making an application which can crop the image of the person. The layout of my application will give a better description
Here the rectangle will be used to capture the image defined by its length and width. The rectangle is movable. How would I go about re-sizing the rectangle. For eg. in WhatsApp when you touch the region inside the rectangle, the rectangle moves. If you touch the edges of rectangle, it provides the ability to re-size image suitable for cropping. So, I have 2 questions. 1) How to receive Touch events on edges of rectangle and 2) How would I re-size my rectangle. I'm using canvas to draw my rectangle. The code for this is as follows:
public class CustomView extends Views
{
public CustomView(Context context)
{
super(context);
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);;
paint.setColor(Color.BLUE);
paint.setStrokeWidth(3);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(0, 0, 300, 300, paint);
}
}
You should implement the OnTouchListener and check in MotionEvent.ACTION_DOWN if you touch the rectangle and modify right, left, bottom and top of rectangle in MotionEvent.ACTION_MOVE
I provided a not tested example:
public class CustomView extends View {
private float left=0,right = 300, top = 0, bottom = 300;
public CustomView(Context context)
{
super(context);
setOnTouchListener( new OnTouchListener() {
float oldX, oldY;
#Override
public boolean onTouch(View v, MotionEvent event) {
boolean touch = false;
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
touch = isTouchBorderRect();
break;
case MotionEvent.ACTION_UP:
touch = false;
case MotionEvent.ACTION_MOVE:
if (touch){
float newX = event.getRawX();
float newY = event.getRawY();
float deltaX = newX-oldX;
float deltaY = newY-oldY;
left-=deltaX;
right +=deltaX;
bottom += deltaY;
top -= deltaY;
}
break;
}
return false;
}
});
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);;
paint.setColor(Color.BLUE);
paint.setStrokeWidth(3);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(left, top, right, bottom, paint);
}
}
In isTouchBorderRect() method you should check if you have touched the rectangle.
This code is not tested but show the idea that you want to develop.
How to Draw a rectangle (Using Shape Resources) at the touched point(like coordinates 28,87). I have Created a shape Like This.
android:shape="rectangle" >
<solid
android:color="#color/transparent"/>
<stroke
android:width="3dp"
android:color="#color/green" />
This rectangle I want to Draw at the touch point on the image.
You can draw a shape on a view in the onDraw() method of that view. There is no method available for drawing a shape drawable on a view canvas.
And you don't have to use a shape drawable for drawing a rectangle. You can draw a rectangle using canvas.drawRect() method.
Here is a code for this:
public class MyView extends View{
float x,y;
Bitmap bmp;
Paint mPaint;
float width = 100.0f;
float height = 50.0f;
boolean touched = false;
public MyView (Context context)
{
super(context);
x = y = 0;
mPaint = new Paint();
mPaint.setColor(Color.BLUE);
mPaint.setStyle(Style.STROKE);
}
#Override
protected void onDraw (Canvas canvas)
{
canvas.drawColor(Color.WHITE);
if(touched)
{
canvas.drawRect(x, y, x+width, y+height, mPaint);
}
}
#Override
public boolean onTouchEvent (MotionEvent event)
{
touched = true;
//getting the touched x and y position
x = event.getX();
y = event.getY();
invalidate();
return true;
}
}
#kam' solution needs an update I guess. Everything that was in the constructor must be in init() method and the constructors must be overwritten 3 times.
public class MyView extends View {
private float xDown = 0,yDown = 0, xUp = 0, yUp = 0;
Paint mPaint;
boolean touched = false;
public MyView(Context context) {
super(context);
init(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
mPaint = new Paint();
mPaint.setColor(Color.BLUE);
mPaint.setStyle(Paint.Style.STROKE);
}
#Override
protected void onDraw (Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);
if(touched) {
canvas.drawRect(xDown, yDown, xUp, yUp, mPaint);
}
}
#Override
public boolean onTouchEvent (MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
xDown = event.getX();
yDown = event.getY();
xUp = 0;
yUp = 0;
break;
case MotionEvent.ACTION_MOVE:
xUp = event.getX();
yUp = event.getY();
touched = true;
break;
case MotionEvent.ACTION_UP:
xUp = event.getX();
yUp = event.getY();
touched = true;
break;
}
invalidate();
return true;
}
}
I have written a custom imageview to show a credit card. To create the credit card I have a base image and I have setters to set the PAN, Card holder, expiry. This text needs to be drawn on top of the base card image. My problem is maintaining the position and size of the text so that it will always look correct no matter the changing size of the base image. The only thing I can rely on is the aspect ratio of the image being the same as a normal credit card.
My custom ImageView
public class CardView extends ImageView {
private String mPan = "4321 0123 4567 8910";
private String mExpiry = "01/16";
private String mCardholder = "MR JOHN SMITH";
private float mPanTextSize = 22;
private float mOtherTextSize = 14;
private Paint mPanPaint = new Paint();
private Paint mCardholderPaint = new Paint();
public CardView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initCardView();
}
public CardView(Context context, AttributeSet attrs) {
super(context, attrs);
initCardView();
}
public CardView(Context context) {
super(context);
initCardView();
}
private final void initCardView() {
mPanPaint.setColor(0xFFFFFFFF);
mPanPaint.setShadowLayer(1, 1, 1, 0xAA000000);
mPanPaint.setAntiAlias(true);
mPanPaint.setTextSize(mPanTextSize * getResources().getDisplayMetrics().scaledDensity);
mPanPaint.setTypeface(Typeface.MONOSPACE);
mCardholderPaint.setColor(0xFFFFFFFF);
mCardholderPaint.setShadowLayer(1, 1, 1, 0xAA000000);
mCardholderPaint.setAntiAlias(true);
mCardholderPaint.setTextSize(mOtherTextSize * getResources().getDisplayMetrics().scaledDensity);
mCardholderPaint.setTypeface(Typeface.MONOSPACE);
setPadding(0,0,0,0);
//setAdjustViewBounds(true);
setScaleType(ImageView.ScaleType.CENTER_INSIDE);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float panLength = mPanPaint.measureText(mPan);
float x = (getWidth() - panLength)/2;
float y = -mPanPaint.ascent() + (getHeight() * 0.46f);
canvas.drawText(mPan, x, y, mPanPaint);
x = (getWidth() - panLength)/1.5f;
y = y - mCardholderPaint.ascent();
canvas.drawText(mExpiry, x, y, mCardholderPaint);
y = y - mCardholderPaint.ascent();
canvas.drawText(mCardholder, x, y, mCardholderPaint);
//super.onDraw(canvas);
}
public void setPan(String pan) {
mPan = pan;
invalidate();
}
public String getPan() {
return mPan;
}
public void setExpiry(String expiry) {
mExpiry = expiry;
invalidate();
}
public String getExpiry() {
return mExpiry;
}
public void setCardholder(String cardholder) {
mCardholder = cardholder;
invalidate();
}
public String getCardholder() {
return mCardholder;
}
}
So sometimes this looks ok but as you get to 10 inch screens the text is way too small, right in the center of the image (imagine looking at a credit card but the number only takes up the space of the middle 8 digits), and as you get to small screens the text is too big, going right up to the image sides or past them.
Any solutions? Any explanation why?
You need to make the text sizes dependent on the size of your image.
try to experiment with different values for yourPanFactor and yourCardholderFactor until you get the desired result
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
final int height = bottom-top;
mPanPaint.setTextSize(height*yourPanFactor);
mCardholderPaint.setTextSize(height*yourCardholderFactor);
}
}
I managed to make this work in this lines of code
if (imageView.getWidth() <= resource.getWidth()) {
ratio = (float) resource.getWidth() / (float) imageView.getWidth();
} else {
ratio = (float) imageView.getWidth() / (float) resource.getWidth();
}
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setTextSize(editText.getTextSize() * ratio);
Note that the editText has the default text size which is 14 sp.
I hope it help you. and correct me if am wrong