Đặt trạng thái của BottomSheetDialogFragment thành được mở rộng


93

Làm cách nào để bạn đặt trạng thái của một phân đoạn mở rộng BottomSheetDialogFragmentthành được mở rộng bằng BottomSheetBehavior#setState(STATE_EXPANDED)cách sử dụng Thư viện thiết kế hỗ trợ Android (v23.2.1)?

https://code.google.com/p/android/issues/detail?id=202396 cho biết:

Trang tính dưới cùng được đặt thành STATE_COLLAPSED lúc đầu. Gọi BottomSheetBehavior # setState (STATE_EXPANDED) nếu bạn muốn mở rộng nó. Lưu ý rằng bạn không thể gọi phương thức trước khi xem bố cục.

Phương pháp được đề xuất yêu cầu chế độ xem phải được thổi phồng trước, nhưng tôi không chắc cách đặt BottomSheetBehaviour thành một mảnh ( BottomSheetDialogFragment).

View bottomSheet = coordinatorLayout.findViewById(R.id.bottom_sheet);  
BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);  

Câu trả lời:


224

"Lưu ý rằng bạn không thể gọi phương thức trước khi xem bố cục."

Văn bản trên là manh mối.

Hộp thoại có một trình nghe được kích hoạt sau khi hộp thoại được hiển thị . Hộp thoại không thể được hiển thị nếu nó không được bố trí.

Vì vậy, trong onCreateDialog()trang dưới cùng phương thức của bạn ( BottomSheetFragment), ngay trước khi trả lại hộp thoại (hoặc bất kỳ nơi nào, khi bạn có tham chiếu đến hộp thoại), hãy gọi:

// This listener's onShow is fired when the dialog is shown
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
    @Override
    public void onShow(DialogInterface dialog) {

        // In a previous life I used this method to get handles to the positive and negative buttons
        // of a dialog in order to change their Typeface. Good ol' days.

        BottomSheetDialog d = (BottomSheetDialog) dialog;

        // This is gotten directly from the source of BottomSheetDialog
        // in the wrapInBottomSheet() method
        FrameLayout bottomSheet = (FrameLayout) d.findViewById(android.support.design.R.id.design_bottom_sheet);

        // Right here!
        BottomSheetBehavior.from(bottomSheet)
            .setState(BottomSheetBehavior.STATE_EXPANDED);
    }
});

Trong trường hợp của tôi, tùy chỉnh của tôi BottomSheethóa ra là:

@SuppressWarnings("ConstantConditions")
public class ShareBottomSheetFragment extends AppCompatDialogFragment {

    @NonNull @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        BottomSheetDialog dialog =
                new BottomSheetDialog(getActivity(), R.style.Haute_Dialog_ShareImage);

        dialog.setContentView(R.layout.dialog_share_image);

        dialog.findViewById(R.id.cancel).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });

        dialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                BottomSheetDialog d = (BottomSheetDialog) dialog;

                FrameLayout bottomSheet = (FrameLayout) d.findViewById(android.support.design.R.id.design_bottom_sheet);
                BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED);
            }
        });

        SwitchCompat switchview = (SwitchCompat) dialog.findViewById(R.id.switchview);
        switchview.setTypeface(FontCache.get(dialog.getContext(), lookup(muli, NORMAL)));

        return dialog;
    }
}

Hãy cho tôi biết nếu điều này sẽ giúp.

CẬP NHẬT

Lưu ý rằng bạn cũng có thể ghi đè BottomSheetDialogFragmentthành:

public class SimpleInitiallyExpandedBottomSheetFragment extends BottomSheetDialogFragment {

    @NonNull @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        BottomSheetDialog dialog = (BottomSheetDialog) super.onCreateDialog(savedInstanceState);

        dialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                BottomSheetDialog d = (BottomSheetDialog) dialog;

                FrameLayout bottomSheet = (FrameLayout) d.findViewById(android.support.design.R.id.design_bottom_sheet);
                BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED);
            }
        });

        // Do something with your dialog like setContentView() or whatever
        return dialog;
    }
}

Nhưng tôi thực sự không hiểu tại sao mọi người lại muốn làm điều đó vì cơ sở BottomSheetFragmentkhông làm bất cứ điều gì khác ngoài việc trả về a BottomSheetDialog.

CẬP NHẬT CHO ANDROIDX

Khi sử dụng AndroidX, tài nguyên được tìm thấy trước đây tại android.support.design.R.id.design_bottom_sheethiện có thể được tìm thấy tại com.google.android.material.R.id.design_bottom_sheet.


Cảm ơn, tôi đã thử phương pháp này. Nó làm cho giao diện có BottomSheetDialogFragmentvẻ buồn tẻ (dường như bỏ qua các khung hình trong hoạt ảnh mở đầu) khi nó chuyển từ hành vi thu gọn sang mở rộng. Chỉnh sửa: Đã kiểm tra điều này trên thiết bị Android Marshmallow và KitKat
dùng2560886

1
Nó hoạt động hoàn hảo cho tôi. Không bỏ qua. Bạn có đang làm gì khác ngoài việc chỉ trả lại một hộp thoại không? Sẽ đánh giá cao nếu bạn cập nhật bài đăng của mình với mã của bạn để tôi có thể có một Ý tưởng tốt hơn.
efemoney

5
Có phải tôi chỉ nhận được gói không thể tìm thấy android.support.design.Rsau khi cập nhật các thư viện hỗ trợ?
natario, 17/09/17

2
Tôi cũng gặp vấn đề với việc giải quyết android.support.design.R, giống như @natario. Tôi đang sử dụng implementation "com.google.android.material:material:1.0.0". Tôi cũng đang sử dụng AndroidX trong dự án.
hsson

21
Khi sử dụng AndroidX, bạn có thể tìm thấy tài nguyên tạicom.google.android.material.R.id.design_bottom_sheet
khẩn cấp.

45

Câu trả lời của efeturi rất tuyệt, tuy nhiên, nếu bạn muốn sử dụng onCreateView () để tạo BottomSheet của mình, trái ngược với việc sử dụng onCreateDialog () , đây là mã bạn sẽ cần thêm vào trong phương thức onCreateView () của mình :

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    getDialog().setOnShowListener(new DialogInterface.OnShowListener() {
        @Override
        public void onShow(DialogInterface dialog) {
            BottomSheetDialog d = (BottomSheetDialog) dialog;
            View bottomSheetInternal = d.findViewById(android.support.design.R.id.design_bottom_sheet);
            BottomSheetBehavior.from(bottomSheetInternal).setState(BottomSheetBehavior.STATE_EXPANDED);
        }
    });
    return inflater.inflate(R.layout.your_bottomsheet_content_layout, container, false);
}

3
Ngoài ra, bạn không cần phải gọi getDialog. Tôi thấy cách tốt nhất để làm điều đó là ghi đè cả onCreateView và onCreateDialog. Xây dựng tầm nhìn của bạn trong onCreateView (như bạn làm với bất kỳ đoạn) và làm các mã cụ thể thoại trong onCreateDialog (gọi super.onCreateDialog để có được những ví dụ)
Stimsoni

2
Điều này tiết kiệm ngày của tôi. Cảm ơn.
AndroidRuntimeException

@Stimsoni onCreateView không được gọi nếu onCreateDialog được sử dụng. developer.android.com/reference/android/support/v4/app/…
Vincent_Paing

1
@Vincent_Paing, Đúng vậy. Trong liên kết đính kèm của bạn có ghi 'onCreateView không cần phải được triển khai'. Nó không nói rằng nó sẽ không được gọi. Hãy xem mã nguồn tại đây github.com/material-components/material-components-android/blob/… . Việc triển khai mặc định gọi onCreateDialog để tạo trang tính dưới cùng và mọi giải pháp ở trên vẫn đang sử dụng onCreateView nghĩa là chúng luôn được gọi. Chỉ cần đảm bảo rằng bạn vẫn gọi super.onCreateDialog () nếu bạn ghi đè nó.
Stimsoni

trong BottomSheetDialogFragment nó khiến tôi gặp sự cố trong onCreateView () Tôi đã đặt nó vào onViewCreate () và nó thật hoàn hảo! cảm ơn bạn
avisper

22

Một giải pháp đơn giản và thanh lịch:

BottomSheetDialogFragment có thể được phân lớp để giải quyết vấn đề này:

class NonCollapsableBottomSheetDialogFragment extends BottomSheetDialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        final BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) super.onCreateDialog(savedInstanceState);

        bottomSheetDialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                FrameLayout bottomSheet = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);

                BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
                behavior.setSkipCollapsed(true);
                behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
            }
        });
        return bottomSheetDialog;
    }
}

Vì vậy, hãy mở rộng lớp này thay vì BottomSheetDialogFragmenttạo trang tính dưới cùng của riêng bạn.

Ghi chú

Thay đổi com.google.android.material.R.id.design_bottom_sheetthành android.support.design.R.id.design_bottom_sheetnếu dự án của bạn sử dụng các thư viện hỗ trợ Android cũ.


1
Có vẻ là com.google.android.material.Rbây giờ thay vì android.support.design.R.
EpicPandaForce

@EpicPandaForce Chắc chắn rồi. Nhóm Android tại Google gần đây đã đóng gói lại thư viện hỗ trợ cũ.
DYS

4

Tôi nghĩ những điều trên là tốt hơn. Đáng buồn thay, tôi đã không tìm thấy những giải pháp trước khi tôi đã giải quyết. Nhưng hãy viết giải pháp của tôi. khá giống với tất cả.

================================================== ================================

Tôi phải đối mặt với cùng một vấn đề. Đây là những gì tôi đã giải quyết. Hành vi được ẩn trong BottomSheetDialog, có sẵn để thực hiện hành vi Nếu bạn không muốn thay đổi bố cục mẹ của mình thành CooridateLayout, bạn có thể thử điều này.

BƯỚC 1: tùy chỉnh BottomSheetDialogFragment

open class CBottomSheetDialogFragment : BottomSheetDialogFragment() {
   //wanna get the bottomSheetDialog
   protected lateinit var dialog : BottomSheetDialog 
   override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
      dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
      return dialog
   }

   //set the behavior here
   fun setFullScreen(){
      dialog.behavior.state = STATE_EXPANDED
   }
}

BƯỚC 2: làm cho phân đoạn của bạn mở rộng phân đoạn tùy chỉnh này

class YourBottomSheetFragment : CBottomSheetDialogFragment(){

   //make sure invoke this method after view is built
   //such as after OnActivityCreated(savedInstanceState: Bundle?)
   override fun onStart() {
      super.onStart()
      setFullScreen()//initiated at onActivityCreated(), onStart()
   }
}

3
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                BottomSheetDialog d = (BottomSheetDialog) dialog;

                FrameLayout bottomSheet = (FrameLayout) d.findViewById(android.support.design.R.id.design_bottom_sheet);
                BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED);
            }
        });

Tôi đã gặp NullPointException BottomSheetBehavior.from(bottomSheet)d.findViewById(android.support.design.R.id.design_bottom_sheet)trả về null.

Thật là kỳ lạ. Tôi thêm dòng mã này vào Đồng hồ trong Màn hình Android ở chế độ GỠ LỖI và thấy nó trả về Framelayout bình thường.

Đây là mã của wrapInBottomSheettrong BottomSheetDialog:

private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) {
        final CoordinatorLayout coordinator = (CoordinatorLayout) View.inflate(getContext(),
                R.layout.design_bottom_sheet_dialog, null);
        if (layoutResId != 0 && view == null) {
            view = getLayoutInflater().inflate(layoutResId, coordinator, false);
        }
        FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet);
        BottomSheetBehavior.from(bottomSheet).setBottomSheetCallback(mBottomSheetCallback);
        if (params == null) {
            bottomSheet.addView(view);
        } else {
            bottomSheet.addView(view, params);
        }
        // We treat the CoordinatorLayout as outside the dialog though it is technically inside
        if (shouldWindowCloseOnTouchOutside()) {
            coordinator.findViewById(R.id.touch_outside).setOnClickListener(
                    new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            if (isShowing()) {
                                cancel();
                            }
                        }
                    });
        }
        return coordinator;
    }

Thỉnh thoảng, tôi thấy rằng R.id.design_bottom_sheetkhông bằng android.support.design.R.id.design_bottom_sheet. Chúng có giá trị khác nhau trong các R.java khác nhau.

Vì vậy, tôi thay đổi android.support.design.R.id.design_bottom_sheetthành R.id.design_bottom_sheet.

dialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                BottomSheetDialog d = (BottomSheetDialog) dialog;

                FrameLayout bottomSheet = (FrameLayout) d.findViewById(R.id.design_bottom_sheet); // use R.java of current project
                BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED);
            }
        });

Không còn NullPointException bây giờ.


3

Áp dụng BottomsheetDialogFragmenttrạng thái trong onResumesẽ giải quyết vấn đề này

@Override
public void onResume() {
    super.onResume();
    if(mBehavior!=null)
       mBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}

onShow(DialogInterface dialog)postDelayedcó thể gây ra trục trặc cho hoạt ảnh


2

Tất cả các kết quả khi sử dụng onShow () đều gây ra lỗi hiển thị ngẫu nhiên khi bàn phím mềm được hiển thị. Xem ảnh chụp màn hình bên dưới - Hộp thoại BottomSheet không nằm ở cuối màn hình mà được đặt giống như bàn phím được hiển thị. Vấn đề này không phải luôn xảy ra nhưng khá thường xuyên.

nhập mô tả hình ảnh ở đây

CẬP NHẬT

Giải pháp của tôi với phản ánh của thành viên tư nhân là không cần thiết. Sử dụng postDelayed (với khoảng 100 ms) để tạo và hiển thị hộp thoại sau khi ẩn bàn phím mềm là giải pháp tốt hơn. Sau đó, các giải pháp trên với onShow () là ok.

Utils.hideSoftKeyboard(this);
mView.postDelayed(new Runnable() {
    @Override
    public void run() {
        MyBottomSheetDialog dialog = new MyBottomSheetDialog();
        dialog.setListener(MyActivity.this);
        dialog.show(getSupportFragmentManager(), TAG_BOTTOM_SHEET_DLG);
    }
}, 100);

Vì vậy, tôi thực hiện giải pháp khác, nhưng nó yêu cầu sử dụng phản chiếu, vì BottomSheetDialog có tất cả các thành viên là riêng tư. Nhưng nó giải quyết lỗi kết xuất. Lớp BottomSheetDialogFragment chỉ là AppCompatDialogFragment với phương thức onCreateDialog tạo BottomSheetDialog. Tôi tạo con riêng của AppCompatDialogFragment tạo lớp của tôi mở rộng BottomSheetDialog và giải quyết quyền truy cập vào thành viên hành vi riêng tư và đặt nó trong phương thức onStart thành trạng thái STATE_EXPANDED.

public class ExpandedBottomSheetDialog extends BottomSheetDialog {

    protected BottomSheetBehavior<FrameLayout> mBehavior;

    public ExpandedBottomSheetDialog(@NonNull Context context, @StyleRes int theme) {
        super(context, theme);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        try {
            Field privateField = BottomSheetDialog.class.getDeclaredField("mBehavior");
            privateField.setAccessible(true);
            mBehavior = (BottomSheetBehavior<FrameLayout>) privateField.get(this);
        } catch (NoSuchFieldException e) {
            // do nothing
        } catch (IllegalAccessException e) {
            // do nothing
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (mBehavior != null) {
            mBehavior.setSkipCollapsed(true);
            mBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
        }
    }
}


public class AddAttachmentBottomSheetDialog extends AppCompatDialogFragment {

    ....

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new ExpandedBottomSheetDialog(getContext(), getTheme());
    }

    ....
}

1

Cách dễ nhất mà tôi đã triển khai như bên dưới, Ở đây chúng tôi đang tìm android.support.design.R.id.design_bottom_sheet và đặt trạng thái trang dưới cùng là MỞ RỘNG .

Nếu không có điều này, trang tính dưới cùng của tôi luôn bị kẹt ở trạng thái ĐÃ LẠNH nếu chiều cao của chế độ xem lớn hơn 0,5 chiều cao của màn hình và tôi phải cuộn theo cách thủ công để xem toàn bộ trang dưới cùng.

class BottomSheetDialogExpanded(context: Context) : BottomSheetDialog(context) {

    private lateinit var mBehavior: BottomSheetBehavior<FrameLayout>

    override fun setContentView(view: View) {
        super.setContentView(view)
        val bottomSheet = window.decorView.findViewById<View>(android.support.design.R.id.design_bottom_sheet) as FrameLayout
        mBehavior = BottomSheetBehavior.from(bottomSheet)
        mBehavior.state = BottomSheetBehavior.STATE_EXPANDED
    }

    override fun onStart() {
        super.onStart()
        mBehavior.state = BottomSheetBehavior.STATE_EXPANDED
    }
}

1

Tương tự như câu trả lời uregentx , trong kotlin , bạn có thể khai báo lớp phân mảnh của mình mở rộng từ đó BottomSheetDialogFragmentvà khi chế độ xem được tạo, bạn có thể đặt trạng thái mặc định của trình nghe hộp thoại sau khi hộp thoại được hiển thị.

STATE_COLLAPSED: Trang tính dưới cùng có thể nhìn thấy nhưng chỉ hiển thị chiều cao nhìn ra.

STATE_EXPANDED: Trang dưới cùng có thể nhìn thấy và chiều cao tối đa của nó.

STATE_HALF_EXPANDED: Trang tính dưới cùng có thể nhìn thấy nhưng chỉ hiển thị một nửa chiều cao của nó.

class FragmentCreateGroup : BottomSheetDialogFragment() {
      ...

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
        // Set dialog initial state when shown
        dialog?.setOnShowListener {
            val bottomSheetDialog = it as BottomSheetDialog
            val sheetInternal: View = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet)!!
            BottomSheetBehavior.from(sheetInternal).state = BottomSheetBehavior.STATE_COLLAPSED
        }

        val view = inflater.inflate(R.layout.fragment_create_group, container, false)
        ...

        return view
    }
}

Hãy nhớ sử dụng thiết kế material design trong gradle.

implementation "com.google.android.material:material:$version"

Ngoài ra, hãy xem phần tham khảo material design Bottom Sheets


Trường hợp không dialog?biến đến từ trong onCreateView?
Eric Smith,

dialoglà một thuộc tính của lớp DialogFragment, thực sự là một Getter. Trong ví dụ này, tôi đã sử dụng getter đó để lấy phiên bản DialogFragment hiện tại và setOnShowListenernó. Có thể bạn đã sử dụng loại hướng dẫn đó trong dự án của mình, ví dụ như trong một hoạt động, để truy cập vào trình tải thanh hành động actionBarđược sử dụng, vì vậy bạn có thể sửa đổi thành phần đó, chẳng hạnactionBar?.subtitle = "abcd"
FJCG

1

Câu trả lời của tôi ít nhiều giống với hầu hết các câu trả lời ở trên với một chút sửa đổi. Thay vì sử dụng findViewById để tìm chế độ xem trang dưới cùng, tôi không muốn mã hóa cứng bất kỳ id tài nguyên chế độ xem khung nào vì chúng có thể thay đổi trong tương lai.

setOnShowListener(dialog -> {
            BottomSheetBehavior bottomSheetBehavior = ((BottomSheetDialog)dialog).getBehavior();
            bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
        });

1
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    return super.onCreateDialog(savedInstanceState).apply {
        setOnShowListener {
            (this@TipsBottomDialogFragment.dialog as BottomSheetDialog).behavior.setState(
                BottomSheetBehavior.STATE_EXPANDED
            )
        }
    }
}

1

Đăng điều này ở đây cho độc giả trong tương lai, vì tôi nghĩ rằng chúng ta có thể sử dụng một giải pháp khác.

Tôi đang cố gắng giải quyết vấn đề tương tự như bạn đã mô tả với a BottomSheetDialog.

Tôi không thích sử dụng id nội bộ của Android và tôi vừa tìm thấy có một phương pháp bên trong BottomSheetDialog getBehaviormà bạn có thể sử dụng:

Bạn có thể sử dụng cái này bên trong BottomSheetDialog:

behavior.state = BottomSheetBehavior.STATE_EXPANDED

Sử dụng, BottomSheetDialogFragmentbạn có thể thực hiện tương tự như truyền hộp thoại từ DialogFragment đó sang BottomSheetDialog.


1

BottomSheetDialogFragment :

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    (dialog as? BottomSheetDialog)?.behavior?.state = STATE_EXPANDED
}

hoặc khi sẵn sàng hiển thị:

private fun onContentLoaded(items: List<Any>) {
    adapter.submitList(items)
    (dialog as? BottomSheetDialog)?.behavior?.state = STATE_EXPANDED
}

0

Trong lớp Kotlin BottomSheetDialogFragment của bạn, ghi đè onCreateDialog như bên dưới

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val bottomSheetDialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
        bottomSheetDialog.setOnShowListener {
            val bottomSheet =
                bottomSheetDialog.findViewById<FrameLayout>(
                    com.google.android.material.R.id.design_bottom_sheet
                )
            val behavior = BottomSheetBehavior.from(bottomSheet!!)
            behavior.skipCollapsed = true
            behavior.state = BottomSheetBehavior.STATE_EXPANDED
        }
        return bottomSheetDialog
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.