安卓BottomSheet如何在点击外部时折叠?

45

我已经使用NestedScrollView实现了底部抽屉式的行为。想知道是否可以在触摸抽屉式视图之外时隐藏它。


2
这是一个自问自答的问题吗?因为您同时添加了问题和答案,所以我想问一下。 - Pankaj Kumar
6
是的,这样其他人如果遇到同样的问题就不会感到沮丧了。 - Pradeep Kumar Kushwaha
有关 BottomSheetDialogFragment 请参见 https://dev59.com/oFkR5IYBdhLWcg3w7xdc。 - CoolMind
10个回答

60

最终我成功了,

使用了以下代码:

@Override public boolean dispatchTouchEvent(MotionEvent event){
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        if (mBottomSheetBehavior.getState()==BottomSheetBehavior.STATE_EXPANDED) {

            Rect outRect = new Rect();
            bottomSheet.getGlobalVisibleRect(outRect);

            if(!outRect.contains((int)event.getRawX(), (int)event.getRawY()))
                mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
        }
    }

    return super.dispatchTouchEvent(event);
}

希望它能挽救某人整个的一天!


1
运行得非常好! - galvan
9
在 Fragment 中怎么样? - Rajesh N
1
@RajeshNasit 在你的活动中实现DispatchTouchEvent方法,并使用FragmentManager调用你的Fragment中的方法。 - Luca Ziegler
我已经完成了这个,但问题是每个视图的滑动点击都会调用触摸事件。我只想为特定的视图滑动启用它。 - Rajesh N
你是如何初始化 mBottomSheetBehavior 的? - Fady Emad
1
它能工作,但这是一个好的解决方案吗?因为每次我们触摸或滚动屏幕时,该方法dispatchTouchEvent会被调用多次,这可能对电池使用和应用程序性能不利。 - ankalagba

37

感谢问题/答案的提供者。我使用了他的代码并在其基础上进行了改进,并希望分享。与其扩展View并添加接口,不如直接在BottomSheetBehavior中编写。像这样:

AutoCloseBottomSheetBehavior.java

import android.content.Context;
import android.graphics.Rect;
import android.support.design.widget.BottomSheetBehavior;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class AutoCloseBottomSheetBehavior<V extends View> extends BottomSheetBehavior<V> {

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

    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN &&
            getState() == BottomSheetBehavior.STATE_EXPANDED) {

            Rect outRect = new Rect();
            child.getGlobalVisibleRect(outRect);

            if (!outRect.contains((int) event.getRawX(), (int) event.getRawY())) {
                setState(BottomSheetBehavior.STATE_COLLAPSED);
            }
        }
        return super.onInterceptTouchEvent(parent, child, event);
    }
}

然后您只需将其添加到您的XML布局中:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    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">

   ... your normal content here ...
   <SomeLayout... />

    ... the bottom sheet with the behavior
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_behavior="<com.package.name.of.the.class>.AutoCloseBottomSheetBehavior">

        ... the bottom sheet views

    </LinearLayout>

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

7
这很好用!小建议:在调用“setState”后添加“return true”可以防止点击事件穿透。在我的使用情况中,如果单击对话框外部,我只想让它折叠而不传递点击事件到它后面的视图 :) - Psest328
1
@Psest328 加上 return true 会导致这个功能有时候失效。如果我在代码中加入了 return true,那么这个表格只有偶尔才能关闭。 - Emil S.
对我来说运行得很好。这应该是被接受的答案。 - Milan Zelenka

15

关于Activity:

@Override 
public boolean dispatchTouchEvent(MotionEvent event){
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        if (mBottomSheetBehavior.getState()==BottomSheetBehavior.STATE_EXPANDED) {

            Rect outRect = new Rect();
            bottomSheet.getGlobalVisibleRect(outRect);

            if(!outRect.contains((int)event.getRawX(), (int)event.getRawY()))
                mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
        }
    }

    return super.dispatchTouchEvent(event);
}

对于片段:使用与活动相同的方法,例如:

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        if (fragment != null && fragment instanceof HomeFragment) {
            ((HomeFragment) fragment).hideBottomSheetFromOutSide(event);
        }
    }
    return super.dispatchTouchEvent(event);
}

在Fragment中创建方法的示例:

   /**
     * Calling from Dashboard Activity
     *
     * @param event Motion Event
     */
    public void hideBottomSheetFromOutSide(MotionEvent event) {
        if (isBottomSheetMenuExpanded()) {
            Rect outRect = new Rect();
            mBinding.homeBottomSheetLayout.getGlobalVisibleRect(outRect);
            if (!outRect.contains((int) event.getRawX(), (int) event.getRawY()))
                mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
        }
    }

希望它对您有所帮助。

谢谢。


你好,这个能和 BottomSheetDialogFragment 一起使用吗? - famfamfam
我还没有检查过。你可以试一下。 - Pratik Butani
已检查,不起作用,断点未触发。 - famfamfam
它能工作,但这是一个好的解决方案吗?因为每次我们触摸或滚动屏幕时,该方法dispatchTouchEvent被调用多次,这可能对电池使用和应用程序性能不利。 - ankalagba
没错,如果你发现其他好的解决方案,请告诉我。 - Pratik Butani

2

为您的主要布局(在此情况下为Coordinate布局)设置一个点击监听器。

@OnClick(R.id.coordinateLayout)
public void onClickView(View view) {
    if (sheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
        sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
    }
}

注意:Butterknife 用于点击事件,否则请在活动的 onCreate 中使用以下代码。
CoordinateLayout layout = (CoordinateLayout) findViewById(R.id. coordinateLayout);
layout.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (sheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
            sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
        }
    }
});

2
您可以调用以下代码,在点击外部时关闭底部弹出对话框。
BottomSheetDialog dialog = new BottomSheetDialog(context);
dialog.setContentView(R.layout.bottom_sheet);
dialog.setCanceledOnTouchOutside(true);
dialog.show(); 

1
someViewToClickOn.setOnClickListener(v -> 
    behavior.setState(BottomSheetBehavior.STATE_HIDDEN));

这也可以!我最初使用了 BottomSheetBehavior.STATE_COLLAPSED,但它无法正常工作。

0

有很多人在寻找一种在片段上实现dispatchTouchEvent的方法。以下是他们可以这样做的方法:

按照以下定义创建自定义布局:

public class DispatchTouchEvent extends LinearLayout {

    public interface onDispatchEvent
    {
        void dispatchEvent(MotionEvent e);
    }

    private onDispatchEvent dispatchEvent;

    public DispatchTouchEvent(Context context) {
        super(context);
    }

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

    public DispatchTouchEvent(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setDispatchEvent(onDispatchEvent dispatchEvent)
    {
        this.dispatchEvent=dispatchEvent;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if(dispatchEvent!=null)
        {
            dispatchEvent.dispatchEvent(ev);
        }
        return super.dispatchTouchEvent(ev);
    }

}

现在将此布局用作片段布局的基础。在片段内,将此布局初始化为:

public class ABC extends fragment implements DispatchTouchEvent.onDispatchEvent
{

DispatchTouchEvent dispatchTouchEvent;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
....
    dispatchTouchEvent = (DispatchTouchEvent)rootView.findViewById(R.id.dispatch_event);
    dispatchTouchEvent.setDispatchEvent(this);
....
}

@Override
public void dispatchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
    if (mBottomSheetBehavior.getState()==BottomSheetBehavior.STATE_EXPANDED) 
    {

        Rect outRect = new Rect();
        bottomSheet.getGlobalVisibleRect(outRect);

        if(!outRect.contains((int)event.getRawX(), (int)event.getRawY()))   
         mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
    }
    }

    }

}

0

对我来说,这只是一个简单的setCancelable(true);

@Override
public void setupDialog(Dialog dialog, int style) {
    super.setupDialog(dialog, style);

    View contentView = View.inflate(getContext(), R.layout.layout_additional_prices, null);
    unbinder = ButterKnife.bind(this, contentView);
    dialog.setContentView(contentView);
    dialog.setOnKeyListener(new BottomSheetBackDismissListener());
    //makeBottomSheetFullScreen(getActivity(), mBottomSheetBehaviorCallback, contentView);
    setCancelable(true);
}

0

有两种方法可以使对话框具有可取消性:

设置此对话框是否可使用 BACK 键取消。

Java

dialog.setCancelable(true);

Kotlin

dialog.setCancelable(true)

设置当触摸窗口边界时,此对话框是否被取消。

Java

dialog.setCanceledOnTouchOutside(true);

科特林

dialog.setCanceledOnTouchOutside(true)

0

我发现使用OP的答案或AutoCloseBottomSheetBehavior.java版本(来自Budius)存在UX问题。我以一种更清晰的方式更改了AutoCloseBottomSheetBehavior代码,不会引起任何UX问题(代码使用Kotlin编写):

class AutoCloseBottomSheetBehavior<V : View>(
        context: Context,
        attrs: AttributeSet
) : BottomSheetBehavior<V>(context, attrs) {

    private val gestureDetector: GestureDetectorCompat =
            GestureDetectorCompat(context, object : GestureDetector.SimpleOnGestureListener() {

                override fun onDown(event: MotionEvent?): Boolean {
                    return true
                }

                override fun onSingleTapUp(e: MotionEvent?): Boolean =
                        when {
                            state == STATE_EXPANDED -> {
                                state = STATE_COLLAPSED
                                true
                            }
                            state == STATE_COLLAPSED && isHideable -> {
                                state = STATE_HIDDEN
                                true
                            }
                            else -> false
                        }
            })

    @SuppressLint("ClickableViewAccessibility")
    override fun onLayoutChild(parent: CoordinatorLayout, child: V, layoutDirection: Int): Boolean {
        parent.setOnTouchListener { _, event ->
            gestureDetector.onTouchEvent(event)
        }
        return super.onLayoutChild(parent, child, layoutDirection)
    }
}

每当用户在父CoordinatorLayout上执行单击操作时,此操作将折叠/隐藏底部工作表,并处理底部工作表可隐藏的情况(如果它处于折叠状态,则我们希望隐藏它)。


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