3

我正在創建一個BottomSheetDialogFragment,允許用戶拍攝照片或從其庫中選擇一張照片。要訪問任一功能,需要WRITE_EXTERNAL_STORAGE權限。防止AlertDialog顯示在另一個DialogFragment的模糊後面

因此,我想要徵得BottomSheetDialogFragment的許可,防止用戶點擊其他任何東西,直到授予權限。如果我在onViewCreated請求權限,權限對話框顯示細膩:

Permission Request

不過,如果權限被拒絕,並且用戶再次嘗試我試圖顯示在AlertDialog的理由,但該對話框被堵塞;推測是通過從BottomSheetDialogFragment朦朧:

Permission Rationale

我認爲這是由BottomSheetDialogFragment的動畫,其不顯示背景昏暗,直到片段完成其動畫引起的。這巧合地發生在onViewCreated之後。有誰知道是否有辦法迫使AlertDialog前面沒有關閉或解僱BottomSheetDialogFragment?或者如果有辦法傾聽BottomSheetDialogFragment動畫完成?

我知道我可能在添加BottomSheetDialogFragment之前請求權限,但我寧願用對話框來請求它以便爲用戶提供一些上下文。


這裏是Fragment

public class ImageChooserDialogFragment extends BottomSheetDialogFragment { 

    public interface OnImageChosenListener { 
     void onImageChosen(Uri data); 
    } 

    private static final String PREFIX_IMAGE_CAPTURE = "IMG_"; 

    private static final int REQUEST_PERMISSION_CAMERA = 0; 
    private static final int REQUEST_PERMISSION_STORAGE = 1; 

    private static final int REQUEST_IMAGE_CAPTURE  = 2; 
    private static final int REQUEST_IMAGE_SELECTION = 3; 

    private static final String[] PERMISSIONS_CAMERA = new String[] { 
      Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA 
    }; 

    private static final String[] PERMISSIONS_STORAGE = new String[] { 
      Manifest.permission.WRITE_EXTERNAL_STORAGE 
    }; 

    private boolean hasFeatureCamera; 

    private Uri mCurrentPhotoResource; 

    private View mView; 

    private OnImageChosenListener mOnImageChosenListener; 

    public static ImageChooserDialogFragment newInstance() { 
     return new ImageChooserDialogFragment(); 
    } 

    @Override 
    public void onAttach(Context context) { 
     super.onAttach(context); 
     Timber.d("onAttach"); 

     if(context instanceof OnImageChosenListener) { 
      mOnImageChosenListener = (OnImageChosenListener) context; 
     } 
    } 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     Fragment parent = getParentFragment(); 

     if(parent != null) { 
      onAttachToFragment(parent); 
     } 

     PackageManager manager = getContext().getPackageManager(); 
     if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 
      hasFeatureCamera = manager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY); 
     } else { 
      hasFeatureCamera = manager.hasSystemFeature(PackageManager.FEATURE_CAMERA) 
        || manager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT); 
     } 
    } 

    public void onAttachToFragment(Fragment fragment) { 
     if(fragment instanceof OnImageChosenListener) { 
      mOnImageChosenListener = (OnImageChosenListener) fragment; 
     } 
    } 

    @Override 
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 
     mView = inflater.inflate(R.layout.dialog_image_chooser, container, false); 
     ButterKnife.bind(this, mView); 

     if(!hasFeatureCamera) { 
      mView.setVisibility(View.GONE); 
     } 

     return mView; 
    } 

    @Override 
    public void onViewCreated(View view, Bundle savedInstanceState) { 
     super.onViewCreated(view, savedInstanceState); 
     if(!PermissionUtil.checkPermissions(getContext(), PERMISSIONS_STORAGE)) { 
      requestPermissionsWithRationale(REQUEST_PERMISSION_STORAGE, PERMISSIONS_STORAGE); 
     } else if(!hasFeatureCamera) { 
      dispatchImageSelectionIntent(); 
     } else { 
      displayRequestPermissionsAlert(R.string.msg_snackbar_permissions_storage); 
     } 
    } 

    @Override 
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 
              @NonNull int[] grantResults) { 
     switch(requestCode) { 
      case REQUEST_PERMISSION_CAMERA: 
       if(PermissionUtil.verifyPermissions(grantResults)) { 
        dispatchImageCaptureIntent(); 
       } else { 
        displayRequestPermissionsAlert(R.string.msg_snackbar_permissions_camera); 
       } break; 
      case REQUEST_PERMISSION_STORAGE: 
       if(PermissionUtil.verifyPermissions(grantResults)) { 
        if(!hasFeatureCamera) { 
         dispatchImageSelectionIntent(); 
        } 
       } else { 
        displayRequestPermissionsAlert(R.string.msg_snackbar_permissions_storage); 
       } break; 
      default: 
       super.onRequestPermissionsResult(requestCode, permissions, grantResults); 
     } 
    } 

    @Override 
    public void onActivityResult(int requestCode, int resultCode, Intent data) { 
     switch (requestCode) { 
      case REQUEST_IMAGE_CAPTURE: 
       if(resultCode == Activity.RESULT_OK) { 
        handleImageCaptureResult(data); 
       } else { 
        destroyTemporaryFile(); 
       } break; 
      case REQUEST_IMAGE_SELECTION: 
       if(resultCode == Activity.RESULT_OK) { 
        handleImageSelectionResult(data); 
       } break; 
      default: 
       super.onActivityResult(requestCode, resultCode, data); 
     } 
    } 

    @OnClick(R.id.photo_take) 
    public void onClickCapture() { 
     if(!PermissionUtil.checkPermissions(getContext(), PERMISSIONS_CAMERA)) { 
      requestPermissionsWithRationale(REQUEST_PERMISSION_CAMERA, PERMISSIONS_CAMERA); 
     } else { 
      dispatchImageCaptureIntent(); 
     } 
    } 

    @OnClick(R.id.photo_choose) 
    public void onClickChoose() { 
     if(!PermissionUtil.checkPermissions(getContext(), PERMISSIONS_STORAGE)) { 
      requestPermissionsWithRationale(REQUEST_PERMISSION_STORAGE, PERMISSIONS_STORAGE); 
     } else { 
      dispatchImageSelectionIntent(); 
     } 
    } 

    private void dispatchImageCaptureIntent() { 
     Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 

     if(intent.resolveActivity(getContext().getPackageManager()) != null) { 
      try { 
       File image = createTemporaryFile(); 
       Uri data = FileProvider.getUriForFile(getContext(), "com.example.app.fileprovider", image); 
       intent.putExtra(MediaStore.EXTRA_OUTPUT, data); 
       startActivityForResult(intent, REQUEST_IMAGE_CAPTURE); 
      } catch (IOException exception) { 
       Timber.w(exception, "Error occurred while creating image file"); 
      } 
     } else { 
      // TODO: handle no application to handle intent 
     } 
    } 

    private void dispatchImageSelectionIntent() { 
     final Intent intent = new Intent(Intent.ACTION_PICK, 
       MediaStore.Images.Media.EXTERNAL_CONTENT_URI); 
     if(intent.resolveActivity(getContext().getPackageManager()) != null) { 
      startActivityForResult(intent, REQUEST_IMAGE_SELECTION); 
     } else { 
      // TODO: handle no application to handle intent 
     } dismiss(); 
    } 

    private void dispatchDetailSettingsIntent() { 
     Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 
       Uri.fromParts("package", getContext().getPackageName(), null)); 
     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 

     if(intent.resolveActivity(getContext().getPackageManager()) != null) { 
      startActivity(intent); 
     } else { 
      // TODO: handle no application to handle intent 
     } dismiss(); 
    } 

    private void dispatchMediaScanIntent(Uri data) { 
     Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, data); 
     getContext().sendBroadcast(intent); 
    } 

    private void displayPermissionsRationale(int requestCode) { 
     switch (requestCode) { 
      case REQUEST_PERMISSION_CAMERA: 
       Timber.d("Request Image capture rationale"); 
       DialogFactory.createRationaleAlert(getContext(), 
         R.string.title_dialog_rationale_camera, 
         R.string.msg_dialog_rationale_camera).show(); 
       break; 
      case REQUEST_PERMISSION_STORAGE: 
       Timber.d("Request Image selection rationale"); 
       DialogFactory.createRationaleAlert(getContext(), 
         R.string.title_dialog_rationale_storage, 
         R.string.msg_dialog_rationale_storage).show(); 
       break; 
      default: 
       Timber.d("No rationale"); 
     } 
    } 

    private void displayRequestPermissionsAlert(@StringRes int message) { 
     Snackbar.make(mView, message, Snackbar.LENGTH_LONG) 
       .setAction(R.string.action_settings, view -> dispatchDetailSettingsIntent()).show(); 
     dismiss(); 
    } 

    private void requestPermissionsWithRationale(int requestCode, @NonNull String[] permissions) { 
     Timber.d("Request permissions with rationale"); 
     if(PermissionUtil.shouldShowRequestPermissionsRationale(this, permissions)) { 
      Timber.d("Display rationale"); 
      displayPermissionsRationale(requestCode); 
     } else { 
      Timber.d("Request Permissions"); 
      requestPermissions(permissions, requestCode); 
     } 
    } 

    private File createTemporaryFile() throws IOException { 
     String fileName = PREFIX_IMAGE_CAPTURE /*+ TimeUtil.getTimeStamp()*/; 

     File directory = getContext().getExternalFilesDir(Environment.DIRECTORY_DCIM); 
     File file = File.createTempFile(fileName, ".jpeg", directory); 

     mCurrentPhotoResource = Uri.fromFile(file); 

     return file; 
    } 

    private void destroyTemporaryFile() { 
     File file = new File(mCurrentPhotoResource.getPath()); 

     if(file.delete()) { 
      Timber.i("Temporary file deleted"); 
     } else { 
      Timber.w("Failed to delete temporary file: " + file); 
     } 
    } 

    private void handleImageCaptureResult(Intent intent) { 
     if(mCurrentPhotoResource != null) { 
      dispatchMediaScanIntent(mCurrentPhotoResource); 

      if (mOnImageChosenListener != null) { 
       mOnImageChosenListener.onImageChosen(mCurrentPhotoResource); 
      } else { 
       Timber.w("Parent Activity or Fragment does not implement OnImageChosenListener; captured result cannot be used"); 
      } 
     } 
    } 

    private void handleImageSelectionResult(Intent intent) { 
     Timber.d("Selection: " + intent.getData()); 
     if(mOnImageChosenListener != null) { 
      mOnImageChosenListener.onImageChosen(intent.getData()); 
     } else { 
      Timber.w("Parent Activity or Fragment does not implement OnImageChosenListener; selected result cannot be used"); 
     } 
    } 

} 

而且DialogFactory類:

public final class DialogFactory { 

    public static AlertDialog createRationaleAlert(
      Context context, @StringRes int title, @StringRes int message) { 
     AlertDialog.Builder builder = new AlertDialog.Builder(context) 
       .setTitle(title).setMessage(message); 
     return createRationaleAlert(context, builder); 
    } 

    public static AlertDialog createRationaleAlert(
      Context context, CharSequence title, CharSequence message) { 
     AlertDialog.Builder builder = new AlertDialog.Builder(context) 
       .setTitle(title).setMessage(message); 
     return createRationaleAlert(context, builder); 
    } 

    private static AlertDialog createRationaleAlert(
      Context context, AlertDialog.Builder builder) { 
     builder.setPositiveButton(R.string.btn_try, (dialog, which) -> { 
      Intent intent = new Intent(Intent.ACTION_VIEW); 
      context.startActivity(intent); 
     }).setNegativeButton(R.string.btn_cancel, (dialog, which) -> { 
      dialog.cancel(); 
     }); 

     return builder.create(); 
    } 

} 

Fragment電話DialogFactory.createRationaleAlert().show()時出現的問題。

+0

我處於相同的情況。任何解決方案? –

+0

@AitorGómezNope。目前,我只在授予權限後才顯示「BottomSheetDialogFragment」。 – Bryan

+0

我試圖把DialogFragment中的DialogFragment放在一個子DialogFragment中,但它也不起作用。 謝謝。 –

回答

2

想來想去,我終於找到了解決辦法。關鍵是要落實DialogInterface.OnShowListener,並在onShow()回調創建新Dialog

public class ImageChooserDialogFragment extends BottomSheetDialogFragment 
     implements DialogInterface.OnShowListener { 

    @Override @NonNull 
    public Dialog onCreateDialog(Bundle savedInstanceState) { 
     super.onCreateDialog(savedInstanceState); 
     getDialog().setOnShowListener(this); 
    } 

    @Override 
    public void onShow(DialogInterface dialog) { 
     if(!PermissionUtil.checkPermissions(getContext(), PERMISSIONS_STORAGE)) { 
      requestPermissionsWithRationale(REQUEST_PERMISSION_STORAGE, PERMISSIONS_STORAGE); 
     } else { 
      displayRequestPermissionsAlert(R.string.msg_snackbar_permissions_storage); 
     } 
    } 

    // ... 

} 
1

是否有一種方法來監聽BottomSheetDialogFragment動畫完成?

mBottomSheetController = BottomSheetBehavior.from(mBottomSheet); 
mBottomSheetController.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { 
    @Override 
    public void onStateChanged(@NonNull View bottomSheet, int newState) { 
     switch (newState) { 
      case BottomSheetBehavior.STATE_EXPANDED: 
       //fully expanded, this is the event you want to listen for 
      break; 
     } 
    } 

    @Override 
    public void onSlide(@NonNull View bottomSheet, float slideOffset) { 
     //do nothing 
    } 
}); 
+0

這似乎並不奏效。首先,顯然'onStateChanged()'不是爲初始狀態調用的;所以除非我自己設置狀態,直到'BottomSheetDialog'被移動後纔會調用它。其次,如果我將狀態設置爲'STATE_EXPANDED',出於某種原因,我只會得到'STATE_SETTLING'的回調。如果我將對話框物理移動到展開狀態,我只會得到'STATE_EXPANDED'的回調。 – Bryan

+0

雖然,這證明我懷疑這與對話框的動畫有關,考慮如果我手動將對話框移動到「STATE_EXPANDED」,它確實在背景昏暗的上方顯示基本對話框。 – Bryan

+0

然後,也許一個(稍微黑客)的解決方案是在啓動基本對話框之前手動調用setState。我無法解釋你所得到的行爲(如果你不是在監聽器內部操作底部工作表) –

0

創建對話框,TYPE_SYSTEM_ALERT。

使用此:

builder.create().getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 
+0

這只是拋出一個'BadTokenException'消息「無法添加窗口[email protected] - 針對窗口類型2003的權限被拒絕「。 – Bryan

0

禁用標誌,負責昏暗的背景

dialog.getWindow()clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);

+0

這使對話框本身的背景透明,而不是對話框的背景暗淡。 – Bryan

+0

試試這個 alertDialog.getWindow()。clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); –