带有 AppCompat BottomSheets 的 NullPointerException

NullPointerException with AppCompat BottomSheets

LinearLayout bottomSheetViewgroup = (LinearLayout) findViewById(R.id.bottomSheet);

bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetViewgroup);

bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); //this line

我的 activity 的 onCreate() 方法中有这段代码,在执行最后一行时出现以下 NPE 异常:

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object java.lang.ref.WeakReference.get()' on a null object reference at android.support.design.widget.BottomSheetBehavior.setState(BottomSheetBehavior.java:440)

我找到了解决办法,但我仍然不知道为什么会这样。解决方案是将这最后一行放在 activity 是 运行 之后直接供用户调用。例如:在 contextMenu 回调或任何 OnClickListener 中。

虽然 Sanf0rds 的回答是正确的,但它不允许将 BottomSheet 定义为默认展开的能力。该问题是由于 WeakReference 直到 onLayoutChild 的最后一行才被设置。

解决方案是提供我们自己的 class,它扩展 BottomSheetBehavior,但在重写的 onLayoutChild 中设置状态。代码如下。

uk/ac/qub/quibe/misc/ExpandedBottomSheetBehavior.java

package uk.ac.qub.quibe.misc;

import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by mcp on 15/03/16.
 */
public class ExpandedBottomSheetBehavior<V extends View> extends android.support.design.widget.BottomSheetBehavior<V> {

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

    @Override
    public boolean onLayoutChild(final CoordinatorLayout parent, final V child, final int layoutDirection) {
        SavedState dummySavedState = new SavedState(super.onSaveInstanceState(parent, child), STATE_EXPANDED);
        super.onRestoreInstanceState(parent, child, dummySavedState);
        return super.onLayoutChild(parent, child, layoutDirection);
        /*
            Unfortunately its not good enough to just call setState(STATE_EXPANDED); after super.onLayoutChild
            The reason is that an animation plays after calling setState. This can cause some graphical issues with other layouts
            Instead we need to use setInternalState, however this is a private method.
            The trick is to utilise onRestoreInstance to call setInternalState immediately and indirectly
         */
    }

}

在布局文件引用中引用您的新自定义行为。

改变

app:layout_behavior="android.support.design.widget.BottomSheetBehavior"

app:layout_behavior="uk.ac.qub.quibe.misc.ExpandedBottomSheetBehavior"

您的代码存在问题,您正试图在 onCreate 中直接调用 setState 方法。这将抛出一个 nullPointer,因为 WeakReference 尚未初始化。当 Coordinator 布局即将放置其子视图时,它将被初始化。

onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection)

Called when the parent CoordinatorLayout is about the lay out the given child view.

所以最好的方法是在 onItemClick 侦听器中将窥视高度设置为 0 和 show/hide。

我在这里回答了这个问题:

也可以考虑监听全局布局事件,这样设置收起状态时就可以确定bottomsheet已经布局好了。

final View bottomSheet = findViewById(R.id.bottom_sheet);
bottomSheet.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            bottomSheet.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
            bottomSheetBehavior.setPeekHeight(300);
            bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
        }
    });
public class ExpandedBottomSheetBehavior<V extends View> extends
    android.support.design.widget.BottomSheetBehavior<V> {

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

  @Override
  public boolean onLayoutChild(final CoordinatorLayout parent, final V child, final int layoutDirection) {
    return super.onLayoutChild(parent, child, layoutDirection);
  }

  @Override
  public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
    try {
      return super.onInterceptTouchEvent(parent, child, event);
    } catch (NullPointerException ignored) {
      return false;
    }
  }
}