android canvas not drawing on touched position - android

I am using canvas to make a simple drawing app. I am using onTouchEvent to handle touch event. I am facing a problem on vertical axis drawn point is differentfrom touched position. the vertical separation in between touched position and drawn position increases as I move upward. To make my problem more clear i am attaching screenshot of my app.
Blue line shows actual touched position and red drawn position.
Here is my code
Mainactivity
public class MainActivity extends AppCompatActivity{
Path mPath;
Canvas canvas;
Paint paint;
float pointX;
float pointY;
int height;
RelativeLayout layout;
int layoutHeight;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
layout=(RelativeLayout)findViewById(R.id.main_layout);
DisplayMetrics dp=getResources().getDisplayMetrics();
WindowManager windowManager=getWindowManager();
height=windowManager.getDefaultDisplay().getHeight();
int width =windowManager.getDefaultDisplay().getWidth();
Bitmap bg = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
layout.setBackground(new BitmapDrawable(bg));
canvas=new Canvas(bg);
paint=new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(4);
mPath=new Path();
canvas.drawPath(mPath,paint);
canvas.drawBitmap(bg,0,0,paint);
}
public void clearCanvas(View v) {
mPath.reset();
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
layout.invalidate();
}
//override the onTouchEvent
#Override
public boolean onTouchEvent(MotionEvent event) {
pointX = event.getX();
pointY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.v("TAG"," action down x:"+pointX+" y:"+pointY);
mPath.moveTo(pointX,pointY);
break;
case MotionEvent.ACTION_MOVE:
Log.v("TAG"," actionmove x:"+pointX+" y:"+pointY);
mPath.lineTo(pointX,pointY);
canvas.drawPath(mPath,paint);
break;
case MotionEvent.ACTION_UP:
break;
}
layout.invalidate();
return true;
}
}
xml layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.vikash.mydrawingapp.MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="clear"
android:onClick="clearCanvas"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>

Use: event.getRawX()
Returns the original raw X coordinate of this event. For touch events on the screen, this is the original location of the event on the screen, before it had been adjusted for the containing window and views.
UPD:
I just tried to repeat your app and all works correctly
activity.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.Test
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
Test.java
public class Test extends View {
private Path path = new Path();
private Paint paint = new Paint();
{
paint.setColor(Color.GREEN);
paint.setStrokeWidth(4);
paint.setStyle(Paint.Style.STROKE);
}
public Test(Context context) {
super(context);
}
public Test(Context context, AttributeSet attrs) {
super(context, attrs);
}
public Test(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public Test(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path, paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.moveTo(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
path.lineTo(event.getX(), event.getY());
break;
}
invalidate();
return true;
}
}

Your problem is that your Canvas X-Axis has the same position of the Screen X-Axis, but your Canvas Y-Axis is offset from Screen Y-Axis (The "MyDrawingApp" bar sets that offset with its height).
What is happening is that when you "getY()" it returns the position of the touch taking as reference the entire screen not your canvas.
One solution is to get the position of the canvas on the screen and minus it to the "getY()" value. Doing this you will get a touch inside of your canvas.
I hope you will understand me, I am not English.
Greetings

Related

Android custom view is offset when while moving

I'm trying to move a custom view that is in my main activity with a single touch input, however the x/y coordinates for the event are offset due to the action bar.
GIF of problem
I'm trying to find a way to negate the size of the action bar to the y coordinate but nothing seems to be working. I've subtracting the difference in the parent view and my custom view's size from the y coordinate getRootView().getHeight() - getHeight() but the value is incorrect.
Can anyone point me in the right direction?
The custom view:
public class SampleView extends View {
private Paint paint;
private Path path = new Path();
public SampleView(Context context) {
super(context);
init();
}
public SampleView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(path, paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
final int x = (int) event.getRawX();
final int y = (int) event.getRawY();
switch(event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
path.moveTo(x, y);
break;
}
case MotionEvent.ACTION_MOVE: {
path.lineTo(x, y);
break;
}
}
invalidate();
return true;
}
}
I haven't touched my MainActivity, but have added in the XML for activity_main my custom view:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.caseyweed.sample.MainActivity">
<com.caseyweed.sample.SampleView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
Use getX() and getY() instead of getRawX() and getRawY() if you want coordinates relative to the view instead of the device screen coordinates.

Android - Google Maps inside CircleView

I wanted to show map inside a circle view where circle's outside area filled with a color. I referred a post Draw transparent circle filled outside. But now the problem is touch events. Map can be touched through outside circle view while I need map can be touched (zoom or move) only form inside Circle view (where the map is visible).
What I tried,
setEnabled=false
clickable=false
but still map is touched from outside circle view.
Is that possible to achieve that map can be touched from inside circle view.
public class RadiusOverlayView extends LinearLayout {
private Bitmap windowFrame;
public RadiusOverlayView(Context context) {
super(context);
}
public RadiusOverlayView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RadiusOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public RadiusOverlayView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (windowFrame == null) {
createWindowFrame(); // Lazy creation of the window frame, this is needed as we don't know the width & height of the screen until draw time
}
canvas.drawBitmap(windowFrame, 0, 0, null);
}
#Override
public boolean isEnabled() {
return false;
}
#Override
public boolean isClickable() {
return false;
}
protected void createWindowFrame() {
windowFrame = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); // Create a new image we will draw over the map
Canvas osCanvas = new Canvas(windowFrame); // Create a canvas to draw onto the new image
RectF outerRectangle = new RectF(0, 0, getWidth(), getHeight());
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); // Anti alias allows for smooth corners
paint.setColor(Color.CYAN); // This is the color of your activity background
osCanvas.drawRect(outerRectangle, paint);
//paint.setColor(Color.TRANSPARENT); // An obvious color to help debugging
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)); // A out B http://en.wikipedia.org/wiki/File:Alpha_compositing.svg
float centerX = getWidth() / 2;
float centerY = getHeight() / 2;
float radius = Math.min(getWidth(), getHeight()) / 2 - 50;
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);
windowFrame = null; // If the layout changes null our frame so it can be recreated with the new width and height
}
}
XML layout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!--loading map in container-->
<FrameLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<mypackage.RadiusOverlayView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
/>
</RelativeLayout>
Result:
Any help will be appreciated.
You could set a View.OnTouchListener to your RadiusOverlayView and calculate whether the RadiusOverlayView needs to manage the touch events or not.
In this example I calculate this by testing if the RadiusOverlayView color touched is != 0 (maybe you want to improve this):
final RadiusOverlayView radiusOverlayView = (RadiusOverlayView) findViewById(R.id.radiusView);
radiusOverlayView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(final View view, final MotionEvent motionEvent) {
view.setDrawingCacheEnabled(true);
Bitmap bmp = Bitmap.createBitmap(view.getDrawingCache());
view.setDrawingCacheEnabled(false);
return bmp.getPixel((int) motionEvent.getX(), (int) motionEvent.getY()) != 0;
}
});

Draw on the custom image view in android

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.

adding View class to the default layout

I'm new to Android development.
I have a class DrawView that extends View, it's like a simple paint app
public class DrawView extends View implements OnTouchListener {
Bitmap bitmap;
Canvas bitmapCanvas;
int color;
// Position of finger down
float pX, pY;
// Position of finger up
float mX, mY;
// Create new path
Path path = new Path();
// Is view initialized?!
boolean isInitialized;
// Create new paint
Paint paint = new Paint();
int begX, begY, endX, endY = 0;
//DrawView constructor
public DrawView(Context context) {
// Initialize new view
super(context);
setFocusable(false);
setFocusableInTouchMode(false);
this.setOnTouchListener(this);
requestLayout();
paint.setAntiAlias(true);
paint.setStyle(Style.STROKE);
// There is no bitmap yet
isInitialized = false;
}
//Initialize bitmap and canvas
private void init() {
bitmap = Bitmap.createBitmap(getWidth(), getHeight()/2, Bitmap.Config.RGB_565);
bitmap.setPixel(72, 72, Color.BLACK);
// Create new canvas and set bitmap
bitmapCanvas = new Canvas();
bitmapCanvas.setBitmap(bitmap);
// ... set canvas background color
bitmapCanvas.drawColor(Color.WHITE);
// We're done with initialization
isInitialized = true;
}
//Reset canvas
public void reset(){
bitmapCanvas.drawColor(Color.WHITE);
}
//Handle event 'onDraw'
#Override
public void onDraw(Canvas canvas) {
// Check if initialized
if (!isInitialized)
init();
// Draw bitmap!
canvas.drawBitmap(bitmap, 0, 0, paint);
}
//Handle event 'onTouch'
public boolean onTouch(View view, MotionEvent event) {
// Check event type
switch (event.getAction()) {
// Finger down
case MotionEvent.ACTION_DOWN:
paint.setColor(Color.BLACK);
paint.setStrokeWidth(7f);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setAntiAlias(true);
// Get current position
pX = event.getX();
pY = event.getY();
// Set beginning of path to (posX,posY)
path.moveTo(pX, pY);
begX= (int) pX;
begY = (int) pY;
bitmapCanvas.drawPoint(pX, pY, paint);
break;
// Finger moves
case MotionEvent.ACTION_MOVE:
mX = event.getX();
mY = event.getY();
// Set position of end of path
path.lineTo(mX, mY);
endX = (int) mX;
endY = (int) mY;
// Draw path
bitmapCanvas.drawPath(path, paint);
// Invalidate canvas (redraw the view)
invalidate();
break;
// Finger up
case MotionEvent.ACTION_UP:
mX = event.getX();
mY = event.getY();
if (mY == pY && mX == pX){
bitmapCanvas.drawPoint(pX, pY, paint);
invalidate();
}
path.reset();
break;
}
return true;
}
}
In my default layout, I want it to contains DrawView and other elements (button, textview ...)
How can I split "default layout" to contains DrawView with these elements?
Thanks
edit:
Where should I put this code to make DrawView work correctly
public class Draw extends Activity {
DrawView drawView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
drawView = new DrawView(this);
setContentView(drawView);
drawView.requestFocus();
}
}
Add your view with it's full name to your layout.For example:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
...>
<TextView.../>
<my.package.MyCustomView
android:id="#+id/my_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<Button.../>
</LinearLayout>
Here MyCustomView is name of java class that extends View and my.package is name of package that contains that class.You can see that you can add LayoutParams to it,as I added layout_height or layout_width.
Edit:
If you want to use your custom view in XML layout,you have to add at least this constructor to your class:
public MyCustomView(Context context, AttributeSet attrs){
super(context, attrs);
...
}
I don't know how you want actually but you can use any layout as per your requirement here is the simple layout which your your view and another views in split screen
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_weight="1">
<TextView /> <!--your textview-->
<TextView /> <!--your textview-->
<TextView /> <!--your textview-->
</LinearLayout>
<com.androidapp.DrawView android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
This will split the screen in equal size for your view and another view with nested LinearLayout
If you want to display your content with wrap size then just remove the weight if you display at top of your DrawView and use wrap_content in height or if you display at bottom than give them weight in 0.2 or o.3 whatever but in float
Edit
Define both constructor
public DrawView(Context context) {
// Initialize new view
super(context);
initObject();
}
public MyCustomView(Context context, AttributeSet attrs){
super(context, attrs);
initObject();
}
private void initObject(){
setFocusable(false);
setFocusableInTouchMode(false);
this.setOnTouchListener(this);
requestLayout();
paint.setAntiAlias(true);
paint.setStyle(Style.STROKE);
// There is no bitmap yet
isInitialized = false;
}

Draw a Rectangle or Shape at Touch Point(Co-Ordinates) on the Image

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

Categories

Resources