在上一節之后,我們可以開始構建視圖了。
我們可以制作一個構建器,這個構建器主要用來干嘛呢?通過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
新聞熱點
疑難解答