ViewPager inside ViewPager
I would like to create a ViewPager (with three items) where each of its view is another ViewPager (with two items). User then swipe items like this:
ViewPager1[0] ViewPager2[0]
ViewPager1[0] ViewPager2[1]
ViewPager1[1] Vie开发者_开发技巧wPager2[0]
ViewPager1[1] ViewPager2[1]
ViewPager1[2] ViewPager2[0]
ViewPager1[2] ViewPager2[1]
How would that be possible?
override canScroll in the parent ViewPager:
@Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
if(v != this && v instanceof ViewPager) {
return true;
}
return super.canScroll(v, checkV, dx, x, y);
}
Try this:
public class CustomViewPager extends ViewPager {
private int childId;
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (childId > 0) {
ViewPager pager = (ViewPager)findViewById(childId);
if (pager != null) {
pager.requestDisallowInterceptTouchEvent(true);
}
}
return super.onInterceptTouchEvent(event);
}
public void setChildId(int id) {
this.childId = id;
}
}
I searched a long time to make a ViewPager inside another ViewPager work and found the solution by "Android Noob" here. Thank you very much for that!
I wanted to share my solution, too. I added the possibility to switch the swipe management to the surrounding ViewPager once the last (most right) element in the inner ViewPager is reached. To prevent glitches, i also save the first swipe direction for the last elemen: i.e. if you swipe left first, a minimal right swipe doesnt reset the scroll state.
public class GalleryViewPager extends ViewPager {
/** the last x position */
private float lastX;
/** if the first swipe was from left to right (->), dont listen to swipes from the right */
private boolean slidingLeft;
/** if the first swipe was from right to left (<-), dont listen to swipes from the left */
private boolean slidingRight;
public GalleryViewPager(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
public GalleryViewPager(final Context context) {
super(context);
}
@Override
public boolean onTouchEvent(final MotionEvent ev) {
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
// Disallow parent ViewPager to intercept touch events.
this.getParent().requestDisallowInterceptTouchEvent(true);
// save the current x position
this.lastX = ev.getX();
break;
case MotionEvent.ACTION_UP:
// Allow parent ViewPager to intercept touch events.
this.getParent().requestDisallowInterceptTouchEvent(false);
// save the current x position
this.lastX = ev.getX();
// reset swipe actions
this.slidingLeft = false;
this.slidingRight = false;
break;
case MotionEvent.ACTION_MOVE:
/*
* if this is the first item, scrolling from left to
* right should navigate in the surrounding ViewPager
*/
if (this.getCurrentItem() == 0) {
// swiping from left to right (->)?
if (this.lastX <= ev.getX() && !this.slidingRight) {
// make the parent touch interception active -> parent pager can swipe
this.getParent().requestDisallowInterceptTouchEvent(false);
} else {
/*
* if the first swipe was from right to left, dont listen to swipes
* from left to right. this fixes glitches where the user first swipes
* right, then left and the scrolling state gets reset
*/
this.slidingRight = true;
// save the current x position
this.lastX = ev.getX();
this.getParent().requestDisallowInterceptTouchEvent(true);
}
} else
/*
* if this is the last item, scrolling from right to
* left should navigate in the surrounding ViewPager
*/
if (this.getCurrentItem() == this.getAdapter().getCount() - 1) {
// swiping from right to left (<-)?
if (this.lastX >= ev.getX() && !this.slidingLeft) {
// make the parent touch interception active -> parent pager can swipe
this.getParent().requestDisallowInterceptTouchEvent(false);
} else {
/*
* if the first swipe was from left to right, dont listen to swipes
* from right to left. this fixes glitches where the user first swipes
* left, then right and the scrolling state gets reset
*/
this.slidingLeft = true;
// save the current x position
this.lastX = ev.getX();
this.getParent().requestDisallowInterceptTouchEvent(true);
}
}
break;
}
super.onTouchEvent(ev);
return true;
}
}
Hope this helps someone in the future!
If the child viewpager is at the end, scroll the parent
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
if(v != this && v instanceof ViewPager) {
int currentItem = ((ViewPager) v).getCurrentItem();
int countItem = ((ViewPager) v).getAdapter().getCount();
if((currentItem==(countItem-1) && dx<0) || (currentItem==0 && dx>0)){
return false;
}
return true;
}
return super.canScroll(v, checkV, dx, x, y);
}
For a ViewPager2
, the current solution is to use a NestedScrollableHost
: https://github.com/android/views-widgets-samples/blob/master/ViewPager2/app/src/main/java/androidx/viewpager2/integration/testapp/NestedScrollableHost.kt
You can view the bug report and progress here: https://issuetracker.google.com/issues/123006042
First create a custom ViewPager class in this way:
public class CustomViewPager extends ViewPager {
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
if(v instanceof ViewPager) {
return true;
}
return super.canScroll(v, checkV, dx, x, y);
}
}
The return (boolean) of the method canScroll will tell you if the horizontal gesture to change page for ViewPager needs to be in the right or left border of the fragment(true) or if it works for full fragment screen (false). If you want, for example, that only your first fragment use the right border to move to the next fragment because the first fragment has another horizontal scrolling event, this will be the code to overriding the method canScroll:
@Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
if(v instanceof ViewPager) {
int currentItem = ((ViewPager) v).getCurrentItem();
if((currentItem==0)){
return true;
}
return false;
}
return super.canScroll(v, checkV, dx, x, y);
}
The Last Step will be to use your CustomViewPager class in your main class:
ViewPager myPager= (CustomViewPager)myContext.findViewById(R.id.myCustomViewPager);
and the xml:
<my.cool.package.name.CustomViewPager
android:id="@+id/myCustomViewPager"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1" />
I solve this task by creating two custom ViewPager's inheritors. In my case - OuterViewPager and InnerViewPager.
public class InnerViewPager extends ViewPager
{
private int mPrevMoveX;
public InnerViewPager(Context context)
{
super(context);
}
public InnerViewPager(Context context, AttributeSet attrs)
{
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event)
{
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
mPrevMoveX = (int) event.getX();
return super.onTouchEvent(event);
case MotionEvent.ACTION_MOVE:
int distanceX = mPrevMoveX - (int) event.getX();
mPrevMoveX = (int) event.getX();
boolean canScrollLeft = true;
boolean canScrollRight = true;
if(getCurrentItem() == getAdapter().getCount() - 1)
{
canScrollLeft = false;
}
if(getCurrentItem() == 0)
{
canScrollRight = false;
}
if(distanceX > 0)
{
return canScrollRight;
}
else
{
return canScrollLeft;
}
}
return super.onInterceptTouchEvent(event);
}
public boolean onTouchEvent(MotionEvent event)
{
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPrevMoveX = (int) event.getX();
return super.onTouchEvent(event);
case MotionEvent.ACTION_MOVE:
int distanceX = mPrevMoveX - (int) event.getX();
mPrevMoveX = (int) event.getX();
boolean canScrollLeft = true;
boolean canScrollRight = true;
if(getCurrentItem() == getAdapter().getCount() - 1)
{
canScrollLeft = false;
}
if(getCurrentItem() == 0)
{
canScrollRight = false;
}
if(distanceX > 0)
{
super.onTouchEvent(event);
return canScrollLeft;
}
else
{
super.onTouchEvent(event);
return canScrollRight;
}
}
return super.onTouchEvent(event);
}
}
public class OuterViewPager extends ViewPager
{
private int mPrevMoveX;
public OuterViewPager(Context context)
{
super(context);
init();
}
public OuterViewPager(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
private void init()
{
setOnPageChangeListener(new CustomPageChangeListener());
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
switch (ev.getAction())
{
case MotionEvent.ACTION_DOWN:
mPrevMoveX = (int) ev.getX();
return super.onInterceptTouchEvent(ev);
case MotionEvent.ACTION_MOVE:
/*there you should get currentInnerPager - instance of InnerPager on current page of instance of OuterPager*/
int distanceX = mPrevMoveX - (int) ev.getX();
mPrevMoveX = (int) ev.getX();
boolean canScrollLeft = true;
boolean canScrollRight = true;
if(currentInnerPager.getCurrentItem() == currentInnerPager.getAdapter().getCount() - 1)
{
canScrollLeft = false;
}
if(currentInnerPager.getCurrentItem() == 0)
{
canScrollRight = false;
}
if(distanceX > 0)
{
return !canScrollLeft;
}
else
{
return !canScrollRight;
}
}
return super.onInterceptTouchEvent(ev);
}
}
Outer pager starts scroll left only when inner pager on last page. And vice versa.
I just test this case, you can make it without extra working, below is my demo
public class MainActivity extends AppCompatActivity {
public static final String TAG = "TAG";
ViewPager parentPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
initViews();
initData();
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
}
private void initViews() {
parentPager = (ViewPager) findViewById(R.id.parent_pager);
}
private void initData() {
List<ViewPager> pagers = new ArrayList<ViewPager>();
for(int j = 0; j < 3; j++) {
List<LinearLayout> list = new ArrayList<LinearLayout>();
for (int i = 0; i < 5; i++) {
LinearLayout layout = new LinearLayout(this);
TextView textView = new TextView(this);
textView.setText("This is the" + i + "th page in PagerItem" + j);
layout.addView(textView);
textView.setGravity(Gravity.CENTER);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) textView.getLayoutParams();
params.gravity = Gravity.CENTER;
list.add(layout);
}
MyViewPagerAdapter adapter = new MyViewPagerAdapter(list);
final ViewPager childPager = (ViewPager) LayoutInflater.from(this).inflate(R.layout.child_layout, null).findViewById(R.id.child_pager);
childPager.setAdapter(adapter);
childPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
Log.d(TAG, "onPageScrolled: position: " + position + ", positionOffset: " + positionOffset);
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
pagers.add(childPager);
}
MyParentViewPagerAdapter parentAdapter = new MyParentViewPagerAdapter(pagers);
parentPager.setAdapter(parentAdapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
class MyViewPagerAdapter extends PagerAdapter {
private List<LinearLayout> data;
public MyViewPagerAdapter(List<LinearLayout> data) {
this.data = data;
}
@Override
public int getCount() {
return data.size();
}
@Override
public int getItemPosition(Object object) {
return data.indexOf(object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
LinearLayout linearLayout = data.get(position);
container.addView(linearLayout);
return data.get(position);
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
LinearLayout layout = data.get(position);
container.removeView(layout);
layout = null;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
class MyParentViewPagerAdapter extends PagerAdapter {
private List<ViewPager> data;
public MyParentViewPagerAdapter(List<ViewPager> data) {
this.data = data;
}
@Override
public int getCount() {
return data.size();
}
@Override
public int getItemPosition(Object object) {
return data.indexOf(object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
ViewPager pager = data.get(position);
if(pager.getParent() != null) {
((ViewGroup) pager.getParent()).removeView(pager);
}
container.addView(pager);
return data.get(position);
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
ViewPager pager = data.get(position);
container.removeView(pager);
pager = null;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
}
The xml is simple, outer ViewPager
in my main layout and the inner ViewPager
in another LinearLayout
I don't understand why don't you just create 1 view pager and make the instantiate item logic to get data from different sources, which would make you reach your goal equally
I do not see a case where you need 2 viewpagers
Example
ViewPager1[0] ViewPager2[0] = page 0 (position/2) = 0
ViewPager1[0] ViewPager2[1] = page 1 ((position-1)/2) = 0
ViewPager1[1] ViewPager2[0] = page 2 (position/2) = 1
ViewPager1[1] ViewPager2[1] = page 3 ((position-1)/2) = 1
ViewPager1[2] ViewPager2[0] = page 4 (position/2) = 2
ViewPager1[2] ViewPager2[1] = page 5 ((position-1)/2) = 2
and in the code:
@Override
public Object instantiateItem(View collection, int position) {
LayoutInflater inflater = THISCLASSNAME.this.getLayoutInflater();
View v = null;
if(position%2 == 0) {
// viewpager 1 code
int vp1pos = position/2;
v = inlater.inflate(R.layout.somelayout, collection, false);
Button b = (Button)v.findViewById(R.id.somebutton);
b.setText(array1[vp1pos]);
} else {
int vp2pos = (position-1)/2;
v = inlater.inflate(R.layout.somelayout, collection, false);
Button b = (Button)v.findViewById(R.id.somebutton);
b.setText(array2[vp2pos]);
}
((DirectionalViewPager) collection).addView(v, 0);
return v;
}
this way you have virtually 2 viewpagers
logic, you may customize it more than that I am just giving you ideas
P.S. I coded this here so if there are character case mistakes or spelling mistakes forgive me.
hope this helps, if you get more specific and need more help to add a comment on my answer and I will amend it
精彩评论