android BottomSheet 在外部点击时如何折叠?
android BottomSheet how to collapse when clicked outside?
我已经使用 NestedScrollView 实现了 bottomsheet 行为。并且想知道在外部触摸时是否可以隐藏底部视图。
我终于做到了,
使用了以下代码行:
@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);
}
希望它能拯救某人一整天!
有很多人找到了在片段上实现 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);
}
}
}
}
感谢 OP question/answer。我使用了他的代码,但改进了它的清洁度并想分享。您可以直接在 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>
为主布局(在本例中为坐标布局)设置点击监听器
@OnClick(R.id.coordinateLayout)
public void onClickView(View view) {
if (sheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
注意:Butterknife 用于点击,否则请在 activity.
的 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);
}
}
});
someViewToClickOn.setOnClickListener(v ->
behavior.setState(BottomSheetBehavior.STATE_HIDDEN));
这也行!我第一次使用 BottomSheetBehavior.STATE_COLLAPSED
不起作用
对于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);
}
对于片段:在Activity中使用相同的方法,如
@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);
}
}
希望对您有所帮助。
谢谢。
您可以调用以下代码在点击外部时关闭底部 sheet 对话框。
BottomSheetDialog dialog = new BottomSheetDialog(context);
dialog.setContentView(R.layout.bottom_sheet);
dialog.setCanceledOnTouchOutside(true);
dialog.show();
我发现使用 OP 的答案或 AutoCloseBottomSheetBehavior.java 版本(来自 Budius)时存在用户体验问题。我更改了 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)
}
}
这个 collapses/hides 底部 sheet 每当用户在父 CoordinatorLayout 上执行一次点击并且还处理底部 sheet 可隐藏的情况(我们想要如果它处于 COLLAPSED 状态,则隐藏它。
对我来说这是一个简单的 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);
}
有两种方法可以使对话框可取消:
设置是否可以使用 BACK 键取消此对话框。
Java
dialog.setCancelable(true);
科特林
dialog.setCancelable(true)
设置在 window 范围之外触摸时是否取消此对话框。
Java
dialog.setCanceledOnTouchOutside(true);
科特林
dialog.setCanceledOnTouchOutside(true)
我已经使用 NestedScrollView 实现了 bottomsheet 行为。并且想知道在外部触摸时是否可以隐藏底部视图。
我终于做到了,
使用了以下代码行:
@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);
}
希望它能拯救某人一整天!
有很多人找到了在片段上实现 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);
}
}
}
}
感谢 OP question/answer。我使用了他的代码,但改进了它的清洁度并想分享。您可以直接在 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>
为主布局(在本例中为坐标布局)设置点击监听器
@OnClick(R.id.coordinateLayout)
public void onClickView(View view) {
if (sheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
注意:Butterknife 用于点击,否则请在 activity.
的 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);
}
}
});
someViewToClickOn.setOnClickListener(v ->
behavior.setState(BottomSheetBehavior.STATE_HIDDEN));
这也行!我第一次使用 BottomSheetBehavior.STATE_COLLAPSED
不起作用
对于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);
}
对于片段:在Activity中使用相同的方法,如
@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);
}
}
希望对您有所帮助。
谢谢。
您可以调用以下代码在点击外部时关闭底部 sheet 对话框。
BottomSheetDialog dialog = new BottomSheetDialog(context);
dialog.setContentView(R.layout.bottom_sheet);
dialog.setCanceledOnTouchOutside(true);
dialog.show();
我发现使用 OP 的答案或 AutoCloseBottomSheetBehavior.java 版本(来自 Budius)时存在用户体验问题。我更改了 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)
}
}
这个 collapses/hides 底部 sheet 每当用户在父 CoordinatorLayout 上执行一次点击并且还处理底部 sheet 可隐藏的情况(我们想要如果它处于 COLLAPSED 状态,则隐藏它。
对我来说这是一个简单的 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);
}
有两种方法可以使对话框可取消:
设置是否可以使用 BACK 键取消此对话框。
Java
dialog.setCancelable(true);
科特林
dialog.setCancelable(true)
设置在 window 范围之外触摸时是否取消此对话框。
Java
dialog.setCanceledOnTouchOutside(true);
科特林
dialog.setCanceledOnTouchOutside(true)