BottomSheetDialog.java revision 657ea1100fee4750f148f9d0dcb7e7e2028f105e
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.design.widget; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.os.Build; 22import android.os.Bundle; 23import android.support.annotation.LayoutRes; 24import android.support.annotation.NonNull; 25import android.support.annotation.StyleRes; 26import android.support.design.R; 27import android.support.v7.app.AppCompatDialog; 28import android.util.TypedValue; 29import android.view.View; 30import android.view.ViewGroup; 31import android.view.Window; 32import android.widget.FrameLayout; 33 34/** 35 * Base class for {@link android.app.Dialog}s styled as a bottom sheet. 36 */ 37public class BottomSheetDialog extends AppCompatDialog { 38 39 private BottomSheetBehavior<FrameLayout> mBehavior; 40 41 boolean mCancelable = true; 42 private boolean mCanceledOnTouchOutside = true; 43 private boolean mCanceledOnTouchOutsideSet; 44 45 public BottomSheetDialog(@NonNull Context context) { 46 this(context, 0); 47 } 48 49 public BottomSheetDialog(@NonNull Context context, @StyleRes int theme) { 50 super(context, getThemeResId(context, theme)); 51 // We hide the title bar for any style configuration. Otherwise, there will be a gap 52 // above the bottom sheet when it is expanded. 53 supportRequestWindowFeature(Window.FEATURE_NO_TITLE); 54 } 55 56 protected BottomSheetDialog(@NonNull Context context, boolean cancelable, 57 OnCancelListener cancelListener) { 58 super(context, cancelable, cancelListener); 59 supportRequestWindowFeature(Window.FEATURE_NO_TITLE); 60 mCancelable = cancelable; 61 } 62 63 @Override 64 public void setContentView(@LayoutRes int layoutResId) { 65 super.setContentView(wrapInBottomSheet(layoutResId, null, null)); 66 } 67 68 @Override 69 protected void onCreate(Bundle savedInstanceState) { 70 super.onCreate(savedInstanceState); 71 getWindow().setLayout( 72 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 73 } 74 75 @Override 76 public void setContentView(View view) { 77 super.setContentView(wrapInBottomSheet(0, view, null)); 78 } 79 80 @Override 81 public void setContentView(View view, ViewGroup.LayoutParams params) { 82 super.setContentView(wrapInBottomSheet(0, view, params)); 83 } 84 85 @Override 86 public void setCancelable(boolean cancelable) { 87 super.setCancelable(cancelable); 88 if (mCancelable != cancelable) { 89 mCancelable = cancelable; 90 if (mBehavior != null) { 91 mBehavior.setHideable(cancelable); 92 } 93 } 94 } 95 96 @Override 97 public void setCanceledOnTouchOutside(boolean cancel) { 98 super.setCanceledOnTouchOutside(cancel); 99 if (cancel && !mCancelable) { 100 mCancelable = true; 101 } 102 mCanceledOnTouchOutside = cancel; 103 mCanceledOnTouchOutsideSet = true; 104 } 105 106 private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) { 107 final CoordinatorLayout coordinator = (CoordinatorLayout) View.inflate(getContext(), 108 R.layout.design_bottom_sheet_dialog, null); 109 if (layoutResId != 0 && view == null) { 110 view = getLayoutInflater().inflate(layoutResId, coordinator, false); 111 } 112 FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet); 113 mBehavior = BottomSheetBehavior.from(bottomSheet); 114 mBehavior.setBottomSheetCallback(mBottomSheetCallback); 115 mBehavior.setHideable(mCancelable); 116 if (params == null) { 117 bottomSheet.addView(view); 118 } else { 119 bottomSheet.addView(view, params); 120 } 121 // We treat the CoordinatorLayout as outside the dialog though it is technically inside 122 coordinator.findViewById(R.id.touch_outside).setOnClickListener(new View.OnClickListener() { 123 @Override 124 public void onClick(View view) { 125 if (mCancelable && isShowing() && shouldWindowCloseOnTouchOutside()) { 126 cancel(); 127 } 128 } 129 }); 130 return coordinator; 131 } 132 133 boolean shouldWindowCloseOnTouchOutside() { 134 if (!mCanceledOnTouchOutsideSet) { 135 if (Build.VERSION.SDK_INT < 11) { 136 mCanceledOnTouchOutside = true; 137 } else { 138 TypedArray a = getContext().obtainStyledAttributes( 139 new int[]{android.R.attr.windowCloseOnTouchOutside}); 140 mCanceledOnTouchOutside = a.getBoolean(0, true); 141 a.recycle(); 142 } 143 mCanceledOnTouchOutsideSet = true; 144 } 145 return mCanceledOnTouchOutside; 146 } 147 148 private static int getThemeResId(Context context, int themeId) { 149 if (themeId == 0) { 150 // If the provided theme is 0, then retrieve the dialogTheme from our theme 151 TypedValue outValue = new TypedValue(); 152 if (context.getTheme().resolveAttribute( 153 R.attr.bottomSheetDialogTheme, outValue, true)) { 154 themeId = outValue.resourceId; 155 } else { 156 // bottomSheetDialogTheme is not provided; we default to our light theme 157 themeId = R.style.Theme_Design_Light_BottomSheetDialog; 158 } 159 } 160 return themeId; 161 } 162 163 private BottomSheetBehavior.BottomSheetCallback mBottomSheetCallback 164 = new BottomSheetBehavior.BottomSheetCallback() { 165 @Override 166 public void onStateChanged(@NonNull View bottomSheet, 167 @BottomSheetBehavior.State int newState) { 168 if (newState == BottomSheetBehavior.STATE_HIDDEN) { 169 dismiss(); 170 } 171 } 172 173 @Override 174 public void onSlide(@NonNull View bottomSheet, float slideOffset) { 175 } 176 }; 177 178} 179