Disable view pager swipe for other child fragments

Viewed 533

I am working with ViewPager2 where i have 2 fragments in the viewpager. It works perfectly but there is a problem that i want it to just be swiped from FragmentTwo to FragmentOne fragment as it is working now. But when i swipe it from FragmentOne to FragmentTwo then it will be able to swipe only from the FirstFragment child fragment of FragmentOne fragment.

Image of my project, the image

In this image MainActivity is base activity where the viewpager2 is placed. It contains 2 fragment, FragmentOne and FragmentTwo. FragmentOne has a customized viewpager2 for BottomNavigationView. It has 3 fragments the first one is HomeFragment and this fragment has a button two transact to FragmentTwo. So i just want that the swipe transaction can be happen only from the HomeFragment, Not from its child or other two fragments. Like instagram where we can just swipe only from HomeFragment to ChatFragment.

Edited

Code for ViewPager in MainActivity

  override fun onCreate(savedInstanceState: Bundle?) {
    val mViewPager = findViewById<ViewPager2>(R.id.view_pager_main_activity)
        mViewPager?.offscreenPageLimit = 2
        mViewPager?.apply { // to disable overScrollAnimation
           (getChildAt(0) as? RecyclerView)?.overScrollMode = RecyclerView.OVER_SCROLL_NEVER
        }
  }


internal class MyViewPagerAdapter(fragmentActivity: FragmentActivity)
    :FragmentStateAdapter(fragmentActivity)
{

    private val fragments: ArrayList<Fragment> = ArrayList()

    fun addFragment(fragment: Fragment)
    {
        fragments.add(fragment)
    }

    override fun getItemCount(): Int {
        return 2
    }

    override fun createFragment(position: Int): Fragment {
        when (position) {
            0 -> {
                return MainFragment()
            }
            1 ->{
                return MessengerFragment()
            }
        }
        return fragments[position]
    }

}

I've tried this way by getting the current fragment & last fragment & .isVisible or .add fragment in the MainActivity but nothing works.

Example:

if (!HomeFragment().isVisible){
            mViewPager?.isUserInputEnabled = false
        }
2 Answers

This is an awful problem. On ios it is much easier. For me this worked:

If you attach the background, so the pager, the swipe is completely normal. But if you attach a child of the current fragment, the swipe will be registered in the onTouch method of the child. Thats pretty basic right.

You cannot return false from the onTouch interface of the child, because you have to touch it vertically...

Solution: You create a SwipeListener for every child view that can be attached. The SwipeListener can detect if you are swiping.

But that's not the plan. You have to check whether the user swiped vertically. If so you can get the offsetX. It is normally a very small amount, which depends on the scroll speed. You connect the offsetX from the SwipeListener with the fakeDragBy(float) of the viewPager.

The scroll from the child will be put on the viewPager scroll. You connect onSwipe(...) with fakeDragBy(...).

Of course you have to calculate the amount of fakeDragBy but that is simple. Look up the documentary. docs

Tip: Call beginFakeDrag before dragging.

Example:

package com.exsent.app.act;

import android.os.Bundle;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;

import com.exsent.app.R;

/**
* Created by Jim-Linus Valentin Ahrend on 3/26/21.
* 
**/
public class EXAMPLE extends AppCompatActivity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ViewPager2 vp2 = findViewById(R.id.viewPager);
    RecyclerView.Adapter anyAdapter = /* your adapter */ null;
    vp2.setAdapter(anyAdapter);

    //so this is the basic background
    //now goto the Adapter


}

void drag(int dx){
    ViewPager2 vp2 = findViewById(R.id.viewPager);
    vp2.beginFakeDrag();
    vp2.fakeDragBy(dx);
    
}

static class MyExampleAdapter extends 
     RecyclerView.Adapter<MyExampleAdapter.ViewHolder>{

    private EXAMPLE x;
    MyExampleAdapter(EXAMPLE x){this.x = x;}
    private GestureDetector detector;

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int 
         position) {

        if(position == 0){

            //gesture detector

            this.detector = new GestureDetector(x, new 
       MyGestureListener());

            RecyclerView recyclerView = 
        holder.itemView.findViewWithTag("recycler");
            recyclerView.setOnTouchListener(new 
         View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    return detector.onTouchEvent(event);
                }
            });


        }

    }

    @Override
    public int getItemCount() {
        return 2;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        FrameLayout fragemntView = new FrameLayout(x);
        fragemntView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

        RecyclerView recyclerView = new RecyclerView(x);
        recyclerView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        recyclerView.setTag("recycler");

        fragemntView.addView(recyclerView);

        return new ViewHolder(fragemntView);
    }

    static class ViewHolder extends RecyclerView.ViewHolder {

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
        }


    }

    static class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
        
        
        EXAMPLE e;
        public MyGestureListener(EXAMPLE e){
            this.e = e;
        }

        @Override
        public boolean onDown(MotionEvent event) {
            Log.d("TAG","onDown: ");

            // don't return false here or else none of the other
            // gestures will work
            return true;
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            Log.i("TAG", "onSingleTapConfirmed: ");
            return true;
        }

        @Override
        public void onLongPress(MotionEvent e) {
            Log.i("TAG", "onLongPress: ");
        }

        @Override
        public boolean onDoubleTap(MotionEvent e) {
            Log.i("TAG", "onDoubleTap: ");
            return true;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2,
                                float distanceX, float distanceY) {
            Log.i("TAG", "onScroll: ");
            
            
            //drag
            if(distanceX>distanceY){ //this condition should check if it is vertical or horizontal. you can define it better if you want.
                
                e.drag(distanceX);
                
            }
            
            return true;
        }

        @Override
        public boolean onFling(MotionEvent event1, MotionEvent 
                               event2,
                               float velocityX, float velocityY) {
            Log.d("TAG", "onFling: ");
            return true;
        }
    }
   }
}

Well, as ViewPager2 is a final class, we cannot simply extend it and override the onInterceptTouchEvent to achive this one-way swap feature.

But I think we can use a custom view class extended from FrameLayout which accepts a ViewPager2 as its only child, then override the dispatchTouchEvent() to manipulate the touch event dispatching.

Register a listener to ViewPager2's page change listener, and store the current page value when the callback is invoked.

In the dispatchTouchEvent(), when the page is 1, call super.dispatchTouchEvent() for normal dispatching, when the page is 0, dispatch the motion event object directly to ViewPager2's first child.

I didn't write actual code to prove my thought. if you need, I could write a sample project to see if it actually work.

Related