-1

I have a ViewPager with 3 fragments in it. I have extended ViewPager and disabled its swiping abilities for reasons specific to my app. So, for navigation between these fragments, I've added a TabLayout to to my activity.

In my second fragment (the one "in the middle") I have a DrawerLayout on the left edge. Everytime I make a swiping motion from left to right in that fragment, the DawerLayout is shown, even if I swipe in the middle of the screen. It should only happen if I swiped close to the left edge of the screen.

I've been having a hard time understanding touch and motion events handling in Android... Is there a way to keep my ViewPager (swiping disabled) and restrict the touch events DrawerLayout 'listens to' to the edge of the screen (as its supposed to)?

If I do not modify the ViewPager (that is, if I keep swiping enabled), it also does not work because swiping from the left edge will begin to open the DrawerLayout, but at the same time, will begin the ViewPager's transition.

I've recreated the issue in a small app.

This is MyViewPager:

public class MyViewPager extends ViewPager {

    public MyViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return false;
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }
}

This is MainActivity:

public class MainActivity extends AppCompatActivity {

    Fragment Fragment1;
    Fragment Fragment2;
    Fragment Fragment3;
    MyViewPager viewPager;
    TabLayout tabLayout;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Fragment1 = new Fragment1();
        Fragment2 = new Fragment2();
        Fragment3 = new Fragment3();

        viewPager = findViewById(R.id.ViewPager);

        tabLayout = findViewById(R.id.TabLayout);

        viewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
            @Override
            public Fragment getItem(int position) {
                switch (position) {
                    case 0:
                        if (Fragment1 == null) {
                            Fragment1 = new Fragment1();
                        }
                        return Fragment1;
                    case 1:
                        if (Fragment2 == null) {
                            Fragment2 = new Fragment2();
                        }
                        return Fragment2;
                    case 2:
                        if (Fragment3 == null) {
                            Fragment3 = new Fragment3();
                        }
                        return Fragment3;
                    default:
                        return null;
                }
            }


            @Override
            public int getCount() {
                return 3;
            }


            @Override
            public CharSequence getPageTitle(int position) {
                switch (position) {
                    case 0:
                        return "frag1";
                    case 1:
                        return "frag2";
                    case 2:
                        return "frag3";
                    default:
                        return "0";
                }
            }
        });

        viewPager.setOffscreenPageLimit(2);
        viewPager.setCurrentItem(0);
        tabLayout.setupWithViewPager(viewPager);
    }
}

ActivityMain' layout:

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout
    android:id="@+id/main_frame"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#4dea21">

    <com.example.android.navbarviewpager.MyViewPager
        android:id="@+id/ViewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:background="#ea3ee2"/>

    <android.support.design.widget.TabLayout
        android:id="@+id/TabLayout"
        android:layout_width="wrap_content"
        android:layout_height="45dp"
        android:layout_gravity="left|bottom"
        app:tabMode="scrollable">

        <android.support.design.widget.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="frag1"/>

        <android.support.design.widget.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="frag2"/>

        <android.support.design.widget.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="frag3"/>

    </android.support.design.widget.TabLayout>

</FrameLayout>

Fragment1:

public class Fragment1 extends Fragment {


    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        return inflater.inflate(R.layout.fragment1, container, false);

    }
}

Fragment2:

public class Fragment1 extends Fragment {


    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        return inflater.inflate(R.layout.fragment2, container, false);

    }
}

Fragment3:

public class Fragment1 extends Fragment {


    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        return inflater.inflate(R.layout.fragment3, container, false);

    }
}

Fragment TWO's layout (the one with the DrawerLayout):

<?xml version="1.0" encoding="utf-8"?>

<android.support.v4.widget.DrawerLayout
    android:id="@+id/drawer_layout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/frag_1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#a2a2ee"
        android:padding="0dp">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="fragmento DOIS"/>

    </FrameLayout>

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left|center_vertical"
        android:background="#b9b9b9"
        android:clickable="true"
        android:orientation="vertical"
        android:paddingBottom="20dp"
        android:paddingTop="20dp">
        <TextView
            android:id="@+id/notified_nav"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="TTTTT"/>
        <TextView
            android:id="@+id/overdue_nav"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="XXXXX"/>
    </LinearLayout>
</android.support.v4.widget.DrawerLayout>

And the other fragments' layouts:

Fragment ONE

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout
    android:id="@+id/frag_1"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#eea2a2"
    android:padding="0dp">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="fragmento UM"
        android:gravity="center"/>

</FrameLayout>

Fragment THREE

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout
    android:id="@+id/frag_1"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#abeea2"
    android:padding="0dp">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="fragmento TRÊS"
        android:gravity="center"/>

</FrameLayout>
bernardo.g
  • 826
  • 1
  • 12
  • 27

2 Answers2

1

While I don't think that it is possible to have a horizontal ViewPager AND a horizontal DrawerLayout on top of each other, you could turn either one by 90 degree and make it a vertically sliding view (easy in the case of the DrawerLayout and, if you use a library-there are a number on github, also easy in the case of the ViewPager).

Concerning what seems to be at the core of your question: it is possible to limit the sensitive area of a DrawerLayout to the edge it is sliding from. To do so, you need to override DrawerLayout and use it instead of DrawerLayout in /layout/fragment2.xml.

This is the code:

public class MyDrawerLayout extends DrawerLayout {
    Context context;

    public MyDrawerLayout(Context context) {
        this(context, null);
    }

    public MyDrawerLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyDrawerLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.context = context;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent motionEvent) {
        // if the drawer is open already, pass motionEvent upwards
        if (this.isDrawerOpen(Gravity.START)) {
            return super.onInterceptTouchEvent(motionEvent);
        }
        // sensitive area is set to 1/4 of the drawer's full width
        // adopt to your needs
        int sensitiveWidth = ((Activity) context).findViewById(R.id.drawer_layout).getWidth() / 4;
        // if the drawer is not (fully) open AND
        // motionEvent.getX() is close to the left edge,
        // fully open the drawer and pass motionEvent upwards
        if (motionEvent.getX() < sensitiveWidth) {
            this.openDrawer(Gravity.START);
            return super.onInterceptTouchEvent(motionEvent);
        }
        // otherwise eat motionEvent
        return false;
    }
}

Two notes:

  1. Once the drawer is open, you probably want it to close after any interaction, even if it lies outside the "sensitive area". This is what the first if-clause in onInterceptTouchEvent is for.

  2. While the drawer is not fully open, the implementation swallows MotionEvents on the DrawerLayout outside of the "sensitive area". Therefore space for a "fling" gesture is narrow. In order to make opening the drawer easy, opening it programmatically is the option I am suggesting here. If your app does not rely on half opened/closed drawers, this seems like a good solution in terms of usability.

kalabalik
  • 3,792
  • 2
  • 21
  • 50
  • It worked out great, thank you. Is there a way to disable the 'peeking' state of the drawer (when there's just a little piece of it visible on the screen)? I've been looking through DrawerLayout's source code, but I don't know where to start (beginner here). – bernardo.g Jan 07 '18 at 11:19
  • 1
    You could add something like `if (this.isDrawerVisible(Gravity.START) && !this.isDrawerOpen(Gravity.START){// do something, for example, close drawer}` to the `onInterceptTouchEvent` method. – kalabalik Jan 07 '18 at 12:43
  • I see, it's visible but not open. It didn't work in `onInterceptTouchEvent`, but in a `SimpleDrawerListener` `onDrawerStateChanged` it did. Thanks again! – bernardo.g Jan 07 '18 at 16:03
0

Here I explain how you can user a TabLayout to move in all for direction this can help.

And for your case you can user a gesture detector

gestureDetectorCompat = new GestureDetectorCompat(this, new GestureDetector.SimpleOnGestureListener() {
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        float angle = (float) Math.toDegrees(Math.atan2(e1.getY() - e2.getY(), e2.getX() - e1.getX()));

        if (angle > -45 && angle <= 45) {
            if(goRight != 5) mainContainer.setCurrentItem(goRight);
            return true;
        }

        if (angle >= 135 && angle < 180 || angle < -135 && angle > -180) {
            if(goLeft != 5) mainContainer.setCurrentItem(goLeft);
            return true;
        }

        if (angle < -45 && angle >= -135) {
            if(goUp != 5)mainContainer.setCurrentItem(goUp);
            return true;
        }

        if (angle > 45 && angle <= 135) {
            if(goDown != 5)mainContainer.setCurrentItem(goDown);
            return true;
        }

        return false;
    }


});

And this

someView.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            gestureDetectorCompat.onTouchEvent(motionEvent);
            return false;
        }
    });

The important step is set your "someView" in your .xml as the area where you want get the movement. You don't need to watch the whole screen

Hope it helps

Canato
  • 3,598
  • 5
  • 33
  • 57