一个 Fragment 覆盖在另一个 Fragment 上的问题

309

当我在一个全屏使用 #77000000 背景的 fragment 上显示另一个 fragment(我们称其为主要 fragment)时,我的主要 fragment 仍然会对点击做出反应(即使我们看不到它,也可以点击按钮)。

问题:如何防止第一个(主要的)fragment 上的点击事件?

编辑:

不幸的是,我不能只隐藏主要 fragment,因为我在第二个 fragment 上使用了透明背景(因此,用户可以看到其后面的内容)。


根据您提供的内容,当您不使用时,应尝试将main FragmentVisibility设置为GONE - adneal
1
我猜测你在实现 onClicked 方法时,当被点击时返回了“false”。 - DeeV
@DeeV,onClick方法不返回任何内容。但是你提供了一个想法,谢谢(我很快会发布答案)。 - Dmitry Zaytsev
1
唉,你说得对。 onTouch 返回它。我只希望我能理解为什么触摸事件会穿过一个片段。如果您没有发出触摸事件,它不应该这样做。 - DeeV
@DeeV,看起来如果你的视图(例如在其他视图之上)没有捕获到onTouch事件,那么系统会继续搜索具有相同坐标的其他视图。 - Dmitry Zaytsev
为什么会发生这种情况?只要你有多个堆叠在一起的片段,就会发生这种情况吗? - portfoliobuilder
12个回答

647
将第二个片段的视图的clickable属性设置为true。该视图会捕获事件,以便不会传递给主片段。因此,如果第二个片段的视图是布局,则应使用以下代码:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:clickable="true" />

4
这对我起了作用。看起来比@Dmitry Zaitsev上面提供的解决方案更容易。 这样做有什么不好的理由吗?我似乎想不出任何,但我只是想确认一下。 - ariets
3
这对我没有用。我的片段中有一个RelativeLayout,并且我使用clickable属性设置了整个视图。@Dmitry的解决方案解决了我的问题。 - 4gus71n
3
这对我很有用。在Android中,“clickable”显然有点像iOS的“userInteractionEnabled”。 - mvds
34
为什么安卓让我们面临艰难的编码条件?! - Lo-Tan
1
我在使用ViewPager时遇到了同样的问题。当我在第一页滚动时,它也会跳转到第二页,而且这个解决方案对我没有用。有什么想法吗? - Gokhan Arik
显示剩余8条评论

76

解决方案非常简单。在我们的第二个片段(与主要片段重叠)中,我们只需要捕获 onTouch 事件:

@Override
public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstance){
    View root = somehowCreateView();

    /*here is an implementation*/

    root.setOnTouchListener(new View.OnTouchListener() {
        public boolean onTouch(View v, MotionEvent event) {
            return true;
        }
    });
    return root;
}

你的解决方案有效,加一分!但是你能告诉我为什么我们需要显式地这样做吗? - Hunt
@Hunt 你不必这样做。那只是另一种方法(请参见被接受的答案)。 - Dmitry Zaytsev
在你的第二个片段的XML主布局中添加android:clickable="true"。 - Rishabh Srivastava
这个解决方案存在问题,当你打开辅助功能TalkBack模式时,它不会读取单独的元素,而是将焦点放在根视图上。 - Amit Garg
Kotlin 版本: root.setOnTouchListener { _, _ -> true} - Gal Rom

29

只需在父布局中添加 clickable="true"focusable="true"

 <android.support.constraint.ConstraintLayout
      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:clickable="true"
      android:focusable="true">

      <!--Your views-->

 </android.support.constraint.ConstraintLayout>

如果您正在使用 AndroidX,请尝试以下操作

 <androidx.constraintlayout.widget.ConstraintLayout
      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:clickable="true"
      android:focusable="true">

          <!--Your views-->

 </androidx.constraintlayout.widget.ConstraintLayout>

这与被接受的答案有何不同吗?focuseable并不是必需的。 - Dmitry Zaytsev
2
我认为这里的 focusable="true" 只是为了避免在Android Studio中出现警告。 - Artem M

13

如果两个Fragment放置在同一个容器视图中,当显示第二个Fragment时,应该隐藏第一个片段。

如果您想了解有关如何解决有关Fragment的问题的更多问题,请查看我的库:https://github.com/JustKiddingBaby/FragmentRigger

FirstFragment firstfragment;
SecondFragment secondFragment;
FragmentManager fm;
FragmentTransaction ft=fm.beginTransaction();
ft.hide(firstfragment);
ft.show(secondFragment);
ft.commit();

1
这应该是正确的答案!谢谢你。我已经在另一个项目中解决了这个问题。但我忘记了我是怎么解决它的,最终我得到了你的答案。谢谢。 - Chinese Cat
4
我认为这不是正确的解决方案。片段/活动在视图堆栈中工作。在从堆栈中弹出顶部片段后,您将不得不再次调用.show,这意味着必须通知底部片段顶部片段已经消失。这只是额外增加了维护逻辑。 - X.Y.

7

您需要在 android:clickable="true" 后添加 android:focusable="true"

Clickable 表示它可以被指针设备点击或触摸设备轻敲。

Focusable 表示它可以从输入设备(如键盘)获得焦点。输入设备(如键盘)无法根据输入本身决定将其输入事件发送到哪个视图,因此它们将其发送到具有焦点的视图。


2
并且需要添加 focusable="true" 以避免在新的 Android Studio 中出现警告。 - Hossam Hassan

4

方法1:

您可以将其添加到所有片段布局中。

android:clickable="true"
android:focusable="true"
android:background="@color/windowBackground"

方法2:(编程方式)
FragmentBase扩展所有的片段等。然后将以下代码添加到FragmentBase中。
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    getView().setBackgroundColor(getResources().getColor(R.color.windowBackground));
    getView().setClickable(true);
    getView().setFocusable(true);
}

3

有一些解决方案是我们在这个讨论中共同贡献的,但我还想提到另一种解决方案。如果您不喜欢在XML中为每个布局的根ViewGroup设置clickable和focusable等于true,您也可以像下面这样将它放在您的基类中。

override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ) : View? {
        super.onCreateView(inflater, container, savedInstanceState)

        val rootView = inflater.inflate(layout, container, false).apply {
            isClickable = true
            isFocusable = true
        }

        return rootView
    }

你也可以使用内联变量,但出于个人原因,我不喜欢它。

希望这对那些讨厌布局XML的人有所帮助。


1
可接受的答案可以“工作”,但也会造成性能成本(过度绘制,在方向更改时重新测量),因为底部的片段仍在被绘制。也许你应该简单地通过标签或ID查找片段,并在需要再次显示时将其可见性设置为GONE或VISIBLE。

在Kotlin中:

fragmentManager.findFragmentByTag(BottomFragment.TAG).view.visibility = GONE

这个解决方案比使用 FragmentTransactionhide()show() 方法更可取,尤其是在你使用动画时。你只需要在 Transition.TransitionListeneronTransitionStart()onTransitionEnd() 中调用它即可。

0

我有多个具有相同XML的片段。
花费了几个小时后,我删除了setPageTransformer,然后它开始工作了。

   //  viewpager.setPageTransformer(false, new BackgPageTransformer())

我有扩展逻辑。

public class BackgPageTransformer extends BaseTransformer {

    private static final float MIN_SCALE = 0.75f;

    @Override
    protected void onTransform(View view, float position) {
        //view.setScaleX Y
    }

    @Override
    protected boolean isPagingEnabled() {
        return true;
    }
}

0

你可以通过使用上一个片段布局的父布局的onClick属性,给它一个空白点击,并在活动中创建一个函数doNothing(View view),并不写任何内容。这样就可以实现了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接