Mapview Fragment + ViewPager, causing strange interactions - android

I have spent a ridiculous number of hours trying to get a functioning, responsive mapview fragment working in my viewpager, and now that it is finally working I'm not quite sure why!
Background:
I am using ActionBarSherlock for my actionbar, I am using a viewpager to switch between three fragments: a list, an imageview + textview, and a mapview. I have a viewpagerindicator to go with my viewpager. I am using the maps version of the android-support-v4.
What Worked:
- Override the viewpager's onInterceptTouchEvent method to return false
- Set an onClickListener on the mapview that does nothing.
The mapview would appear, but would not respond before I added the listener, but why did adding an onclicklistener make ALL the gestures responsive?

Maybe you should start again. In my case, all i had to do was put MapFragment in ViewPager with FragmentPageAdapter.
Then created my own ViewPager with overriden method just to make MapFragment usable:
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// hacky hack to never intercept map pages events unless its on the
// very edge of screen - last or first fifth of the view size
if (((FragmentPagerAdapter) getAdapter()).getItem(getCurrentItem()) instanceof SupportMapFragment) {
if (event.getAction() == MotionEvent.ACTION_DOWN && event.getX() < (getWidth() - (getWidth() / 5)) && event.getX() > (getWidth() / 5)) {
// Never allow swiping to switch if not right on the edge
return false;
}
}
return super.onInterceptTouchEvent(event);
}

Related

How to detect View pager vertical swipe and send that event to the layout below ?

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

Disable last page on ViewPager

i'm using viewpagerIndicator in my app, i have 200 pages. is possible disable the Swipe 1 page before the last page?
Example my currentpage is the 199 here the swipe is disble and never change to the next page, only the previous pages.
EDIT: i have one calendar in the Tabindicator, the pages show info for day, i need show the next day but not swipe because not exist info for this reason i need disable this page.
Exam:
19/ago/2014 | 20/ago/2014 | 21/ago/2014
before |Current |Last Page
in before i can swipe left and right but in Current i need only swipe left and disable right is possible?
You can return 199 from getCount() from adapter that way viewpager will not know there are 200 pages.
EDIT:
Well In that case, you have to override viewpager and manually disable the motion when viewpager is showing second last page.
It would look something like this
First extend : class CustomViewPager extends ViewPager {...}
float lastX;
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if(getCurrentItem() == getAdapter().getCount()-1){
if(ev.getAction() == MotionEvent.ACTION_DOWN){
lastX = ev.getX();
// Just touch down let viewpager also handle touch event
return super.onInterceptTouchEvent(event);
}
else if(ev.getAction() == MotionEvent.ACTION_MOVE){
float xDiff = ev.getX() - lastX;
if(xDiff > 0){
// Attempt to move right on second last page
// Return false, we dont want to handle this movement
return false;
}
else{
// Left you can move
return super.onInterceptTouchEvent(event);
}
}
// Notice that we are not updating lastX on motion towards left
// This is to enable user can swipe towards left a bit and comeback, but dont dare go right
}
else{
// Not on second last page let viewpager handle page event
return super.onInterceptTouchEvent(event);
}
}

Circular ViewPager implementation

Solution-1: By specifying a very large number as the actual count, but maps them to the actual range of the dataset/pageset
ViewPager as a circular queue / wrapping
Problem: It doesn't work if there are less than 4 items.
Solution-2 Adding 2 extra items i.e. last item in the first and first item in the last and onPageSelected, depending on the position values, set the current item
Implementing Circular Scrolling In PagerAdapter
Problem: Extra pager indicators and extra fragment instances.
Solution-3: Using the onTouchListener on ViewPager
Implementing Circular Scrolling In PagerAdapter
Problem: The first MotionEvent is always null. Is it because of the ViewPager's own onTouchListener implementation?
Is there a proper solution for this?
Or even if the 3rd solution works fine, it would be great.
I use a combination of Solution 2 and Solution 3, I use the on touch listener of the pager to watch for swipes and see if I need to show the copy of the first or last page based on the current page
example:
mPager.setOnTouchListener(new OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
dxInitial=event.getX();
//
}else if(event.getAction() == MotionEvent.ACTION_MOVE){
//
if(event.getX() - dxInitial > 50 && currentPage==1){
}else if(dxInitial - event.getX() > 50 && currentPage==5){
mPager.setCurrentItem(0,false);
}else if(dxInitial - event.getX() > 50 && currentPage==6){
mPager.setCurrentItem(1,false);
}else if(event.getX() - dxInitial > 50 && currentPage==0){
mPager.setCurrentItem(5,false);
}else if(event.getX() - dxInitial > 100 && currentPage==6){
//
mPager.setCurrentItem(1,false);
}
}
return false;
}
});
you have extra pager indicators but the user should never see or notice them, just set smoothScroll to false when you change the page

achartengine with panning inside a viewpager

I have an achartengine GraphicalView chart inside a android.support.v4.view.ViewPager.
I want my chart to pan when the user drags her finger on it, but now the event is being caught by the ViewPager after a small drag movement.
Preferably, I would like to be able to let the user pan to the end of the chart and then let the ViewPager switch pages.
As an alternative, I would like to at least stop the ViewPager from catching the drag movement inside the chart.
Any ideas?
Here goes my solution.
It allows the user to drag the graph around while there is data in it. After that, the drag events are caught by the ViewPager.
Like I said, the key is this solution is the requestDisallowInterceptTouchEvent function.
public class ParameterGraphicalView extends org.achartengine.GraphicalView {
// stores the data model size
private int mDataSize = 0;
// stores the first X position in the dataset
private long mDataStartX = 0;
// stores the last X position in the dataset
private long mDataEndX = 0;
// the ViewPager
private ViewParent mViewPager;
//(...)
#Override
public boolean onTouchEvent(MotionEvent event) {
// save the position of the first touch so we can determine whether the user is dragging
// left or right
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mFirstTouchX = event.getX();
}
// when mViewPager.requestDisallowInterceptTouchEvent(true), the viewpager does not
// intercept the events, and the drag events (pan, pinch) are caught by the GraphicalView
// we want to keep the ViewPager from intercepting the event if:
// 1- there are 2 or more touches, i.e. the pinch gesture
// 2- the user is dragging to the left but there is no data to show to the right
// 3- the user is dragging to the right but there is no data to show to the left
if (event.getPointerCount() > 1
|| (event.getX() < mFirstTouchX && mDataSize > 0 && mRenderer.getXAxisMax() < mDataEndX)
|| (event.getX() > mFirstTouchX && mData.size() > 0 && mRenderer.getXAxisMin() > mDataStartX)) {
mViewPager.requestDisallowInterceptTouchEvent(true);
}
else {
mViewPager.requestDisallowInterceptTouchEvent(false);
}
return super.onTouchEvent(event);
}
}
I think you should Implement your own ViewPager and handle Touch event yourself.
Maybe this Link helps you : how to disable viewpager adapter on touching specific views?

Nesting Android ViewPager, Swiping ListItems inside a ListView horizontally

I have a ViewPager on the root-level of an activity.
Each page of the pager contains a ListFragment (backed by a FragmentPagerAdapter).
Some of the list view items should contain additionally ViewPagers to support swiping the content of those items (e. g. a horizontal gallery inside a list item).
How can I nest view pagers? ViewPager -> ListView (in a page) -> ViewPager (inside a list item)
I can swipe between the ListFragments horizontally and I can swipe the whole list vertically, but I cannot swipe inside list items.
I added an OnTouchListener to the interior ViewPager:
private OnTouchListener mSuppressInterceptListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(
event.getAction() == MotionEvent.ACTION_DOWN &&
v instanceof ViewGroup
) {
((ViewGroup) v).requestDisallowInterceptTouchEvent(true);
}
return false;
}
};
This just detects ACTION_DOWN touch events on the inner ViewPager and prevents the outer one from intercepting it. Because it returns false, only the ACTION_DOWN event should be hit; all the other events will be ignored. You can add this listener to every element you want to "protect" from the outer ViewPager's scrolling, though obviously if you want to pick up any other touch behaviour on those elements you'll need to deal with them inside the touch listener and possibly implement a better listener.
Credit to #Rodja who gave me the idea in the first place.
While it's not the best interaction design, it is possible to implement this by overwriting the dispatchTouchEvent(MotionEvent ev) method of the root-level Activity and using requestDisallowInterceptTouchEvent(true) on the mainPager and the current ListView to prevent other scrolling. Look at this example:
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Fragment listFragment = getSupportFragmentManager().findFragmentByTag(
"android:switcher:" + R.id.pager + ":" + (mainPager.getCurrentItem()));
mainPager.getChildAt(mainPager.getCurrentItem());
if (listFragment == null)
return super.dispatchTouchEvent(ev);
ViewPager embeddedPager = (ViewPager) listFragment.getView().findViewById(R.id.videopager);
if (embeddedPager != null) {
int[] position = new int[2];
embeddedPager.getLocationOnScreen(position);
if (ev.getY() > position[1] && ev.getY() < position[1] + embeddedPager.getHeight()) {
mainPager.requestDisallowInterceptTouchEvent(true);
if (embeddedPager.getScrollX() % embeddedPager.getWidth() != 0) {
ListView listView = (ListView) listFragment.getView().findViewById(
android.R.id.list);
listView.requestDisallowInterceptTouchEvent(true);
}
}
}
return super.dispatchTouchEvent(ev);
}
You can't really nest elements that need the same gestures to control them. Since the view pager is already capturing the horizontal motion, your nested elements will not get it. You could probably do a lot of work to get around this by managing focus and the like - but in the end your app will be confusing for users. Its really better to not nest elements that would use the same interaction... in this case two view pagers both watching for a side to side motion.

Categories

Resources