I've created an OnTouchListener that will allow me to drag views around the screen. This works well, except for one problem:
My views resize instead of leaving the parent. They are in a RelativeLayout, which I suspect is contributing to the problem. They only resize when touching the right or bottom boundaries of my RelativeLayout.
I'm hoping there is a simple solution to this problem, (ideally not involving resizing the RelativeLayout), but I haven't found a solution yet.
Ideally, this solution would be applicable for all views, as I use this OnTouchListener for TextViews as well as ImageViews. I was hoping to find an XML solution. I'm assuming others have dealt with this issue??
PS: Before I post this..I've just remembered I believe there is a flag for hitting a RelativeLayout's border, so maybe I can apply negative margin to the TextView when that happens. Thoughts?
EDIT
Here is my OnTouchListener:
// Dragging functionality for all views and double tap to remove
// functionality for ImageViews
final OnTouchListener onTouchListener = new OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
v.bringToFront();
//if(v instanceof android.widget.ImageView)
//mScaleDetector.onTouchEvent(event);
layoutParams = (LayoutParams) v.getLayoutParams();
switch (event.getActionMasked())
{
case MotionEvent.ACTION_DOWN:
// Where the user started the drag
pressed_x = (int) event.getRawX();
pressed_y = (int) event.getRawY();
if (v instanceof android.widget.ImageView)
{
curTime = System.currentTimeMillis();
if(curTime - prevTime <= DOUBLE_TAP_INTERVAL)
{
v.setVisibility(View.GONE);
}
prevTime = curTime;
}
case MotionEvent.ACTION_MOVE:
// Where the user's finger is during the drag
final int x = (int) event.getRawX();
final int y = (int) event.getRawY();
// Calculate change in x and change in y
dx = x - pressed_x;
dy = y - pressed_y;
// Update the margins
layoutParams.leftMargin += dx;
layoutParams.topMargin += dy;
v.setLayoutParams(layoutParams);
// Save where the user's finger was for the next ACTION_MOVE
pressed_x = x;
pressed_y = y;
break;
default:
break;
}
return true;
}
}
Related
I have an image and it moves to my finger position when I touch it, Everything is working but I want to know what these variables do.
Part of code
//I'm trying since the morning to understand what these variables do but I failed so can someone tell me why we put these variables?
dX = view.getX() - event.getRawX();
dY = view.getY() - event.getRawY();
Full code
#SuppressLint("ClickableViewAccessibility")
#Override
public boolean onTouch(View view, MotionEvent event) {
if (view.getId() == R.id.img_view) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
dX = view.getX() - event.getRawX();
dY = view.getY() - event.getRawY();
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
view.setX(event.getRawX() + dX);
view.setY(event.getRawY() + dY);
}
}
return true;
}
dX and dY is a difference between top left corner of this view and touch point on this view - you are touching view somewhere in the middle of it, almost never on its edge (pixel on 0,0 coordinates). when ACTION_MOVE occurs then you are using setX/setY methods for changing position, which are setting position of this top left corner - for preserving feeling of dragging (no view jump, smooth drag) you have to add these initial difference. use Log.i(..) and observe Logcat tab in Android Studio, you will notice which value is responsible for which param of touch/view
Hello! Thank you for reading.
Based upon this answer, I integrated View dragging like so:
float dX, dY;
#Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
dX = view.getX() - event.getRawX();
dY = view.getY() - event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
setX(event.getRawX() + dX);
setY(event.getRawY() + dY);
break;
default:
return false;
} return true;
}
However, the View's position relative to my finger is proportionally offset with the scale of the parent layout. In other words, when I start zooming in the layout, View dragging breaks.
I've been debugging for quite a bit and it seems that even knowing the parent layout scale (as, say, dScale), I can't use that value in a way that fixes the offset completely.
I cannot even reach a point where the distance between my finger and the View is constant.
To note that the issue is only observable when the parent's scale is not equal to 1.
.
Thank you very much for your help!
For further reference, this situation can be solved by applying the parent's scale to the getRawX() methods in both occurrences. I somehow hadn't tried that...
float dX, dY;
#Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
dX = view.getX() - event.getRawX() / dScale;
dY = view.getY() - event.getRawY() / dScale;
break;
case MotionEvent.ACTION_MOVE:
setX(event.getRawX() / dScale + dX);
setY(event.getRawY() / dScale + dY);
break;
default:
return false;
} return true;
}
I'd also recommend putting the MOVE case first in the switch.
I suppose the reasoning behind this solution can be vaguely explained by "view positioning methods know how to handle scaling, touch callback methods don't".
Hope this will help some!
I am trying to implement a touch-draggable ImageViewin an Android app. There are loads of sample codes for this on SO and elsewhere, and all seem to be along the lines of this one.
I tried something similar to this, but it was very laggy and I kept getting the warning
Choreographer﹕ Skipped XX frames! The application may be doing too much work on its main thread.
I followed the Android documentation advice on this and tried to do the work in a separate thread, but this hasn't solved the issue.
Basically I register an OnTouchListener to my view, then in the onTouch() method I start a new thread to try and do the work (the final update of the layout params is sent back to the UI thread for processing using View.post()).
I can't see how to achieve this by doing any less work on the main thread. Is there some way to fix my code? Or is there perhaps a better approach altogether?
The code:
private void setTouchListener() {
final ImageView inSituArt = (ImageView)findViewById(R.id.inSitu2Art);
inSituArt.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
final MotionEvent event2 = event;
new Thread(new Runnable() {
#Override
public void run() {
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) inSituArt.getLayoutParams();
switch (event2.getAction()) {
case MotionEvent.ACTION_DOWN: {
int x_cord = (int) event2.getRawX();
int y_cord = (int) event2.getRawY();
// Remember where we started
mLastTouchY = y_cord;
mLastTouchX = x_cord;
}
break;
case MotionEvent.ACTION_MOVE: {
int x_cord = (int) event2.getRawX();
int y_cord = (int) event2.getRawY();
float dx = x_cord - mLastTouchX;
float dy = y_cord - mLastTouchY;
layoutParams.leftMargin += dx;
layoutParams.topMargin += dy;
layoutParams.rightMargin -= dx;
final FrameLayout.LayoutParams finalLayoutParams = layoutParams;
inSituArt.post(new Runnable() {
#Override
public void run() {
inSituArt.setLayoutParams(finalLayoutParams);
}
});
mLastTouchX = x_cord;
mLastTouchY = y_cord;
}
break;
default:
break;
}
}
}).start();
return true;
}
});
}
with the new Thread you just added more choppiness to the drag movement.
As mentioned by #JohnWhite onTouch is called lots of times in a row, and creating and firing new threads like this is REALLY, REALLY bad.
The general reason why you were encountering those issues to begin with is because the example code that u were following, event though it works, it's a bad unoptimised code.
every time you're calling setLayoutParams the system have to execute a new complete layout pass, and that is a lot of stuff to be doing continuously.
I won't completely write the code for you, but I'll point you to possible alternatives.
First remove all the thread logic you used. You're not really executing any complex calculation, everything can run on the UI-thread.
use offsetTopAndBottom and offsetLeftAndRight to move your ImageView on the screen.
use ViewTransformations with setTranslationX (and Y)
change your ImageView to match_parent and create a custom ImageView. In this custom view you'll override the onDraw method to move the image right before you draw it. Like this:
#Override
public void onDraw(Canvas canvas) {
int saveCount = canvas.save();
canvas.translate(dx, dy); // those are the values calculated by your OnTouchListener
super.onDraw(canvas); // let the image view do the normal draw
canvas.restoreToCount(saveCount); // restore the canvas position
}
this last option is very interesting as it's not changing the layouts in absolutely no way. It's only making the ImageView take care of the whole area and moving the drawing to the correct position. Very efficient way of doing.
on some of those options you'll need to call "invalidate" on the view, so the draw can be called again.
onTouch is an event that fires repeatedly, and you are asking the view to redraw itself in a new location every time onTouch is fired. Draw is a very expensive operation for your application - it should never be asked to do this repeatedly in the regular view hierarchy. Your goal should be to reduce the number of draw commands that occur, so I would set a simple modulus operation that only allows it to execute the move every, say, 10 onTouch events. You will have a tradeoff eventually where it looks choppy because it isn't occurring every frame if you put your modulus too high, but there should be a balance in the 5-30 range.
Above in your class, you'll want something like:
static final int REFRESH_RATE = 10; //or 20, or 5, or 30, whatever works best
int counter = 0;
And then in your touch event:
case MotionEvent.ACTION_MOVE: {
counter += 1;
if((REFRESH_RATE % counter) == 0){ //this is the iffstatement you are looking for
int x_cord = (int) event2.getRawX();
int y_cord = (int) event2.getRawY();
float dx = x_cord - mLastTouchX;
float dy = y_cord - mLastTouchY;
layoutParams.leftMargin += dx;
layoutParams.topMargin += dy;
layoutParams.rightMargin -= dx;
final FrameLayout.LayoutParams finalLayoutParams = layoutParams;
inSituArt.post(new Runnable() {
#Override
public void run() {
inSituArt.setLayoutParams(finalLayoutParams);
}
});
mLastTouchX = x_cord;
mLastTouchY = y_cord;
}
counter = 0;
}
I found a real solution after a long time.
You should use a Drawable instead of a Bitmap.
override fun onDraw(canvas: Canvas?) {
canvas?.save()
canvas?.concat(myCustomMatrix)
mDrawable?.draw(canvas!!)
canvas?.restore()
}
move image (dx, dy)
myCustomMatrix.postTranslate(dx, dy)
invalidate()
I am using a small square RelativeLayout as a game pad for movement in a neighboring GLSurfaceView.
I set it up so that in the onTouch method of the RelativeLayout's onTouchListenner I call a method built into the GLSurfaceView that updates the translation and rotation coordinates for my drawing.
I have everything working fine, except for the fact that touch events are only triggered if the user moves his or her finger on the RelativeLayout.
I would like it to feel a little bit like a joystick: if you leave your finger pressed on the top of the RelativeLayout, then you will keep going "up" ( or more programatically: the event.getX() and event.getY() that were sent last should get looped, or re-sent, in the GLSurfaceView until the user either moves his finger inside the RelativeLayout, or stops touching the RelativeLayout altogether.)
What should I use to detect whether or not the RelativeLayout is currently being touched (even if there is no motion in the said touch)?
Thanks!
So this is what I came up pretty much simultaneously with csmcklvey
if(event.getAction()==MotionEvent.ACTION_UP)
gM.isTouchedL = false;
else
gM.isTouchedL = true;
return true;
Where .isTouchedL is the "control boolean" I use in the GLSurfaceView
Green lit csmc's answer anyways! :)
I have used rotation animation in one of my apps, which is using the onTouchEven of view. Major function has been performed in event "ACTION_MOVE". It will help you, to get through.
public boolean onTouch(View v, MotionEvent event)
{
final float xc = volumeButton.getWidth() / 2;
final float yc = volumeButton.getHeight() / 2;
final float x = event.getX();
final float y = event.getY();
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
{
// volumeButton.clearAnimation();
// mCurrAngle = Math.toDegrees(Math.atan2(x - xc, yc - y));
break;
}
case MotionEvent.ACTION_MOVE:
{
mPrevAngle = mCurrAngle;
mCurrAngle = Math.toDegrees(Math.atan2(x - xc, yc - y));
animate(mPrevAngle, mCurrAngle, 100);
break;
}
case MotionEvent.ACTION_UP :
{
mPrevAngle = mCurrAngle = 0;
break;
}
}
return true;
}
You might be able to use the event.getAction() method of the MotionEvent parameter of your onTouch() method. You can check to see if it is MotionEvent.ACTION_DOWN or MotionEvent.ACTION_UP.
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//start something
}
else if (event.getAction() == MotionEvent.ACTION_UP) {
//stop something
}
My thought is that you might be able to start or stop a thread in this manner but either way this might at least give you some more information about when the user actually presses and lifts his/her finger.
can any one help me how to drag and drop the text view on entire image view. i found from this drag n drop textview in android here the text view is static, in my app textview and image view's are dynamically change. `#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Remember our initial down event location.
startX = event.getRawX();
startY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float x = event.getRawX();
float y = event.getRawY();
// Calculate move update. This will happen many times
// during the course of a single movement gesture.
scrollByX = x - startX; //move update x increment
scrollByY = y - startY; //move update y increment
startX = x; //reset initial values to latest
startY = y;
invalidate(); //force a redraw
break;
}
return true; //done with this event so consume it
}
i wright this code, but it's not working properly.
You can always change the image and text of existing view programatically using setText sort of methods.
There are many drag and drop tutrials if you google it. strongly suggest you try them out.
Basically what you do is implement a OnTouchListener on the TextView like
textview.setOntouchListener(listen);
OnTouchListener onThumbTouch = new OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
switch(v.getId())
{
case R.id.slider_thumb_id:
{
switch(event.getAction())
{
case MotionEvent.ACTION_MOVE:
{
}
case MotionEvent.ACTION_UP:
{
}
}
break;
}
}
return true;
}
};
Here from the event attribute you get the value of x and y and the touch event and
use that to alter the position of the textview. I did it by add add/subtracting the margin values.
EDIT:
This is what I did to update the position of my view
Look event.getRawY(); is going to give you the distance from the parent view. So if I get event.getRawY() = 50. I know I need the text view to be 50 pixels from the top.
LinearLayout.LayoutParams iconParams = (LinearLayout.LayoutParams)
v.getLayoutParams();
iconParams.topMargin = 50;
v.setLayoutParams(iconParams);
So now the view that i selected is 50 px from the top of the parent view.