NavigationDrawer 和 ViewPager - 滑动问题
NavigationDrawer and ViewPager - swiping issue
我有一个 ViewPager
,里面有 3 个片段。出于我的应用程序的特定原因,我扩展了 ViewPager
并禁用了它的滑动功能。因此,为了在这些片段之间导航,我添加了一个 TabLayout
到我的 activity.
在我的第二个片段("in the middle")中,我在左边缘有一个 DrawerLayout
。
每次我在该片段中从左向右滑动时,都会显示 DawerLayout
, 即使我在屏幕中间滑动也是如此。只有当我靠近屏幕的左边缘滑动时才会发生这种情况。
我一直很难理解 Android 中的触摸和运动事件处理...有没有办法保持我的 ViewPager
(禁用滑动)并限制触摸事件 DrawerLayout
'listens to' 到屏幕边缘(应该如此)?
如果我不修改ViewPager
(即如果我保持滑动启用),它也不起作用,因为从左边缘滑动将开始打开DrawerLayout
,但是同时,将开始 ViewPager
的过渡。
我在一个小应用程序中重现了这个问题。
这是 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;
}
}
这是 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' 布局:
<?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>
片段 1:
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);
}
}
片段 2:
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);
}
}
片段 3:
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 的布局(带 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>
以及其他片段的布局:
片段一
<?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>
片段三
<?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>
虽然我认为水平 ViewPager
和水平 DrawerLayout
不可能相互重叠,但您可以将其中一个旋转 90 度并使其成为垂直滑动视图(在 DrawerLayout
的情况下很容易,如果你使用库 - github 上有一个数字,在 ViewPager
的情况下也很容易)。
关于您问题的核心问题:可以将 DrawerLayout
的敏感区域限制在它滑动的边缘。为此,您需要覆盖 DrawerLayout
并使用它代替 /layout/fragment2.xml
.
中的 DrawerLayout
这是代码:
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;
}
}
两个注意事项:
抽屉打开后,您可能希望它在 任何 交互后关闭,即使它位于 "sensitive area" 之外。这就是 onInterceptTouchEvent
中第一个 if 子句的用途。
虽然抽屉没有完全打开,吞噬MotionEvent
s在DrawerLayout
外面[=46] =].因此 "fling" 手势的 space 是窄的。为了使打开抽屉变得容易,以编程方式打开它是我在这里建议的选项。如果您的应用程序不依赖一半 opened/closed 个抽屉,就可用性而言,这似乎是一个很好的解决方案。
Here 我解释了如何使用 TabLayout 向所有方向移动这会有所帮助。
对于您的情况,您可以使用手势检测器
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;
}
});
还有这个
someView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
gestureDetectorCompat.onTouchEvent(motionEvent);
return false;
}
});
重要的一步是将 .xml 中的“someView”设置为您希望移动的区域。不用看全屏
希望对您有所帮助
我有一个 ViewPager
,里面有 3 个片段。出于我的应用程序的特定原因,我扩展了 ViewPager
并禁用了它的滑动功能。因此,为了在这些片段之间导航,我添加了一个 TabLayout
到我的 activity.
在我的第二个片段("in the middle")中,我在左边缘有一个 DrawerLayout
。
每次我在该片段中从左向右滑动时,都会显示 DawerLayout
, 即使我在屏幕中间滑动也是如此。只有当我靠近屏幕的左边缘滑动时才会发生这种情况。
我一直很难理解 Android 中的触摸和运动事件处理...有没有办法保持我的 ViewPager
(禁用滑动)并限制触摸事件 DrawerLayout
'listens to' 到屏幕边缘(应该如此)?
如果我不修改ViewPager
(即如果我保持滑动启用),它也不起作用,因为从左边缘滑动将开始打开DrawerLayout
,但是同时,将开始 ViewPager
的过渡。
我在一个小应用程序中重现了这个问题。
这是 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;
}
}
这是 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' 布局:
<?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>
片段 1:
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);
}
}
片段 2:
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);
}
}
片段 3:
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 的布局(带 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>
以及其他片段的布局:
片段一
<?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>
片段三
<?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>
虽然我认为水平 ViewPager
和水平 DrawerLayout
不可能相互重叠,但您可以将其中一个旋转 90 度并使其成为垂直滑动视图(在 DrawerLayout
的情况下很容易,如果你使用库 - github 上有一个数字,在 ViewPager
的情况下也很容易)。
关于您问题的核心问题:可以将 DrawerLayout
的敏感区域限制在它滑动的边缘。为此,您需要覆盖 DrawerLayout
并使用它代替 /layout/fragment2.xml
.
DrawerLayout
这是代码:
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;
}
}
两个注意事项:
抽屉打开后,您可能希望它在 任何 交互后关闭,即使它位于 "sensitive area" 之外。这就是
onInterceptTouchEvent
中第一个 if 子句的用途。虽然抽屉没有完全打开,吞噬
MotionEvent
s在DrawerLayout
外面[=46] =].因此 "fling" 手势的 space 是窄的。为了使打开抽屉变得容易,以编程方式打开它是我在这里建议的选项。如果您的应用程序不依赖一半 opened/closed 个抽屉,就可用性而言,这似乎是一个很好的解决方案。
Here 我解释了如何使用 TabLayout 向所有方向移动这会有所帮助。
对于您的情况,您可以使用手势检测器
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;
}
});
还有这个
someView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
gestureDetectorCompat.onTouchEvent(motionEvent);
return false;
}
});
重要的一步是将 .xml 中的“someView”设置为您希望移动的区域。不用看全屏
希望对您有所帮助