Android Bottom sheet 是什么?
请看下面几个链接:
https://www.google.com/design/spec/components/bottom-sheets.html
http://android-developers.blogspot.com/2016/02/android-support-library-232.html
简单说,就是一个从底部弹出的页面,随着你手指向上滑动而动,滑动到最低端则会消失。
其简单使用可以参考这个链接。
但是,最近在使用 BottomSheetDialog 时碰到了坑,在这里记录一下,跟大家分享。
注:这里我使用的的是 compile ‘com.android.support:design:23.2.1’ 包中的 BottomSheetDialog
下面进入正题
在用户角度来说,使BottomSheetDialog 消失有三种方法
- 点击阴影区域。
- 点击物理返回键。
- 在BottomSheetDialog 区域中向下滑动。
最后一种是 BottomSheetDialog 特有的,当实例化一个BottomSheetDialog后,使用第一、二种方法使其消失后,在调用 show() 方法后一切正常,但是当使用第三种方法使其消失然后在调用 show() 后,只见屏幕出现阴影,但是之前的View却未出现。
解决方法
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.annotation.StyleRes;
import android.support.design.widget.BottomSheetBehavior;
import android.support.design.widget.CoordinatorLayout;
import android.support.v7.app.AppCompatDialog;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.FrameLayout;
import java.lang.reflect.Field;
/**
* Base class for {@link android.app.Dialog}s styled as a bottom sheet.
*/
public class MyDialog extends AppCompatDialog {
private BottomSheetBehavior bottomSheetBehavior;
public MyDialog(@NonNull Context context) {
this(context, 0);
}
public MyDialog(@NonNull Context context, @StyleRes int theme) {
super(context, getThemeResId(context, theme));
// We hide the title bar for any style configuration. Otherwise, there will be a gap
// above the bottom sheet when it is expanded.
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
}
protected MyDialog(@NonNull Context context, boolean cancelable,
OnCancelListener cancelListener) {
super(context, cancelable, cancelListener);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
}
@Override
public void setContentView(@LayoutRes int layoutResId) {
super.setContentView(wrapInBottomSheet(layoutResId, null, null));
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setLayout(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
}
@Override
public void setContentView(View view) {
super.setContentView(wrapInBottomSheet(0, view, null));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
super.setContentView(wrapInBottomSheet(0, view, params));
}
private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) {
final CoordinatorLayout coordinator = (CoordinatorLayout) View.inflate(getContext(),
android.support.design.R.layout.design_bottom_sheet_dialog, null);
if (layoutResId != 0 && view == null) {
view = getLayoutInflater().inflate(layoutResId, coordinator, false);
}
FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(android.support.design.R.id.design_bottom_sheet);
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
bottomSheetBehavior.setBottomSheetCallback(mBottomSheetCallback);
if (params == null) {
bottomSheet.addView(view);
} else {
bottomSheet.addView(view, params);
}
// We treat the CoordinatorLayout as outside the dialog though it is technically inside
if (shouldWindowCloseOnTouchOutside()) {
coordinator.findViewById(android.support.design.R.id.touch_outside).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (isShowing()) {
cancel();
}
}
});
}
return coordinator;
}
private boolean shouldWindowCloseOnTouchOutside() {
if (Build.VERSION.SDK_INT < 11) {
return true;
}
TypedValue value = new TypedValue();
//noinspection SimplifiableIfStatement
if (getContext().getTheme()
.resolveAttribute(android.R.attr.windowCloseOnTouchOutside, value, true)) {
return value.data != 0;
}
return false;
}
private static int getThemeResId(Context context, int themeId) {
if (themeId == 0) {
// If the provided theme is 0, then retrieve the dialogTheme from our theme
TypedValue outValue = new TypedValue();
if (context.getTheme().resolveAttribute(
android.support.design.R.attr.bottomSheetDialogTheme, outValue, true)) {
themeId = outValue.resourceId;
} else {
// bottomSheetDialogTheme is not provided; we default to our light theme
themeId = android.support.design.R.style.Theme_Design_Light_BottomSheetDialog;
}
}
return themeId;
}
private BottomSheetBehavior.BottomSheetCallback mBottomSheetCallback
= new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet,
@BottomSheetBehavior.State int newState) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
dismiss();
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
};
@Override
protected void onStart() {
super.onStart();
// 会有异常
// if (bottomSheetBehavior != null)
// bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
//暂时解决方法
try {
Field field = BottomSheetBehavior.class.getDeclaredField("mState");
field.setAccessible(true);
field.setInt(bottomSheetBehavior, BottomSheetBehavior.STATE_EXPANDED);
} catch (NoSuchFieldException e) {
Log.e("BottomSheetBehavior", "Error setting BottomSheetBehavior initial state (1)", e);
} catch (IllegalAccessException e) {
Log.e("BottomSheetBehavior", "Error setting BottomSheetBehavior initial state (2)", e);
}
}
}
将以上代码作为 BottomSheetDialog 使用即可。
原因
如果看过BottomSheetDialog 源码,可以看出,上面的MyDialog 与 BottomSheetDialog 的源码大致相同的,主要的修改是 onStart() 方法,在该方法中,我将 MyDialog的状态设为 STATE_EXPANDED状态,即展开的状态。为什么要这么做呢?是这样的,当我们使用第三种方法使其消失时,将BottomsheetDialog 拖到不可见,这时 BottomsheetDialog 的状态是Hiden,当我们调用show()时,BottomsheetDialog依然处于 Hiden状态,所以才会出现只有阴影,没有View内容的状态。而用第一、二种方法使其消失时,BottomsheetDialog 的状态并没有改变,所以再次调用 show() 的时候没有问题。读者不妨可以打印Log看看。
所以,我们只要在onStart()方法中将其状态设为 STATE_EXPANDED 即可,但是,注意看代码,如果我们使用
if (bottomSheetBehavior != null)
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
理论上是不应该会出错的,但是在这个版本中这样使用的话就会抛出一个 软引用空指针 的异常,这是一个SDK的Bug,详情看这,在这里使用了 反射 的方法来修改状态值。可能在下一版本中这个Bug就被修改了,但使用这个版本的时候就姑且这么用吧。
注:以上内容如有错误,望不吝指出!
转载请注明出处:http://blog.csdn.net/big_heart_c/article/