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.
Related
I've implemented Dave Smith's elegant solution to displaying multiple views inside a ViewPager here, but am having trouble dispatching touch events to the fragments that are not the "focused" one.
In his PagerContainer solution, there is functionality to handle the touch events outside of the ViewPager's focused area (see below), but that's only to enable scrolling. I need those touch events to actually interact with the views on the fragments themselves.
Does anyone have any experience with this?
#Override
public boolean onTouchEvent(MotionEvent ev) {
//We capture any touches not already handled by the ViewPager
// to implement scrolling from a touch outside the pager bounds.
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mInitialTouch.x = (int)ev.getX();
mInitialTouch.y = (int)ev.getY();
default:
ev.offsetLocation(mCenter.x - mInitialTouch.x, mCenter.y - mInitialTouch.y);
break;
}
return mPager.dispatchTouchEvent(ev);
}
How do I get the touch events from the PagerContainer propagated to the appropriate fragment?
You can use Event bus for this purpose checkout this link how to use it.I have used it for this kind of communications between activities and fragments. https://github.com/greenrobot/EventBus
So, I've got a partial solution implemented but it's not the final answer. In the OnTouchEvent overridden in the PagerContainer, I'm now checking the HitRect of each fragment and determining what fragment the event's point is inside. The problem now? How to stop scrolling to the fragment that's been selected.
Here's what I've tried, but it's still scrolling to the clicked on fragment. Any ideas on how to stop this?
#Override
public boolean onTouchEvent(MotionEvent evt) {
//We capture any touches not already handled by the ViewPager
// to implement scrolling from a touch outside the pager bounds.
int fragCount = mPager.getAdapter().getCount();
for(int i = 0; i < fragCount; i++)
{
View view = mPager.getChildAt(i);
Rect rect = new Rect();
view.getHitRect(rect);
if(rect.contains((int)evt.getX(),(int)evt.getY()))
{
int currentItem = mPager.getCurrentItem();
if(currentItem != i)
{
mPager.clearOnPageChangeListeners();
mPager.setCurrentItem(currentItem + i, false);
mPager.addOnPageChangeListener(this);
}
break;
}
}
return mPager.dispatchTouchEvent(evt);
}
If anyone stumbles on this question and is looking for a solution, I ended up using HorizontalGridView instead of ViewPager...
Javadoc for HorizontalGridView here
HorizontalGridView Sample
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?
Currently I'm using ListView and it's working fine. But I have text in a ListView that is like a paragraph and I just want to show those 2 lines of text and make the rest of the text scrollable, but I'm having an issue that if I make the TextView scrollable inside of the ListView, then the TextView get the focus of its parent (ListView) and won't let it be scrolled.
So can I achieve this scrollable TextView functionality that won't disturb the scrolling property of the ListView?
Thank you.
I was able to do this in the following way:
Into the getView method of the ListAdapter obtain the TextView object of the line, and write
textView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
v.getParent().getParent().requestDisallowInterceptTouchEvent(true);
return false ;
}
});
when you will touch the TextView, take control of the scrolling
What you are trying to achieve really is impossible. How can the OS know that you are trying to scroll the list vs the list row paragraph? You would essentially need to scroll to the bottom of the list row paragraph before the actual list itself could scroll. This is confusing to the user, and not common UX.
I would suggest you look into ExpandableListView. It allows you to have collapsed versions of each row, in your case just 2 lines of text for each list row. When the user taps on the row, it could expand to the full paragraph form, and the list would be scrollable the whole time. There are plenty of tutorials you should be able to find online.
Nothing is Impossible yet way to do things are may b difficult. Directly this thing can`t be achieved but indirectly yes it can be achieved, and yes i achieved.
how did i achieve is a bit complex but yes will share that how did i achieve.
In a ListView when i click on Textview i block the Touch mode of the listView so that their toch method don't intercept each other, and that can be done by using requestDisallowInterceptTouchEvent(true);
this block the TouchListener of the parent (ListView).
Now when click on TextView i allow its touch listener and also setMovementMethod()
but for Movement i made a custom class and Class is Following
public class myScrollMethod extends ScrollingMovementMethod {
#Override
public void onTakeFocus(TextView widget, Spannable text, int dir) {
// TODO Auto-generated method stub
super.onTakeFocus(widget, text, dir);
}
#Override
public boolean onKeyDown(TextView widget, Spannable buffer,
int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_DOWN:
for (int i = 0, scrollAmount = getScrollAmount(widget); i < scrollAmount; i++) {
down(widget, buffer);
}
return true;
case KeyEvent.KEYCODE_DPAD_UP:
for (int i = 0, scrollAmount = getScrollAmount(widget); i < scrollAmount; i++) {
up(widget, buffer);
}
return true;
default:
return super.onKeyDown(widget, buffer, keyCode, event);
}
}
private int getScrollAmount(TextView widget) {
final int visibleLineCount = (int) ((1f * widget.getHeight()) / widget
.getLineHeight());
int scrollAmount = visibleLineCount - 1;
if (scrollAmount < 1) {
scrollAmount = 1;
}
return scrollAmount;
}
}
After that when i click on parent i enable the TouchIntercepter of the parent set true and that get hold on its parent and start scrolling.
By this way i have successfully achieved this requirement
today I got a problem with touch event handling on android custom views.In this case i have created parent view call weekview and chiled call weekdayview.i want implement touch event like singleTap,LongPress in child view only and when i swipe on parent or child i wanna scroll parent view.when i implement touch event in both view it dose not work.
can anyone help me on this.It's really helpful to me.
Thank you
class ChildView extends View {
public void setGestureDetector (GestureDetector g)
{
gesture = g;
}
#Override
public boolean onTouchEvent (....)
{
return gesture.onTouchEvent (....); // touch event will dispatch to gesture
}
}
class ParentView extends View implements GestureDetector.OnGestureListener {
gesture = new GestureDetector (this);
child = new ChildView (...);
child.setGestureDetector (gesture);
#Override
public boolean onTouchEvent (..)
{
// handle your parent touch event here
}
public boolean onDown (...)
{
return true;
}
public boolean fling (...)
{
// here to handle child view fling
}
}
This is peso-code (not real android java) to show you the concept to use GestureDetector, you can deal with all events from your child view in your parent View. As I tested on my android phone, onTouchEvent in ChildView didn't recognize ACTION_UP very well, so even you swipe your Child View, sometimes fling will not work (it depends on ACTION_UP).
So if you want to write more accurate swipe on your Child View, better write your owen Gesture Detect class, and in your ChildView, you can do this -
float oldX;
float distanceX;
public boolean onTouchEvent (MotionEvent event...)
{
if (event.getAction == MotionEvent.ACTION_DOWN) {
// handle down
oldX = event.getX ();
}
if (event.getAction == MotionEvent.ACTION_MOVE {
// handle move
distanceX = event.getX() - oldX; // more accurate
gesture.onSwipe (distanceX); // your own gesture class
}
}
Set your swipe velocity (to detect user's intention to swipe) and override the onTouchEvent() in your child view.
Here, call super.onTouchEvent() and return, which calls your parent view. Handle the events in the parent view.
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);
}