What I am doing:
I am trying to create a button dynamically onTouch
What is Happening:
I am able to create the button but the button is not created exactly
in the place I touch, instead its little bit in the bottom
right(might be adjustment of button in pixel).
How can i make sure i create the button exactly in the same place i
touch
#Override
public boolean onTouch(View v, MotionEvent event) {
//Get the x & y co-ordinates from the event
float x = event.getX();
float y = event.getY();
//Convert into Integer
int mX = (int) x;
int mY = (int) y;
//Perform Event on touch of canvas
performEventOnTouchOfCanvas(event, mX, mY);
return true;
}
private void performEventOnTouchOfCanvas(MotionEvent event,int mX, int mY) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Point mPoint=new Point(mX,mY);
createButton(mPoint.x,mPoint.y);
break;
}
}
private void createButton(float x, float y) {
Button btn = new Button(ActDrawAreaTwo.this);
RelativeLayout.LayoutParams bp = new RelativeLayout.LayoutParams(40, 40);
bp.leftMargin = (int) x;
bp.topMargin = (int) y;
//Assign the Id to the button
btn.setLayoutParams(bp);
CommonFunctions.setBackgroundDrawable(btn, ActDrawAreaTwo.this, R.drawable.white_circle_dot);//Set Button Drawable
String mTag=String.valueOf((int)x )+","+ String.valueOf((int) y);
btn.setTag(mTag);
canvasLayoutId.addView(btn);
}
Note: canvasLayoutId is a relative layout
Android start drawing from views topmost left side. So when you pass coordinates, it will assume those are topmost left side of the button. If you want your button to appear in the middle of where you touch you need to change your coordinates with these:
x = x + button_width / 2
y = y + button_height / 2
In Android default padding is also same button's border, so you can find button's width and height with using this:
button_width = mButton.getPaddingRight() - mButton.getPaddingLeft();
button_height = mButton.getPaddingBottom() - mButton.getPaddingTop();
You can also use button.getWidth() and button.getHeight() assuming you are not using MATCH_PARENT or WRAP_CONTENT as your paramters.
Related
Recently I have been trying to implement dragging and scaling on a picture that I place in a FrameLayout. What I want to achieve is simple: to be able to drag the picture around and zoom it. I went to the Android Developer website and followed the guide there.
Then following the code examples on that website I wrote MyCustomView:
public class MyCustomView extends ImageView {
private static final int INVALID_POINTER_ID = 0xDEADBEEF;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
private float mLastTouchX, mLastTouchY;
private int mActivePointerId = INVALID_POINTER_ID;
private LayoutParams mLayoutParams;
private int mPosX, mPosY;
public MyCustomView(Context context) {
super(context);
mScaleDetector = new ScaleGestureDetector(context, new CustomScaleListener());
mLayoutParams = (LayoutParams) super.getLayoutParams();
if (mLayoutParams != null) {
mPosX = mLayoutParams.leftMargin;
mPosY = mLayoutParams.topMargin;
} else {
mLayoutParams = new LayoutParams(300, 300);
mLayoutParams.leftMargin = 0;
mLayoutParams.topMargin = 0;
}
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.scale(mScaleFactor, mScaleFactor);
canvas.restore();
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
// Let the ScaleGestureDetector inspect all events
mScaleDetector.onTouchEvent(ev);
final int action = MotionEventCompat.getActionMasked(ev);
switch (action) {
case MotionEvent.ACTION_DOWN: {
final int pointerIndex = MotionEventCompat.getActionIndex(ev);
//final float x = MotionEventCompat.getX(ev, pointerIndex);
//final float y = MotionEventCompat.getY(ev, pointerIndex);
final float x = ev.getRawX();
final float y = ev.getRawY();
// Remember where we started (for dragging)
mLastTouchX = x;
mLastTouchY = y;
// Save the ID of this pointer (for dragging)
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
break;
}
case MotionEvent.ACTION_MOVE: {
// Find the index of the active pointer and fetch its position
final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
//final float x = MotionEventCompat.getX(ev, pointerIndex);
//final float y = MotionEventCompat.getY(ev, pointerIndex);
final float x = ev.getRawX();
final float y = ev.getRawY();
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
//TODO: Update the location of this view
mPosX += dx;
mPosY += dy;
mLayoutParams.leftMargin += dx;
mLayoutParams.topMargin += dy;
super.setLayoutParams(mLayoutParams);
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 = MotionEventCompat.getActionIndex(ev);
final int pointerID = MotionEventCompat.getPointerId(ev, 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 = MotionEventCompat.getX(ev, newPointerIndex);
//mLastTouchY = MotionEventCompat.getY(ev, newPointerIndex);
mLastTouchX = ev.getRawX();
mLastTouchY = ev.getRawY();
mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
}
break;
}
}
return true;
}
private class CustomScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
invalidate();
return true;
}
}
In the MainActivity I simply instantiated a MyCustomView object and attached it to ViewGroup at the background, which is a FrameLayout. The xml file has nothing but a FrameLayout there.
public class MainActivity extends AppCompatActivity {
private ViewGroup layoutRoot;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
layoutRoot = (ViewGroup) findViewById(R.id.view_root);
final MyCustomView ivAndroid = new MyCustomView(this);
ivAndroid.setImageResource(R.mipmap.ic_launcher);
ivAndroid.setLayoutParams(new FrameLayout.LayoutParams(300, 300));
layoutRoot.addView(ivAndroid);
}
}
And here comes the problem that troubles me:
The Android Developer website uses this to obtain the coordinates of the finger that touches the picture:
final float x = MotionEventCompat.getX(ev, pointerIndex);
final float y = MotionEventCompat.getY(ev, pointerIndex);
But it works horribly! The picture moves, but it does not follow my finger exactly, it always moves LESS than my finger does, and most importantly, it flashes.
So that is why you can see in MyCustomView that I have commented out this line and instead used this code:
final float x = ev.getRawX();
final float y = ev.getRawY();
While this time the picture moves smoothly in accordance with my finger, this change introduces a new problem. On the Android Developer website for dragging and scaling, there is a design principle that says:
In a drag (or scroll) operation, the app has to keep track of the original pointer (finger), even if additional fingers get placed on the screen. For example, imagine that while dragging the image around, the user places a second finger on the touch screen and lifts the first finger. If your app is just tracking individual pointers, it will regard the second pointer as the default and move the image to that location.
After I started using ev.getRawX() and ev.getRawY(), adding a second finger to the screen gives me exactly the problem stated above. But MotionEventCompat.getX(ev, pointerIndex) and MotionEventCompat.getY(ev, pointerIndex) does not.
Can somebody help me explain why it happens? I know that MotionEventCompat.getX(ev, pointerIndex) returns the coordinate after some sort of adjustment, and that ev.getRawX() returns the absolute coordinate. But I don't understand how exactly the adjustment works (Is there a formula or graphical explanation for it?). I also want to know why using MotionEventCompat.getX(...) would prevent the picture from jumping to the second finger on screen (after the first finger has been lifted).
Last but not least, the scaling code simply doesn't work AT ALL. If someone and teach me on that it will also be greatly appreciated!
This question is long, so I will partionate it in smaller bits. Also, english is not my native language so I had some difficulties writting the answer. Comment if a part is not clear.
Can somebody help me explain why it happens?
getRawX() and ev.getRawY() will both give you the absolute pixel value of the event. Those will also (for the sake of backwards compatibility, when most screens could only track 1 "region" at a time) will always consider the finger as the first (and only) finger that is interacting with the device.
Then, came improvements that allowed to track the finger ID., the MotionEventCompat.getX(ev, pointerIndex) and MotionEventCompat.getY(ev, pointerIndex) functions allowed for further finesse when creating our onTouch() Listeners.
Is there a formula or graphical explanation for it?
Basically, you need to take into consideration the "Screen Density" of that device. Such as:
float SCREEN_DENSITY = getResources().getDisplayMetrics().density;
protected void updateFrame(FrameLayout frameLayout, int h, int w, int x, int y) {
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
(int) ((w * SCREEN_DENSITY) + 0.5),
(int) ((h * SCREEN_DENSITY) + 0.5)
);
params.leftMargin = (int) ((x * SCREEN_DENSITY) + 0.5);
params.topMargin = (int) ((y * SCREEN_DENSITY) + 0.5);
frameLayout.setLayoutParams(params);
}
I also want to know why using MotionEventCompat.getX(...) would prevent the picture from jumping to the second finger on screen (after the first finger has been lifted)
If you take into consideration that the first "finger" was lifted, then the new one, has a different "initial point", and different "history", because of that, it can send its event in relation to the movement made, not the final position on screen. This way it wont "jump to where the finger is" but will move according to the ammount of "x units" and "y units" traversed.
Last but not least, the scaling code simply doesn't work AT ALL. If someone and teach me on that it will also be greatly appreciated!
You are consuming the event (by returning true on your onTouch Listener), because of that, no other Listener can continue reading from the event, in a way that you can trigger more Listeners.
If you desire, move both functions (move and resize) inside the onTouch. My onTouch Listener has over 1700 lines of code (because it does a lot of stuff, including programatically creating Views and adding listeners to that), so I cant post it here, but basically:
1 Finger = move the frame. Get raw values, and use the "updateFrame"
2 Fingers = resize the frame. Get raw values, and use the "updateFrame"
3+ Fingers = Drop first finger, suppose 2 Fingers.
Is there any way to add pinch zoom in zoom out on edit Text?
Although this is a bit of strange user interaction, I believe it should be able to be done by just combining some simple view gesture recognition and changing the font size. You could begin by creating a custom EditText and overriding the onTouchEvent(MotionEvent) method. In onTouchEvent(MotionEvent), you can make use of ScaleGestureDetector (more info here) to detect "pinch-to-zoom" gestures. Also take a look at this Android guide for more info on implementing custom gesture detections in views.
After you detect the zooming gesture, you can simply use setTextSize in EditText to adjust the size of the font relative to the change in zoom. This of course isn't going to give you a smooth zooming gesture like zooming on a website. Another method you could try is taking the zoom gesture and physically adjusting the size (width and height) of the EditText but that's just a thought.
Hope this helps!
This code does the job, you have to add super.onTouchEvent(event); so you don't lose EditText properties
final static float move = 200;
float ratio = 1.0f;
int bastDst;
float baseratio;
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
if (event.getPointerCount() == 2) {
int action = event.getAction();
int mainaction = action & MotionEvent.ACTION_MASK;
if (mainaction == MotionEvent.ACTION_POINTER_DOWN) {
bastDst = getDistance(event);
baseratio = ratio;
} else {
// if ACTION_POINTER_UP then after finding the distance
// we will increase the text size by 15
float scale = (getDistance(event) - bastDst) / move;
float factor = (float) Math.pow(2, scale);
ratio = Math.min(1024.0f, Math.max(0.1f, baseratio * factor));
text.setTextSize(ratio + 15);
}
}
return true;
}
// get distance between the touch event
private int getDistance(MotionEvent event) {
int dx = (int) (event.getX(0) - event.getX(1));
int dy = (int) (event.getY(0) - event.getY(1));
return (int) Math.sqrt(dx * dx + dy * dy);
}
I am trying to make an Android paint application for finger painting and I am having trouble with moving the lines I draw.
What I tried to do was offset the path of the currently selected line by the difference between the initial finger press coordinates and the current coordinates in OnTouchEvent during ACTION_MOVE.
case MotionEvent.ACTION_MOVE:
selectline.getLine().offset(x - otherx, y - othery);
otherx and othery are set as the x and y coordinates during ACTION_MOVE and x and y are the current cursor coordinates. My lines are stored as a separate class containing the path, color, thickness and bounding box.
What I got was the shape flying off the screen in the direction of my finger without stopping at the slightest movement. I tried using a matrix to move the path, but the result was the same.
When I tried to insert a "do while" that would check whether the current coordinates would match the path's .computeBounds() rectangle center, but the program crashes as soon as I move my finger.
Any help would be appreciated, thanks.
Most likely that you did not use the right scale for the coordinates.
Source: Get Canvas coordinates after scaling up/down or dragging in android
float px = ev.getX() / mScaleFactor + rect.left;
float py = ev.getY() / mScaleFactor + rect.top;
// where mScaleFactor is the scale use in canvas and rect.left and rect.top is the coordinate of top and left boundary of canvas respectively
Its a bit late but it may solve others problem. I solved this issue like this,
get initial X,Y position on onLongPress
public void onLongPress(MotionEvent motionEvent) {
try {
shapeDrag = true;
SmEventX = getReletiveX(motionEvent);
SmEventY = getReletiveY(motionEvent);
} catch (Exception e) {
e.printStackTrace();
}
and then on onToucn(MotionEvent event)
case MotionEvent.ACTION_MOVE: {
actionMoveEvent(motionEvent);
try {
if (shapeDrag) {
StylePath sp = alStylePaths
.get(alStylePaths.size() - 1);
Path mpath = sp.getPath();
float tempX = getReletiveX(motionEvent) - SmEventX;
float tempY = getReletiveY(motionEvent) - SmEventY;
mpath.offset(tempX, tempY);
SmEventX = getReletiveX(motionEvent);
SmEventY = getReletiveY(motionEvent);
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
}
I faced the same trouble and in my case it was a very naive mistake. Since the description of the "symptoms" matches exactly (shape flying off the screen in the direction of the finger at the slightest movement, shape moved correctly at ACTION_UP event), I think the reason behind might be the same.
Basically the problem is in the update of the touch position coordinates within the ACTION_MOVE event. If you don't update the last touch position, the calculated distance will be always between the current touch position and the first touch position stored at ACTION_DOWN event: if you apply this offset consecutively to the path, the translation will sum up and consequently the shape will "fly" rapidly off the screen.
The solution is then quite simple: just update the last touch position at the end of the ACTION_MOVE event:
float mLastTouchX, mLastTouchY;
#Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN: {
// get touch position
final float x = ev.getX();
final float y = ev.getY();
// save the initial touch position
mLastTouchX = x;
mLastTouchY = y;
break;
}
case MotionEvent.ACTION_MOVE: {
// get touch position
final float x = ev.getX();
final float y = ev.getY();
// calculate the distance moved
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
// here apply translation to the path
// update touch position for the next move event
mLastTouchX = x;
mLastTouchY = y;
break;
}
}
return true;
}
Hope this helps.
I am looking have a layout with an image as background and add an image inside which allows me to drag within the layout only. How can I achieve it?
So let's say the red area is my framelayout and the orange box is the image:
The app loads with the orange all the to the bottom and left and then I want to let the user drag it around the red area only.
If (orange.x goes beyond left of the red box) {
the orange box.x = the orange box + the orange box.width;
}
else if (orange.x goes beyond the right of the red box) {
the orange box.x = the orange box - the orange box.width;
}
else If (orange.y goes beyond top of the red box) {
the orange box.y = the orange box + the orange box.width;
}
else if (orange.y goes beyond the bottom of the red box) {
the orange box.y = the orange box - the orange box.width;
}
else { // do not allow use to drag outside, if they do drag it outside leave the orange box the last known position inside the red box area
y = orange box.y; //get x coordinate of the orange box
x = orange box.x; //get y coordinate of the orange box
}
So I can setup my XML like this:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/redboxarea">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:layout_width="#dimen/widthsmall"
android:layout_height="#dimen/heightsmall"
android:scaleType="fitXY"
android:src="#drawable/orangebox"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true" />
</RelativeLayout>
</FrameLayout>
How can I achieve the android code to achieve what I am looking to do? Or is something else recommended?
You can create a custom View (Extend the View class).
Draw the bitmap to a canvas (this is all in your custom view class)
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
//mPosX and mPosY default to 0, then on later redraws correspond to where the object was last dragged.
canvas.drawBitmap(mSourceImage, mPosX, mPosY, null);
}
Then override the onTouchEvent method
#Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
mLastTouchX = x;
mLastTouchY = y;
// Save the ID of this pointer ( this is for multitouch)
mActivePointerId = ev.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE: {
// Find the index of the active pointer and fetch its position
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
//Get new location
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
//Compare to old location
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
//set new resting location
mPosX += dx;
mPosY += dy;
mLastTouchX = x;
mLastTouchY = y;
//Invalidate the view and force a redraw with the new values for mPosX and mPosY.
invalidate();
break;
}
And in your main activity code:
//instantiate the view object
touchView = new YourCustomViewClass(this, mPicturePath);
//set the size of your view
touchView.setLayoutParams(new LayoutParams(700, 200));
//add it to your main activity layout
mainLayout.addView(touchView);
I've been working on a very simple little application using an extended view.
The problem is that i can't find what been pressed in my onTouchEvent.
I've understood that i've to compare the pressure-points (x and y) to the elements and see which one it could be.
But...
I've declared a rectangle using:
Paint color= new Paint();
color.setColor(Color.BLACK);
rectF = new RectF(0.0f, 0.0f, 1.0f, 0.5f) ;
canvas.drawRect(rectF, color);
So far so good, but in the onTouchEvent function
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction() ;
float x = event.getX() ;
float y = event.getY() ;
switch(action) {
case MotionEvent.ACTION_DOWN:
if(rectF().contains(x, y)) {
// Something should happen
}
}
invalidate();
return true ;
}
I set the rectF with some kind of relative points ranging from 0-1, but the X and Y i get from the event ranges from 0 and upwards depending on screen-size.
Can I easily convert either of the values?
Edit
Thought someone was interested in the final solution...
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction() ;
float x = event.getX() ;
float y = event.getY() ;
int width = this.getWidth() ;
int height = this.getHeight() ;
switch(action) {
case MotionEvent.ACTION_DOWN:
if(rectF().contains(x/width, y/height)) {
// Something should happen
}
}
return true ;
}
The values of the touch point and the RectF have to be on the same reference scale in oreder to be compared properly. Meaning that if you declare your RectF to use relative sizes (0-1) you will either need to :
1: normalize the MotionEvent position by your screen size
OR 2: "denormalize" the RectF dimensions by your actual screen size.
I think 1 is generally preferrable as it lets you define your rects in terms of relative layout, independent of the actual screen size and convert the event positions on-the-fly.
like this : rectF.contains(x/screenWidth, y/screenHeight);
You can retrieve the screen dimensions by calling Display display = getWindowManager().getDefaultDisplay() in your Activity and then calling display.getWidth() or display.getHeight()