在上一节之后,我们可以开始构建视图了。
我们可以制作一个构建器,这个构建器主要用来干嘛呢?通过Menu文件来使用适配器等工具来构建整个BottomSheet的视图。因为我们的控件想要做到不事先在xml中定义,整体都是使用代码来动态生成,即插即用型,所以我们就有了这么一个构建器来动态生成BottomSheet这一视图。新建 BottomSheetAdapterBuilder.java
首先我们来看 createAdapterItems
函数,该函数的作用就是从Menu中读取item来得到items。对于MenuItem是可能有SubMenu的,所以我们也必须要对这个进行检测。但是对于grid类型的是不能有子菜单的,所以我们会抛出一个错误。对于有子菜单的item,我们把他升级为标题类型,然后在读取子菜单项。
最重要的是 createView
函数,作用当然是动态创建BottomSheet了。首先是从Menu中读取出items,然后根据类型来构建出一个view对象。接下来有个特殊操作,如果只有一个标题的话,将其固定。
根据类型的不同,也使用不同LayoutManager,这一点也不需要解释,但是在grid类型中,需要计算每一个item的宽度是多少的小计算,减去两边的margin,中间的一除即可。然后搭配上适配器即可。这样的话整个BottomSheet的视图就动态搭建完成了。
动态搭建完成后,我们还需要将这个部件放进我们的主要视图中,这里必须要说明一点,BottomSheet必须要是在CoordinatorLayout中才能使用,因为他的一些behavior折叠操作都是需要在CoordinatorLayout中使用。
这里采用了一个从底部弹起的方式,所以继承了 BottomSheetDialog
public class BottomSheetMenuDialog extends BottomSheetDialog implements BottomSheetItemClickListener { BottomSheetBehavior.BottomSheetCallback mCallback; BottomSheetBehavior mBehavior; private BottomSheetItemClickListener mClickListener; private AppBarLayout mAppBarLayout; private boolean mExpandOnStart; private boolean mDelayDismiss; boolean mRequestedExpand; boolean mClicked; boolean mRequestCancel; boolean mRequestDismiss; OnCancelListener mOnCancelListener; public BottomSheetMenuDialog(Context context) { super(context); } public BottomSheetMenuDialog(Context context, int theme) { super(context, theme); } /** * Dismiss the BottomSheetDialog while animating the sheet. */ public void dismissWithAnimation() { if (mBehavior != null) { mBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); } } @Override public void setOnCancelListener(OnCancelListener listener) { super.setOnCancelListener(listener); mOnCancelListener = listener; } @Override public void cancel() { mRequestCancel = true; super.cancel(); } @Override public void dismiss() { mRequestDismiss = true; if (mRequestCancel) { dismissWithAnimation(); } else { super.dismiss(); } } @Override protected void onStart() { super.onStart(); final FrameLayout sheet = (FrameLayout) findViewById(R.id.design_bottom_sheet); if (sheet != null) { mBehavior = BottomSheetBehavior.from(sheet); mBehavior.setBottomSheetCallback(mBottomSheetCallback); mBehavior.setSkipCollapsed(true); if (getContext().getResources().getBoolean(R.bool.tablet_landscape)) { CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) sheet.getLayoutParams(); layoutParams.width = getContext().getResources() .getDimensionPixelSize(R.dimen.bottomsheet_width); sheet.setLayoutParams(layoutParams); } // Make sure the sheet doesn't overlap the appbar if (mAppBarLayout != null) { if (mAppBarLayout.getHeight() == 0) { mAppBarLayout.getViewTreeObserver() .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { applyAppbarMargin(sheet); } }); } else { applyAppbarMargin(sheet); } } if (getContext().getResources().getBoolean(R.bool.landscape)) { fixLandscapePeekHeight(sheet); } if (mExpandOnStart) { sheet.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); if (mBehavior.getState() == BottomSheetBehavior.STATE_SETTLING && mRequestedExpand) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { sheet.getViewTreeObserver().removeOnGlobalLayoutListener(this); } else { //noinspection deprecation sheet.getViewTreeObserver().removeGlobalOnLayoutListener(this); } } mRequestedExpand = true; } }); } } } public void setAppBar(AppBarLayout appBar) { mAppBarLayout = appBar; } public void expandOnStart(boolean expand) { mExpandOnStart = expand; } public void delayDismiss(boolean dismiss) { mDelayDismiss = dismiss; } public void setBottomSheetCallback(BottomSheetBehavior.BottomSheetCallback callback) { mCallback = callback; } public void setBottomSheetItemClickListener(BottomSheetItemClickListener listener) { mClickListener = listener; } public BottomSheetBehavior getBehavior() { return mBehavior; } @Override public void onBottomSheetItemClick(MenuItem item) { if (!mClicked) { if (mBehavior != null) { if (mDelayDismiss) { BottomSheetBuilderUtils.delayDismiss(mBehavior); } else { mBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); } } if (mClickListener != null) { mClickListener.onBottomSheetItemClick(item); } mClicked = true; } } private BottomSheetBehavior.BottomSheetCallback mBottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() { @Override public void onStateChanged(@NonNull View bottomSheet, @BottomSheetBehavior.State int newState) { if (mCallback != null) { mCallback.onStateChanged(bottomSheet, newState); } //noinspection WrongConstant if (newState == BottomSheetBehavior.STATE_HIDDEN) { mBehavior.setBottomSheetCallback(null); try { BottomSheetMenuDialog.super.dismiss(); }catch (IllegalArgumentException e){ // Ignore exception handling } // User dragged the sheet. if (!mClicked && !mRequestDismiss && !mRequestCancel && mOnCancelListener != null) { mOnCancelListener.onCancel(BottomSheetMenuDialog.this); } } } @Override public void onSlide(@NonNull View bottomSheet, float slideOffset) { if (mCallback != null) { mCallback.onSlide(bottomSheet, slideOffset); } } }; private void fixLandscapePeekHeight(final View sheet) { // On landscape, we shouldn't use the 16:9 keyline alignment sheet.post(new Runnable() { @Override public void run() { mBehavior.setPeekHeight(sheet.getHeight() / 2); } }); } private void applyAppbarMargin(View sheet) { CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) sheet.getLayoutParams(); layoutParams.topMargin = mAppBarLayout.getHeight(); sheet.setLayoutParams(layoutParams); }}在onStart函数中的 R.id.design_bottom_sheet 是安卓自带的id来放置dialog。接下来的if判断,是否处于平板中,若是在平板中,会更改整个dialog的宽度。然后判断AppBar的高度,使BottomSheet不会盖过Appbar。还有横屏模式与自动开启的逻辑处理。
还有一些关于点击操作的处理,其中BottomSheetBuilderUtils是我们编写的一个辅助工具类,用来储存状态与延迟关闭,会有0.3秒的时间来延迟,是否开启这个功能由 mDelayDismiss 来决定。
BottomSheetBuilderUtils.javapublic class BottomSheetBuilderUtils { public static final String SAVED_STATE = "saved_behavior_state"; public static void delayDismiss(final BottomSheetBehavior behavior) { new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { behavior.setState(BottomSheetBehavior.STATE_HIDDEN); } }, 300); } public static void saveState(Bundle outState, BottomSheetBehavior behavior) { if (outState != null) { outState.putInt(SAVED_STATE, behavior.getState()); } } public static void restoreState(final Bundle savedInstanceState, final BottomSheetBehavior behavior) { if (savedInstanceState != null) { Handler handler = new Handler(Looper.getMainLooper()); handler.postDelayed(new Runnable() { @Override public void run() { int state = savedInstanceState.getInt(SAVED_STATE); if (state == BottomSheetBehavior.STATE_EXPANDED && behavior != null) { behavior.setState(state); } } }, 300); } }}主要的处理就是构建一个view来放入CoordinatorLayout的view中或dialog中,而构建view之前我们也已经写好。核心函数就是
public View createView() { if (mMenu == null && mAdapterBuilder.getItems().isEmpty()) { throw new IllegalStateException("You need to provide at least one Menu " + "or an item with addItem"); } if (mCoordinatorLayout == null) { throw new IllegalStateException("You need to provide a coordinatorLayout" + "so the view can be placed on it"); } View sheet = mAdapterBuilder.createView(mTitleTextColor, mBackgroundDrawable, mBackgroundColor, mDividerBackground, mItemTextColor, mItemBackground, mIconTintColor, mItemClickListener); ViewCompat.setElevation(sheet, mContext.getResources() .getDimensionPixelSize(R.dimen.bottomsheet_elevation)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { sheet.findViewById(R.id.fakeShadow).setVisibility(View.GONE); } CoordinatorLayout.LayoutParams layoutParams = new CoordinatorLayout.LayoutParams(CoordinatorLayout.LayoutParams.MATCH_PARENT, CoordinatorLayout.LayoutParams.WRAP_CONTENT); layoutParams.gravity = Gravity.CENTER_HORIZONTAL; layoutParams.setBehavior(new BottomSheetBehavior()); if (mContext.getResources().getBoolean(R.bool.tablet_landscape)) { layoutParams.width = mContext.getResources() .getDimensionPixelSize(R.dimen.bottomsheet_width); } mCoordinatorLayout.addView(sheet, layoutParams); mCoordinatorLayout.postInvalidate(); return sheet; } public BottomSheetMenuDialog createDialog() { if (mMenu == null && mAdapterBuilder.getItems().isEmpty()) { throw new IllegalStateException("You need to provide at least one Menu " + "or an item with addItem"); } BottomSheetMenuDialog dialog = mTheme == 0 ? new BottomSheetMenuDialog(mContext, R.style.BottomSheetBuilder_DialogStyle) : new BottomSheetMenuDialog(mContext, mTheme); if (mTheme != 0) { setupThemeColors(mContext.obtainStyledAttributes(mTheme, new int[]{ R.attr.bottomSheetBuilderBackgroundColor, R.attr.bottomSheetBuilderItemTextColor, R.attr.bottomSheetBuilderTitleTextColor})); } else { setupThemeColors(mContext.getTheme().obtainStyledAttributes(new int[]{ R.attr.bottomSheetBuilderBackgroundColor, R.attr.bottomSheetBuilderItemTextColor, R.attr.bottomSheetBuilderTitleTextColor,})); } View sheet = mAdapterBuilder.createView(mTitleTextColor, mBackgroundDrawable, mBackgroundColor, mDividerBackground, mItemTextColor, mItemBackground, mIconTintColor, dialog); sheet.findViewById(R.id.fakeShadow).setVisibility(View.GONE); dialog.setAppBar(mAppBarLayout); dialog.expandOnStart(mExpandOnStart); dialog.delayDismiss(mDelayedDismiss); dialog.setBottomSheetItemClickListener(mItemClickListener); if (mContext.getResources().getBoolean(R.bool.tablet_landscape)) { FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(mContext.getResources() .getDimensionPixelSize(R.dimen.bottomsheet_width), ViewGroup.LayoutParams.WRAP_CONTENT); dialog.setContentView(sheet, layoutParams); } else { dialog.setContentView(sheet); } return dialog; }这样的话,我们整个BottomSheet就构建完成了。
然后设置点击事件调用函数即可。
本文github地址参考:
https://github.com/rubensousa/BottomSheetBuilder
新闻热点
疑难解答