BottomSheetDialog.java revision d7283f666ec5b3d4e3fff2b328ec04ad1022bfd3
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.v4.view.AccessibilityDelegateCompat; 28import android.support.v4.view.ViewCompat; 29import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; 30import android.support.v7.app.AppCompatDialog; 31import android.util.TypedValue; 32import android.view.MotionEvent; 33import android.view.View; 34import android.view.ViewGroup; 35import android.view.Window; 36import android.widget.FrameLayout; 37 38/** 39 * Base class for {@link android.app.Dialog}s styled as a bottom sheet. 40 */ 41public class BottomSheetDialog extends AppCompatDialog { 42 43 private BottomSheetBehavior<FrameLayout> mBehavior; 44 45 boolean mCancelable = true; 46 private boolean mCanceledOnTouchOutside = true; 47 private boolean mCanceledOnTouchOutsideSet; 48 49 public BottomSheetDialog(@NonNull Context context) { 50 this(context, 0); 51 } 52 53 public BottomSheetDialog(@NonNull Context context, @StyleRes int theme) { 54 super(context, getThemeResId(context, theme)); 55 // We hide the title bar for any style configuration. Otherwise, there will be a gap 56 // above the bottom sheet when it is expanded. 57 supportRequestWindowFeature(Window.FEATURE_NO_TITLE); 58 } 59 60 protected BottomSheetDialog(@NonNull Context context, boolean cancelable, 61 OnCancelListener cancelListener) { 62 super(context, cancelable, cancelListener); 63 supportRequestWindowFeature(Window.FEATURE_NO_TITLE); 64 mCancelable = cancelable; 65 } 66 67 @Override 68 public void setContentView(@LayoutRes int layoutResId) { 69 super.setContentView(wrapInBottomSheet(layoutResId, null, null)); 70 } 71 72 @Override 73 protected void onCreate(Bundle savedInstanceState) { 74 super.onCreate(savedInstanceState); 75 getWindow().setLayout( 76 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 77 } 78 79 @Override 80 public void setContentView(View view) { 81 super.setContentView(wrapInBottomSheet(0, view, null)); 82 } 83 84 @Override 85 public void setContentView(View view, ViewGroup.LayoutParams params) { 86 super.setContentView(wrapInBottomSheet(0, view, params)); 87 } 88 89 @Override 90 public void setCancelable(boolean cancelable) { 91 super.setCancelable(cancelable); 92 if (mCancelable != cancelable) { 93 mCancelable = cancelable; 94 if (mBehavior != null) { 95 mBehavior.setHideable(cancelable); 96 } 97 } 98 } 99 100 @Override 101 public void setCanceledOnTouchOutside(boolean cancel) { 102 super.setCanceledOnTouchOutside(cancel); 103 if (cancel && !mCancelable) { 104 mCancelable = true; 105 } 106 mCanceledOnTouchOutside = cancel; 107 mCanceledOnTouchOutsideSet = true; 108 } 109 110 private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) { 111 final CoordinatorLayout coordinator = (CoordinatorLayout) View.inflate(getContext(), 112 R.layout.design_bottom_sheet_dialog, null); 113 if (layoutResId != 0 && view == null) { 114 view = getLayoutInflater().inflate(layoutResId, coordinator, false); 115 } 116 FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet); 117 mBehavior = BottomSheetBehavior.from(bottomSheet); 118 mBehavior.setBottomSheetCallback(mBottomSheetCallback); 119 mBehavior.setHideable(mCancelable); 120 if (params == null) { 121 bottomSheet.addView(view); 122 } else { 123 bottomSheet.addView(view, params); 124 } 125 // We treat the CoordinatorLayout as outside the dialog though it is technically inside 126 coordinator.findViewById(R.id.touch_outside).setOnClickListener(new View.OnClickListener() { 127 @Override 128 public void onClick(View view) { 129 if (mCancelable && isShowing() && shouldWindowCloseOnTouchOutside()) { 130 cancel(); 131 } 132 } 133 }); 134 // Handle accessibility events 135 ViewCompat.setAccessibilityDelegate(bottomSheet, new AccessibilityDelegateCompat() { 136 @Override 137 public void onInitializeAccessibilityNodeInfo(View host, 138 AccessibilityNodeInfoCompat info) { 139 super.onInitializeAccessibilityNodeInfo(host, info); 140 if (mCancelable) { 141 info.addAction(AccessibilityNodeInfoCompat.ACTION_DISMISS); 142 info.setDismissable(true); 143 } else { 144 info.setDismissable(false); 145 } 146 } 147 148 @Override 149 public boolean performAccessibilityAction(View host, int action, Bundle args) { 150 if (action == AccessibilityNodeInfoCompat.ACTION_DISMISS && mCancelable) { 151 cancel(); 152 return true; 153 } 154 return super.performAccessibilityAction(host, action, args); 155 } 156 }); 157 bottomSheet.setOnTouchListener(new View.OnTouchListener() { 158 @Override 159 public boolean onTouch(View view, MotionEvent event) { 160 // Consume the event and prevent it from falling through 161 return true; 162 } 163 }); 164 return coordinator; 165 } 166 167 boolean shouldWindowCloseOnTouchOutside() { 168 if (!mCanceledOnTouchOutsideSet) { 169 if (Build.VERSION.SDK_INT < 11) { 170 mCanceledOnTouchOutside = true; 171 } else { 172 TypedArray a = getContext().obtainStyledAttributes( 173 new int[]{android.R.attr.windowCloseOnTouchOutside}); 174 mCanceledOnTouchOutside = a.getBoolean(0, true); 175 a.recycle(); 176 } 177 mCanceledOnTouchOutsideSet = true; 178 } 179 return mCanceledOnTouchOutside; 180 } 181 182 private static int getThemeResId(Context context, int themeId) { 183 if (themeId == 0) { 184 // If the provided theme is 0, then retrieve the dialogTheme from our theme 185 TypedValue outValue = new TypedValue(); 186 if (context.getTheme().resolveAttribute( 187 R.attr.bottomSheetDialogTheme, outValue, true)) { 188 themeId = outValue.resourceId; 189 } else { 190 // bottomSheetDialogTheme is not provided; we default to our light theme 191 themeId = R.style.Theme_Design_Light_BottomSheetDialog; 192 } 193 } 194 return themeId; 195 } 196 197 private BottomSheetBehavior.BottomSheetCallback mBottomSheetCallback 198 = new BottomSheetBehavior.BottomSheetCallback() { 199 @Override 200 public void onStateChanged(@NonNull View bottomSheet, 201 @BottomSheetBehavior.State int newState) { 202 if (newState == BottomSheetBehavior.STATE_HIDDEN) { 203 cancel(); 204 } 205 } 206 207 @Override 208 public void onSlide(@NonNull View bottomSheet, float slideOffset) { 209 } 210 }; 211 212} 213