Is it possible to create a list view in android that is not fixed to a particular position and user can move the list view on a gesture some what floating listview that can be moved anywhere on the screen?I tried finding it but could not find some link.Does any one have some idea about it?
You can achieve this by setting an OnTouchListener on the list view and overriding it's onTouch() method. In the onTouch() method you will have to handle the touch events like(ACTION_DOWN, ACTION_MOVE). Here is how you can handle touch event:
#Override
public boolean onTouch (View v, MotionEvent event)
{
switch(event.getAction() & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
start.set(event.getX(), event.getY()); //saving the initial x and y position
mode = DRAG;
break;
case MotionEvent.ACTION_MOVE:
if(mode == DRAG) {
float scrollByX = event.getX() - start.x; //computing the scroll in X
float scrollByY = event.getY() - start.y; //computing the scroll in Y
v.scrollBy((int)scrollByX/20, 0);
}
break;
}
return false;
}
When the user places the finger down on the screen the ACTION_DOWN event is triggered and when the user drags his finger on the screen ACTION_MOVE event is triggered. Hence a drag event is a combination of ACTION_DOWN and ACTION_MOVE.
Once you have the scrollX and scrollY value the you can move the view on the screen by passing these values to the scrollBy() method of the View class.
Here in the code there are two things to notice:
first is that I have passed 0 as the value of scrollY as it will prevent the list view to be dragged and scrolled simultaneously.
second is that the return value is false as you want the android to handle the scrolling of the list items
Here you can find the complete code for this
I would move the listview using the margins from the layout.
Outline:
Override OnTouch, and based on the event.getX() and event.getY(), update the margins of the listview as follows:
if the parent is a relative layout:
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)mListView.getLayoutParams();
lp.leftMargin = event.getX();
lp.topMargin = event.getY();
mListView.setLayoutParams(lp);
Add the dispatchTouchEvent to your activity so that events from the listview get passed up to onTouch:
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
onTouchEvent(ev);
return super.dispatchTouchEvent(ev);
}
Hope this helps.
Although it's hard to maintain, AbsoluteLayout lets you specify a position in (x,y) coordinates. You would just declare your ListView inside a AbsoluteLayout, get a handle to it in java code where you intercept the clicks and then set new coordinates.
In the Android framework, the word "floating" is usually synonymous with new Window (such as a Dialog). It depends somewhat on what your end goal is, but if you want to create a ListView that floats over the rest of the content in your hierarchy, that you can easily hide and show, you should probably consider wrapping that view inside a PopupWindow, which has methods to allow you to easily show the window with showAsDropdown() and shotAtLocation() and also move it around while visible with update().
This is how the newer versions of the SDK create pop-up lists for Spinner and Menu
Related
I have one view that has some vertical gestures like swipe down to move the view down etc. lets call this View rootView. Everything was good util I needed to add a view pager on top of it. Now the ViewPager is consuming all the touch events. The ViewPager is scrolling horizontally as it should, but consuming the vertical swipes too and doing nothing (not passing the event to the rootView). How to make both the Views listen to their corresponding touch events and send the rest to other. I tried creating a CustomViewPager and overriding its onTouchEvent in the hope of recognizing the swipe down first and return false in that case so that the rootview will get a chance to handle that touch event. But in the process of recognizing the gesture as down/up , the ViewPager is consuming ACTION_DOWN and ACTION_MOVE event which are needed by the rootView to process the amount of finger movement.
A solution that came to my mind is to add onTouchEvent on all the layout over the ViewPager, which recognize the Vertical vs horizontal and call the appropriate touchevent (of rootView vs ViewPager) , but in order to recognize the up/down/side gesture, the layout will consume some events which may be valueable to the rootView.
Another solution that comes to mind is to override the ontouchEvent of ViewPager and call the onTouchEvent of the rootView irrespective of the up/down/side movement. In this way both the ViewPager and rootView can use the event, but it is sometimes making the screen fluctuates.
How should I solve this problem ? I would appreciate some suggestions, and no need to provide the code, just a good way to solve this problem.
I've had to completely modify my original answer. My first solution was to subclass ViewPager and override onTouchEvent(), adding logic to switch between the CustomViewPager and the RelativeLayout below it. Returning false means the view ignores the event, and returning super.onTouchEvent(event) means the view consumes the event until the user lifts their finger. All the logic was in the ACTION_MOVE part of the event, but since you only get one chance to return a value (which is the first part of the event, ACTION_DOWN), you can't use any movement to determine whether to consume or ignore the event.
I finally got it to work by writing an entirely new class, starting with the ViewPager code and adding logic to recognize vertical gestures. When a vertical gesture occurs, code is run that acts on the RelativeLayout below it, instead of passing the touch event to it. Below is a code snippet from the onTouchEvent() of my class.
public class CustomViewPager extends ViewGroup {
// code from ViewPager class...
case MotionEvent.ACTION_MOVE:
if (!mIsBeingDragged) {
if (DEBUG) Log.v(TAG, "!mIsBeingDragged");
final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, pointerIndex);
final float xDiff = Math.abs(x - mLastMotionX);
final float y = MotionEventCompat.getY(ev, pointerIndex);
final float yDiff = Math.abs(y - mLastMotionY);
if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
if (xDiff > mTouchSlop && xDiff > yDiff) {
isVerticalMovement = false;
hasMovedHorizontally = true;
if (DEBUG) Log.v(TAG, "Starting drag!");
mIsBeingDragged = true;
requestParentDisallowInterceptTouchEvent(true);
mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
mInitialMotionX - mTouchSlop;
mLastMotionY = y;
setScrollState(SCROLL_STATE_DRAGGING);
setScrollingCacheEnabled(true);
// Disallow Parent Intercept, just in case
ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
else {
if (yDiff > mTouchSlop) {
// VERTICAL SWIPE -- do stuff here...
}
}
}
}
Have you tried placing the rootView on top of the ViewPager?
That way, the ViewPager should still work although being under the rootView
I have some views which I like to drag around. The views are within
a LinearLayout, which itself is within a scrollview.
I want to get the position of the current finger (touch), to
make a smooth scroll on my scrollview, depending of the
height of the current drag.
I start my draggin after a long click with the
View build-in listener startDrag
view.startDrag(null, shadowBuilder, view, 0);
I am also able to get the relativ position of the drag
on the view that is currently hovered by
view.setOnDragListener(new OnDragListener() {
#Override
public boolean onDrag(View v, DragEvent event) {
//get event positions
}
});
But this only works for the view where the drag shadow is currently on
and DragEvent only provides relative positions, not raw positions.
What I need is the position of the finger, while the drag happens. Unfortunately all onTouchEvents get consumed while dragging.
Does anybody know how I get this to work?
Ps: One way that works (kind of) which I currently use, is to calculate the
position of the touch via the dragEvent.relativePosition in combination
with the view position. But isn't there a better way to go?
OK, since there seems not to be an easier answer, I will provide my own relativly easy solution for further readers, with no need of gesture detection.
At first you need the view that receives the drag event and at second the
drag event ifself (or at least the x and y coordinates).
With fetching of the view position and some simple addition you get the
raw touch position.
This method is tested with the show pointer position developer options
to provide the correct data.
The method for the calculation looks like this:
/**
* #param item the view that received the drag event
* #param event the event from {#link android.view.View.OnDragListener#onDrag(View, DragEvent)}
* #return the coordinates of the touch on x and y axis relative to the screen
*/
public static Point getTouchPositionFromDragEvent(View item, DragEvent event) {
Rect rItem = new Rect();
item.getGlobalVisibleRect(rItem);
return new Point(rItem.left + Math.round(event.getX()), rItem.top + Math.round(event.getY()));
}
Call this method inside of your onDragListener implementation:
#Override
public boolean onDrag(View v, DragEvent event) {
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
//etc etc. do some stuff with the drag event
break;
case DragEvent.ACTION_DRAG_LOCATION:
Point touchPosition = getTouchPositionFromDragEvent(v, event);
//do something with the position (a scroll i.e);
break;
default:
}
return true;
}
Addtional:
If you want to determinate if the touch is inside of a specific view, you can
do something like this:
public static boolean isTouchInsideOfView(View view, Point touchPosition) {
Rect rScroll = new Rect();
view.getGlobalVisibleRect(rScroll);
return isTouchInsideOfRect(touchPosition, rScroll);
}
public static boolean isTouchInsideOfRect(Point touchPosition, Rect rScroll) {
return touchPosition.x > rScroll.left && touchPosition.x < rScroll.right //within x axis / width
&& touchPosition.y > rScroll.top && touchPosition.y < rScroll.bottom; //withing y axis / height
}
It is also possible to implement a smooth scroll on a ListView based on this solution.
So that the user can drag an item out of a list, and scroll the list via dragging the item either to the top or bottom of the list view.
Cheers.
I have a view in a linearlayout. When the view is longpressed the view will be removed from this linearlayout and placed, on the same position of the screen, to a relative layout. On this way a can move the view over the screen with my finger.
it almost works:
i got the longpress event working (remove the view and place the view to the relativelayout). after that i add an ontoucheventlistener so my view stays with my finger, but only for a second. the last time the touchevent is fired i got "MotionEvent.ACTION_CANCEL". When i remove my finger and place my finger again to the view i can go feature with my movement, then it will keep until i remove my finger.
I think that my problem is that the view it removed for a short moment, at that time i get a "MotionEvent.ACTION_CANCEL", however, there are still some unhandled events, they will be fired first. Thats why i got for about 1 second still ontouchevents. (this is just a thought).
someone a idee how i can keep the ontouchevent, or let the ontouchevent fired without replacing my finger?
Edited
My thought is not correct. When i do a longpress the view stays with my finger, however i lost the view as soon as i move about 50 to 100 pixels to any direction.
Edited 2
the longpress code of the view inside the linearlayout
view.setOnLongClickListener(new OnLongClickListener() {
public boolean onLongClick(View v) {
_linearLayout.removeView(v);
moveView(v);
return true;
}
});
moveView will be called by the longpress
private void moveView(View v) {
_relativeLayout.addView(v);
v.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_MOVE:
int x = (int) event.getRawX();
int y = (int) event.getRawY();
v.layout(x, y, x + v.getWidth(), y + v.getHeight());
break;
case MotionEvent.ACTION_UP:
_relativeLayout.removeView(v);
v = null;
break;
case MotionEvent.ACTION_CANCEL:
//it comes here when i move my finger more then 100 pixels
break;
}
return true;
}
});
}
of corse, this is the relevant part of the code and not the original code
Have a look at the Experience - Android Drag and Drop List post. There is also a source code that does something very similar to what are you trying to get. Eric, the author of that post, uses a separate temporary ImageView to hold a dragged view image, taken with getDrawingCache(). He also hides the original list item by setVisibility(View.INVISIBLE).
In the DragNDrop project (link above) you can replace drag(0,y) calls by drag(x,y) in DragNDrop.java to see a dragging in all directions.
Just throwing some possibilities in because I can't see the code but MotionEvent.ACTION_CANCEL could be being called as the parent layout is switched and the view is getting redrawn.
You could try overriding the onTouchEvent and at the point the ACTION_CANCEL event is sent try get hold of an equivalent view in the new layout and operate on that but I still think the better idea is to redesign so it all takes place in the same layout.
I have a simple application with two views, one is a TableView and the other is ListView. I use GestureDetector to detect the swipes across the screen similarly to how it is done here. Everything works OK, if the list view is populated with just a few items, however when the ListView fills up the whole screen the gesture detection stops working. Doing the swipe across the screen simply shows highlights one of the list items.
I think this is happening because ListView somehow steals the touch events from the GestureListener. Is there a way to prevent this?
I found that GestureDetector works fine in ListItems if you traverse a fairly accurate horizontal path. However if you stray slightly, the list scrolls and the gesture does not complete. What is going on is as follows:
The GestureDetector starts off happily taking MotionEvents from the onTouch you install in the ListItems via setOnTouchListener().
The ListView is meanwhile listening in on events sent to its child views via onInterceptTouchEvent()
The ListView detects that you have started to scroll and returns true from onInterceptTouchEvent().
From then on the MotionEvents are sent to the ListView and NOT to the original target... the ListView starts receiving MotionEvents in its onTouch handler. This continues until the final ACTION_UP. (Note that the MotionEvents from ACTION_DOWN through all the ACTION_MOVEs to ACTION_UP are considered a single gesture and everything starts again after the final ACTION_UP in the sequence)
The original target (ListItem) gets an ACTION_CANCEL MotionEvent and the GestureDetector in your ListItem bails out. This can be seen to happen if you paste the code for GestureDetector into your app and step through it.
I needed my app to behave as if the horizontal swipe continued even if the touch strays from horizontal slightly.
SOLUTION:
This involves ViewGroup.requestDisallowInterceptTouchEvent (boolean disallowIntercept) which stops the parent being able to peek at the motion events. The method involves implementing onTouchListener to detect a slight swipe (10 pixels or so) then stopping the parent intercepting motion events. The parent will then not scroll and the gesture detector continues to completion.
Here's the code:
private boolean mFlingInProgress = false;
private float mStartX = 0;
private final int FLING_TRIGGER_DISTANCE = 10;
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
float currentX = event.getRawX();
switch (action) {
case MotionEvent.ACTION_DOWN:
mStartX = currentX;
break;
case MotionEvent.ACTION_MOVE:
if (false == mFlingInProgress) {
if (Math.abs(currentX - mStartX) > FLING_TRIGGER_DISTANCE) {
// stop the parent intercepting motion events
mLayout.getParent().requestDisallowInterceptTouchEvent(true);
mFlingInProgress = true;
}
}
break;
case MotionEvent.ACTION_UP:
mFlingInProgress = false;
break;
}
return mGestureDetector.onTouchEvent(event);
}
You could create a custom listview and then implement the gesture detector inside of this i.e. on each row of the list. Could be worth a try.
I have a custom Android view which overrides onTouchEvent(MotionEvent) to handle horizontal scrolling of content within the view. However, when the ScrollView in which this is contained scrolls vertically, the custom view stops receiving touch events. Ideally what I want is for the custom view to continue receiving events so it can handle its own horizontal scrolling, while the containing view hierarchy deals with vertical scrolling.
Is there any way to continue receiving those motion events on scroll? If not, is there any other way to get the touch events I need?
Use requestDisallowInterceptTouchEvent(true) in the childview to prevent from vertical scrolling if you want to continue doing horizontal scrolling and latter reset it when done.
private float downXpos = 0;
private float downYpos = 0;
private boolean touchcaptured = false;
#Override
public boolean onTouchEvent(MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
downXpos = event.getX();
downYpos = event.getY();
touchcaptured = false;
break;
case MotionEvent.ACTION_UP:
requestDisallowInterceptTouchEvent(false);
break;
case MotionEvent.ACTION_MOVE:
float xdisplacement = Math.abs(event.getX() - downXpos);
float ydisplacement = Math.abs(event.getY() - downYpos);
if( !touchcaptured && xdisplacement > ydisplacement && xdisplacement > 10) {
requestDisallowInterceptTouchEvent(true);
touchcaptured = true;
}
break;
}
super.onTouchEvent(event);
return true;
}
I'm answering my own question in case anyone else is as bad at Googling for the answer as I apparently was. :P
A workaround for this problem is to extend ScrollView and override the onInterceptTouchEvent method so that it only intercepts touch events where the Y movement is significant (greater than the X movement, according to one suggestion).