Làm cách nào để triển khai onBackPression () trong Fragment?


459

Có cách nào chúng ta có thể triển khai onBackPressed()trong Android Fragment tương tự như cách chúng ta triển khai trong Hoạt động Android không?

Như vòng đời mảnh vỡ không có onBackPressed(). Có phương pháp nào khác để vượt xe onBackPressed()trong các đoạn Android 3.0 không?


13
IMHO, một đoạn không nên biết cũng không quan tâm đến nút BACK. Một hoạt động có thể quan tâm đến nút BACK, mặc dù với các đoạn thường được xử lý bởi FragmentManager. Nhưng vì đoạn này không biết về môi trường lớn hơn của nó (ví dụ, có phải là một trong số nhiều hoạt động hay không), nên thực sự không an toàn cho một mảnh khi cố gắng xác định chức năng BACK thích hợp là gì.
CommonsWare

61
Sẽ thế nào khi tất cả các đoạn đó hiển thị một WebView và bạn muốn WebView "quay lại" trang trước đó khi nhấn nút quay lại?
mharper

Câu trả lời của Michael Herbig là hoàn hảo cho tôi. Nhưng đối với những người không thể sử dụng getSupportFragmentManager (). Sử dụng getFragmentManager () thay thế.
Dantalian

Câu trả lời của Dmitry Zaitsev về [Câu hỏi tương tự] [1] hoạt động tốt. [1]: stackoverflow.com/a/13450668/1372866
ashakirov

6
để trả lời mharper có thể bạn có thể làm một cái gì đó như webview.setOnKeyListener (new OnKeyListener () {@Override boolean công khai onKey (Xem v, int keyCode, sự kiện KeyEvent) {if (keyCode == KeyEvent.KEYCODE_BACK {webview.goBack (); return true;} return false;}});
Omid Aminiva

Câu trả lời:


307

Tôi đã giải quyết theo cách này ghi đè onBackPressedtrong Hoạt động. Tất cả FragmentTransactionaddToBackStacktrước khi cam kết:

@Override
public void onBackPressed() {

    int count = getSupportFragmentManager().getBackStackEntryCount();

    if (count == 0) {
        super.onBackPressed();
        //additional code
    } else {
        getSupportFragmentManager().popBackStack();
    }

}

vấn đề tương tự stackoverflow.com/questions/32132623/
Mạnh

4
đếm == 1 sẽ cho phép đóng đoạn đầu tiên trên một lần nhấn nút quay lại. Nhưng nếu không thì giải pháp tốt nhất
Kunalxigxag

2
Nếu bạn đang sử dụng thư viện v7 hỗ trợ và Hoạt động của bạn mở rộng từ FragmentActivity (hoặc một lớp con, chẳng hạn như AppCompatActivity), điều này sẽ xảy ra theo mặc định. Xem FragmentActivity # onBackPression
Xiao

3
nhưng bạn không thể sử dụng các biến, lớp và hàm từ lớp của đoạn trong mã này
user25

2
@Prabs nếu bạn đang sử dụng đoạn v4 hỗ trợ thì hãy chắc chắn rằng bạn đang sử dụng getSupportFragmentManager (). GetBackStackEntryCount ();
Veer3383

151

Theo tôi giải pháp tốt nhất là:

GIẢI PHÁP JAVA

Tạo giao diện đơn giản:

public interface IOnBackPressed {
    /**
     * If you return true the back press will not be taken into account, otherwise the activity will act naturally
     * @return true if your processing has priority if not false
     */
    boolean onBackPressed();
}

Và trong hoạt động của bạn

public class MyActivity extends Activity {
    @Override public void onBackPressed() {
    Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_container);
       if (!(fragment instanceof IOnBackPressed) || !((IOnBackPressed) fragment).onBackPressed()) {
          super.onBackPressed();
       }
    } ...
}

Cuối cùng trong Fragment của bạn:

public class MyFragment extends Fragment implements IOnBackPressed{
   @Override
   public boolean onBackPressed() {
       if (myCondition) {
            //action not popBackStack
            return true; 
        } else {
            return false;
        }
    }
}

GIẢI PHÁP KOTLINE

1 - Tạo giao diện

interface IOnBackPressed {
    fun onBackPressed(): Boolean
}

2 - Chuẩn bị hoạt động của bạn

class MyActivity : AppCompatActivity() {
    override fun onBackPressed() {
        val fragment =
            this.supportFragmentManager.findFragmentById(R.id.main_container)
        (fragment as? IOnBackPressed)?.onBackPressed()?.not()?.let {
            super.onBackPressed()
        }
    }
}

3 - Thực hiện trong đoạn mục tiêu của bạn

class MyFragment : Fragment(), IOnBackPressed {
    override fun onBackPressed(): Boolean {
        return if (myCondition) {
            //action not popBackStack
            true
        } else {
            false
        }
    }
}

1
R.id.main_containergì Đó có phải là ID cho FragmentPager không?
Nathan F.

1
@MaximeJallu trong giải pháp Kotlin, trộn các cuộc gọi an toàn ( .?) và thực thi các lệnh không null ( !!) có thể dẫn đến NPE - diễn viên không phải lúc nào cũng an toàn. Tôi muốn viết if ((fragment as? IOnBackPressed)?.onBackPressed()?.not() == true) { ... }hoặc nhiều hơn kotliney(fragment as? IOnBackPressed)?.onBackPressed()?.not()?.let { ... }
Antek

1
@Antek Xin chào, Cảm ơn bạn đã quay trở lại, bạn có thể đã chỉnh sửa bài đăng để chỉnh sửa. Đừng đánh giá nghiêm túc giải pháp tổng thể.
Maxime Jallu

4
Có nên không .onBackPressed()?.takeIf { !it }?.let{...}? .not()chỉ trả về nghịch đảo.
David Miguel

1
val fragment = this.supportFragmentManager.findFragmentById(R.id.flContainer) as? NavHostFragment val currentFragment = fragment?.childFragmentManager?.fragments?.get(0) as? IOnBackPressed currentFragment?.onBackPressed()?.takeIf { !it }?.let{ super.onBackPressed() }Chương trình này dành cho những người sử dụng Kotlin và NavigationContoder
Pavle Pavlov

97

Theo câu trả lời @HaMMeRed ở đây là mã giả nên hoạt động như thế nào. Hãy nói rằng hoạt động chính của bạn được gọi là BaseActivitycó các đoạn con (như trong ví dụ lib SlidingMothy). Dưới đây là các bước:

Đầu tiên chúng ta cần tạo giao diện và lớp thực hiện giao diện của nó để có phương thức chung

  1. Tạo giao diện lớp OnBackPressedListener

    public interface OnBackPressedListener {
        public void doBack();
    }
  2. Tạo lớp thực hiện các kỹ năng của OnBackPressedListener

    public class BaseBackPressedListener implements OnBackPressedListener {
        private final FragmentActivity activity;
    
        public BaseBackPressedListener(FragmentActivity activity) {
            this.activity = activity;
        }
    
        @Override
        public void doBack() {
            activity.getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        }
    }
  3. Kể từ bây giờ, chúng tôi sẽ làm việc với mã của chúng tôi BaseActivity và các đoạn của nó

  4. Tạo người nghe riêng trên đầu lớp của bạn BaseActivity

    protected OnBackPressedListener onBackPressedListener;
  5. tạo phương thức để thiết lập trình nghe BaseActivity

    public void setOnBackPressedListener(OnBackPressedListener onBackPressedListener) {
        this.onBackPressedListener = onBackPressedListener;
    }
  6. trong ghi đè onBackPressedthực hiện một cái gì đó như thế

    @Override
    public void onBackPressed() {
        if (onBackPressedListener != null)
            onBackPressedListener.doBack();
        else
            super.onBackPressed();
  7. trong đoạn của bạn trong onCreateViewbạn nên thêm người nghe của chúng tôi

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        activity = getActivity();
    
        ((BaseActivity)activity).setOnBackPressedListener(new BaseBackPressedListener(activity));
    
        View view = ... ;
    //stuff with view
    
        return view;
    }

Voila, bây giờ khi bạn nhấp lại vào đoạn, bạn nên bắt tùy chỉnh phương thức quay lại.


Nếu có nhiều hơn một đoạn là một người nghe, làm thế nào để bạn xác định, trong onBackPressed()đó, đoạn nào đã được hiển thị khi nhấn nút quay lại?
Al Lelopath

2
bạn có thể tùy chỉnh trình nghe nhấp thay vì baseBackPressionListener mới và gọi đó là ẩn danh để thiết lập hành vi của riêng mình, đại loại như thế này:((BaseActivity)activity).setOnBackPressedListener(new OnBackpressedListener(){ public void doBack() { //...your stuff here }});
cá chết

Cảm ơn, mặc dù tôi đã thay đổi vị trí của mình về vấn đề này, tôi khuyên bạn nên tách rời với các tin nhắn quảng bá địa phương ngay bây giờ. Tôi nghĩ rằng nó sẽ mang lại các thành phần chất lượng cao hơn được phân tách cao.
HaMMeReD


Điều này hoạt động hoàn hảo với tôi, tôi khởi động lại Activity
raphaelbgr

86

Điều này làm việc cho tôi: https://stackoverflow.com/a/27145007/3934111

@Override
public void onResume() {
    super.onResume();

    if(getView() == null){
        return;
    }

    getView().setFocusableInTouchMode(true);
    getView().requestFocus();
    getView().setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {

            if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK){
                // handle back button's click listener
                return true;
            }
            return false;
        }
    });
}

2
Làm việc hoàn hảo! Nhưng sau đó, bạn cũng phải xử lý hành động mặc định, bằng cách viết thêm một điều kiện IF nữa. Kể từ đó, tôi đã hạ xuống slideUpPanel của mình nếu nó được mở rộng. Vì vậy, trước tiên tôi phải đánh dấu một kiểm tra nếu (bảng điều khiển đã hết) sau đó .....
sud007

18
Vấn đề với giải pháp này là getView () chỉ trả về khung nhìn chính (chứ không phải là khung nhìn phụ, nếu chúng có tiêu điểm). Vì vậy, nếu bạn có EditText và nhập một số văn bản sau đó nhấn "back" một lần để ẩn bàn phím, lần nhấn thứ hai vào "back" được bắt đầu từ "EditText" (chính chế độ xem) và phương thức này dường như không bắt được " quay lại nút "sau đó nhấn (vì nó chỉ bắt cho chế độ xem chính). Theo tôi hiểu, bạn có thể bắt gặp các sự kiện nhấn phím trên "Chỉnh sửa" cũng như bất kỳ chế độ xem nào khác, nhưng nếu bạn có dạng "phức tạp" thì điều này không sạch sẽ và có thể duy trì được như có thể.
Pelpotronic

Đây là một giải pháp đơn giản tuyệt vời cho cơn ác mộng là những mảnh vỡ. Nếu bạn có một hình thức edittext 'phức tạp' thì có lẽ bạn nên xóa dự án của mình và bắt đầu lại. Trả về false khi bạn muốn sử dụng hành vi tiêu chuẩn. Trả lại đúng khi bạn đã chặn báo chí phía sau và đã làm điều gì đó với nó
xử vào

65

Nếu bạn muốn loại chức năng đó, bạn sẽ cần ghi đè nó trong hoạt động của mình và sau đó thêm YourBackPressedgiao diện cho tất cả các đoạn mà bạn gọi trên đoạn có liên quan bất cứ khi nào nhấn nút quay lại.

Chỉnh sửa: Tôi muốn nối thêm câu trả lời trước của tôi.

Nếu tôi làm điều này ngày hôm nay, tôi sẽ sử dụng một chương trình phát sóng, hoặc có thể là một chương trình phát sóng được đặt hàng nếu tôi mong muốn các bảng khác sẽ cập nhật đồng nhất với bảng nội dung chính / chính.

LocalBroadcastManagertrong Thư viện hỗ trợ có thể giúp với điều này và bạn chỉ cần gửi chương trình phát sóng onBackPressedvà đăng ký vào các đoạn mà bạn quan tâm. Tôi nghĩ rằng Nhắn tin là một triển khai tách rời hơn và sẽ mở rộng tốt hơn, vì vậy đây sẽ là đề xuất triển khai chính thức của tôi ngay bây giờ. Chỉ cần sử dụng Intenthành động của bộ lọc làm thông báo cho tin nhắn của bạn. gửi mới được tạo của bạn ACTION_BACK_PRESSED, gửi nó từ hoạt động của bạn và lắng nghe nó trong các mảnh có liên quan.


4
Ý tưởng khá tiện lợi! Một số suy nghĩ bổ sung: + điều quan trọng là phải nhận ra logic này với việc phát đi từ hoạt động> các đoạn (không phải theo cách khác). OnBackPression chỉ khả dụng ở cấp độ hoạt động, do đó, đặt bẫy sự kiện ở đó và phát nó tới tất cả các đoạn "nghe" là chìa khóa ở đây. + Sử dụng EventBus (như Otto của Square) khiến việc triển khai cho một trường hợp như thế này trở nên tầm thường!
Kaushik Gopal

2
Tôi chưa sử dụng EventBus của Square, nhưng tôi sẽ có các sản phẩm trong tương lai. Tôi nghĩ rằng đó là một giải pháp java thuần túy tốt hơn và nếu bạn có thể loại bỏ các phần Android trong kiến ​​trúc của mình, thì tốt hơn hết.
HaMMeReD

Làm thế nào để bạn chỉ để các mảnh hàng đầu để xử lý sự kiện? ví dụ: bạn muốn phá hủy đoạn hiện đang hiển thị
eugene

Máy thu phát sóng phải được gắn với vòng đời nếu bạn đang sử dụng nó, vì vậy khi không nhìn thấy được thì không nên nhận sự kiện.
HaMMeReD

Cũng lưu ý rằng LocalBroadcastManagerkhông thể phát sóng theo thứ tự
Xiao

46

Không có cái nào dễ thực hiện và cũng không hoạt động theo cách tối ưu.

Các đoạn có một phương thức gọi onDetach sẽ thực hiện công việc.

@Override
    public void onDetach() {
        super.onDetach();
        PUT YOUR CODE HERE
    }

ĐIỀU NÀY S DO LÀM VIỆC.


20
nhưng vấn đề ở đây là bạn không biết liệu mảnh vỡ đó có bị tách ra do ấn vào lưng hay do một số hành động khác, mà người dùng đã dẫn đến một số hoạt động hoặc đoạn khác.
Nắng

7
onDetach () được gọi khi đoạn không còn gắn liền với hoạt động của nó. Điều này được gọi sau onDestroy (). Mặc dù bạn sẽ nhận được mã này khi bạn nhấn nút quay lại, bạn cũng sẽ nhận được mã này khi bạn thay đổi từ mảnh này sang mảnh khác. Bạn có thể làm một cái gì đó tiện lợi để làm cho công việc này mặc dù ...
apmartin1991

Hoạt động tốt cho một DialogFragment.
CoolMind 11/05/2016

2
Đừng quên thêm isRemoving()như được mô tả trong stackoverflow.com/a/27103891/2914140 .
CoolMind

1
cái này sai. Nó có thể được gọi vì nhiều lý do
Bali

40

Nếu bạn đang sử dụng androidx.appcompat:appcompat:1.1.0hoặc ở trên thì bạn có thể thêm một OnBackPressedCallbackđoạn vào như sau

requireActivity()
    .onBackPressedDispatcher
    .addCallback(this, object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            Log.d(TAG, "Fragment back pressed invoked")
            // Do custom work here    

            // if you want onBackPressed() to be called as normal afterwards
            if (isEnabled) {
                isEnabled = false
                requireActivity().onBackPressed()
            }
        }
    }
)

Xem https://developer.android.com/guide/navlation/navlation-custom-back


Nếu bạn đang sử dụng androidx-core-ktx, bạn có thể sử dụngrequireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { /* code to be executed when back is pressed */ }
Tối đa

Điều quan trọng cần lưu ý là LifecycleOwnerparam nên được thêm vào như trong ví dụ này. Không có nó, bất kỳ mảnh vỡ nào bắt đầu sau đó sẽ gọi handleBackPressed()nếu nhấn nút quay lại.
Eric B.

phương pháp này phải với thư viện điều hướng?
Im lặng

25

Chỉ cần thêm addToBackStacktrong khi bạn đang chuyển đổi giữa các mảnh của bạn như dưới đây:

fragmentManager.beginTransaction().replace(R.id.content_frame,fragment).addToBackStack("tag").commit();

nếu bạn viết addToBackStack(null), nó sẽ tự xử lý nhưng nếu bạn đưa thẻ, bạn nên xử lý thủ công.


Có bất cứ điều gì khác cần phải được thực hiện hoặc thêm phương thức addToBackStack là đủ?
Sreecharan Desabattula

1
nếu bạn chỉ thêm rằng nó sẽ hoạt động, nếu nó vẫn không hoạt động thì sẽ có một cái gì đó trong mã của bạn. Để lại mã ở đâu đó để xem vui lòng @SreecharanDesabattula
Rudi


20

vì câu hỏi này và một số câu trả lời đã hơn năm tuổi, hãy để tôi chia sẻ giải pháp của mình. Đây là phần tiếp theo và hiện đại hóa cho câu trả lời từ @oyenigun

CẬP NHẬT: Ở cuối bài viết này, tôi đã thêm một triển khai thay thế bằng cách sử dụng phần mở rộng Fragment trừu tượng không liên quan đến Activity, điều này sẽ hữu ích cho bất kỳ ai có hệ thống phân cấp phân đoạn phức tạp hơn liên quan đến các đoạn lồng nhau yêu cầu hành vi ngược khác nhau.

Tôi cần phải thực hiện điều này vì một số đoạn tôi sử dụng có chế độ xem nhỏ hơn mà tôi muốn loại bỏ bằng nút quay lại, chẳng hạn như chế độ xem thông tin nhỏ bật lên, v.v., nhưng điều này tốt cho bất kỳ ai cần ghi đè hành vi của nút quay lại bên trong các mảnh.

Đầu tiên, xác định một Giao diện

public interface Backable {
    boolean onBackPressed();
}

Giao diện này, mà tôi gọi Backable(tôi là người gắn bó để đặt tên cho các quy ước), có một phương thức duy nhất onBackPressed()phải trả về một booleangiá trị. Chúng ta cần thực thi một giá trị boolean bởi vì chúng ta sẽ cần phải biết nếu nhấn nút quay lại đã "hấp thụ" sự kiện trở lại. Trở về truenghĩa là nó có, và không cần thêm hành động nào, nếu không, falsenói rằng hành động quay lại mặc định vẫn phải diễn ra. Giao diện này phải là tệp riêng của nó (tốt nhất là trong một gói riêng có têninterfaces ). Hãy nhớ rằng, tách các lớp học của bạn thành các gói là thực hành tốt.

Thứ hai, tìm đoạn đầu

Tôi đã tạo một phương thức trả về Fragmentđối tượng cuối cùng trong ngăn xếp phía sau. Tôi sử dụng thẻ ... nếu bạn sử dụng ID, hãy thực hiện các thay đổi cần thiết. Tôi có phương thức tĩnh này trong một lớp tiện ích liên quan đến các trạng thái điều hướng, v.v ... nhưng tất nhiên, đặt nó ở nơi phù hợp nhất với bạn. Để chỉnh sửa, tôi đã đặt tôi vào một lớp được gọi NavUtils.

public static Fragment getCurrentFragment(Activity activity) {
    FragmentManager fragmentManager = activity.getFragmentManager();
    if (fragmentManager.getBackStackEntryCount() > 0) {
        String lastFragmentName = fragmentManager.getBackStackEntryAt(
                fragmentManager.getBackStackEntryCount() - 1).getName();
        return fragmentManager.findFragmentByTag(lastFragmentName);
    }
    return null;
}

Hãy chắc chắn rằng số lượng ngăn xếp trở lại lớn hơn 0, nếu không ArrayOutOfBoundsExceptioncó thể được ném vào thời gian chạy. Nếu nó không lớn hơn 0, trả về null. Chúng tôi sẽ kiểm tra giá trị null sau ...

Thứ ba, thực hiện trong một mảnh

Thực hiện Backablegiao diện trong bất kỳ đoạn nào mà bạn cần ghi đè hành vi của nút quay lại. Thêm phương thức thực hiện.

public class SomeFragment extends Fragment implements 
        FragmentManager.OnBackStackChangedListener, Backable {

...

    @Override
    public boolean onBackPressed() {

        // Logic here...
        if (backButtonShouldNotGoBack) {
            whateverMethodYouNeed();
            return true;
        }
        return false;
    }

}

Trong phần onBackPressed()ghi đè, đặt bất kỳ logic nào bạn cần. Nếu bạn muốn nút quay lại không bật ngăn xếp phía sau (hành vi mặc định), hãy trả về true , rằng sự kiện trở lại của bạn đã được hấp thụ. Nếu không, trả lại sai.

Cuối cùng, trong Hoạt động của bạn ...

Ghi đè onBackPressed()phương thức và thêm logic này vào nó:

@Override
public void onBackPressed() {

    // Get the current fragment using the method from the second step above...
    Fragment currentFragment = NavUtils.getCurrentFragment(this);

    // Determine whether or not this fragment implements Backable
    // Do a null check just to be safe
    if (currentFragment != null && currentFragment instanceof Backable) {

        if (((Backable) currentFragment).onBackPressed()) {
            // If the onBackPressed override in your fragment 
            // did absorb the back event (returned true), return
            return;
        } else {
            // Otherwise, call the super method for the default behavior
            super.onBackPressed();
        }
    }

    // Any other logic needed...
    // call super method to be sure the back button does its thing...
    super.onBackPressed();
}

Chúng tôi nhận được đoạn hiện tại trong ngăn xếp phía sau, sau đó chúng tôi kiểm tra null và xác định xem nó có thực hiện Backablegiao diện của chúng tôi không . Nếu có, xác định xem sự kiện đã được hấp thụ. Nếu vậy, chúng tôi đã hoàn thành onBackPressed()và có thể trở lại. Nếu không, hãy coi nó như một ấn trở lại bình thường và gọi siêu phương thức.

Tùy chọn thứ hai để không liên quan đến Hoạt động

Đôi khi, bạn không muốn Activity xử lý việc này chút nào và bạn cần xử lý trực tiếp trong phân đoạn. Nhưng ai nói bạn không thể có Mảnh vỡ với API nhấn? Chỉ cần mở rộng đoạn của bạn đến một lớp mới.

Tạo một lớp trừu tượng mở rộng Fragment và thực hiện View.OnKeyListnergiao diện ...

import android.app.Fragment;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;

public abstract class BackableFragment extends Fragment implements View.OnKeyListener {

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        view.setFocusableInTouchMode(true);
        view.requestFocus();
        view.setOnKeyListener(this);
    }

    @Override
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        if (event.getAction() == KeyEvent.ACTION_UP) {
            if (keyCode == KeyEvent.KEYCODE_BACK) {
                onBackButtonPressed();
                return true;
            }
        }

        return false;
    }

    public abstract void onBackButtonPressed();
}

Như bạn có thể thấy, bất kỳ đoạn nào mở rộng BackableFragmentsẽ tự động thu lại các lần nhấp bằng View.OnKeyListenergiao diện. Chỉ cần gọi onBackButtonPressed()phương thức trừu tượng từ bên trong onKey()phương thức đã triển khai bằng cách sử dụng logic tiêu chuẩn để phân biệt nhấn nút quay lại. Nếu bạn cần đăng ký các lần bấm phím khác với nút quay lại, chỉ cần đảm bảo gọi superphương thức khi ghi đè onKey()vào đoạn của bạn, nếu không bạn sẽ ghi đè hành vi trong phần trừu tượng.

Sử dụng đơn giản, chỉ cần mở rộng và thực hiện:

public class FragmentChannels extends BackableFragment {

    ...

    @Override
    public void onBackButtonPressed() {
        if (doTheThingRequiringBackButtonOverride) {
            // do the thing
        } else {
            getActivity().onBackPressed();
        }
    }

    ...
}

onBackButtonPressed()phương thức trong siêu lớp là trừu tượng, nên một khi bạn mở rộng, bạn phải thực hiện onBackButtonPressed(). Nó trả về voidvì nó chỉ cần thực hiện một hành động trong lớp phân đoạn và không cần chuyển tiếp sự hấp thụ của báo chí trở lại Hoạt động. Đảm bảo bạn gọi onBackPressed()phương thức Activity nếu bất cứ điều gì bạn đang làm với nút quay lại không yêu cầu xử lý, nếu không, nút quay lại sẽ bị tắt ... và bạn không muốn điều đó!

Hãy cẩn thận Như bạn có thể thấy, điều này đặt người nghe chính vào chế độ xem gốc của đoạn và chúng ta sẽ cần phải tập trung vào nó. Nếu có các văn bản chỉnh sửa liên quan (hoặc bất kỳ chế độ xem lấy nét tập trung nào khác) trong đoạn của bạn mở rộng lớp này, (hoặc các đoạn hoặc khung nhìn bên trong khác có cùng), bạn sẽ cần xử lý riêng điều đó. Có một bài viết hay về việc mở rộng EditText để mất tập trung vào báo chí trở lại.

Tôi hi vọng ai đó thấy nó hữu ích. Chúc mừng mã hóa.


Cảm ơn, đó là một giải pháp tốt đẹp. Bạn có thể nói, tại sao bạn gọi super.onBackPressed();hai lần?
CoolMind

Nó chỉ được gọi một lần cho mỗi kịch bản liên quan đến trạng thái null currentFragment. Nếu đoạn không phải là null và đoạn đó thực hiện Backablegiao diện, không có hành động nào được thực hiện. Nếu đoạn không phải là null và nó KHÔNG thực hiện Backable, chúng ta gọi siêu phương thức. Nếu đoạn này là null, nó sẽ bỏ qua cuộc gọi siêu cuối cùng.
mwieczorek

Xin lỗi, tôi không hiểu. Trong trường hợp khi a currentFragmentkhông null và là một thể hiện của Backable và nó bị tách ra (chúng tôi nhấn backnút và đóng đoạn) thì lần xuất hiện đầu tiên super.onBackPressed();được gọi, sau đó là lần thứ hai.
CoolMind

Giải pháp tuyệt vời
David Toledo

Có thể "Closable" nghe tốt hơn một chút so với "Backable"
pumnao

15

Giải pháp rất đơn giản:

1) Nếu bạn có một lớp phân đoạn cơ sở mà tất cả các phân đoạn mở rộng, thì hãy thêm mã này vào lớp của nó, nếu không hãy tạo một lớp phân đoạn cơ sở như vậy

 /* 
 * called when on back pressed to the current fragment that is returned
 */
 public void onBackPressed()
 {
    // add code in super class when override
 }

2) Trong lớp Activity của bạn, ghi đè onBackPression như sau:

private BaseFragment _currentFragment;

@Override
public void onBackPressed()
{
      super.onBackPressed();
     _currentFragment.onBackPressed();
}

3) Trong lớp Fragment của bạn, thêm mã mong muốn của bạn:

 @Override
 public void onBackPressed()
 {
    setUpTitle();
 }


9

onBackPressed() khiến Fragment bị tách khỏi Activity.

Theo câu trả lời của @Sterling Diaz tôi nghĩ anh ấy đúng. NHƯNG một số tình huống sẽ sai. (ví dụ: Màn hình xoay)

Vì vậy, tôi nghĩ rằng chúng ta có thể phát hiện liệu có isRemoving()đạt được mục tiêu hay không.

Bạn có thể viết nó tại onDetach()hoặc onDestroyView(). Nó là công việc.

@Override
public void onDetach() {
    super.onDetach();
    if(isRemoving()){
        // onBackPressed()
    }
}

@Override
public void onDestroyView() {
    super.onDestroyView();
    if(isRemoving()){
        // onBackPressed()
    }
}

8

Vâng, tôi đã làm nó như thế này, và nó làm việc cho tôi

Giao diện đơn giản

FragmentOnBackClickInterface.java

public interface FragmentOnBackClickInterface {
    void onClick();
}

Ví dụ thực hiện

MyFragment.java

public class MyFragment extends Fragment implements FragmentOnBackClickInterface {

// other stuff

public void onClick() {
       // what you want to call onBackPressed?
}

sau đó chỉ cần ghi đè onBackPress trong hoạt động

    @Override
public void onBackPressed() {
    int count = getSupportFragmentManager().getBackStackEntryCount();
    List<Fragment> frags = getSupportFragmentManager().getFragments();
    Fragment lastFrag = getLastNotNull(frags);
    //nothing else in back stack || nothing in back stack is instance of our interface
    if (count == 0 || !(lastFrag instanceof FragmentOnBackClickInterface)) {
        super.onBackPressed();
    } else {
        ((FragmentOnBackClickInterface) lastFrag).onClick();
    }
}

private Fragment getLastNotNull(List<Fragment> list){
    for (int i= list.size()-1;i>=0;i--){
        Fragment frag = list.get(i);
        if (frag != null){
            return frag;
        }
    }
    return null;
}

Không làm việc ! super.onbackpressed () luôn được gọi :(
Animesh Mangla

Làm thế nào để vượt qua sự kiện Nội tâm này. Tôi có ViewPager trong Activity và ViewPager có Fragment, Bây giờ tất cả những Fragment đó đều có đoạn con. Làm thế nào để vượt qua sự kiện nút trở lại cho các mảnh con đó.
Kishan Vaghela

@KishanVaghela Tôi đã không chạm vào Android kể từ đó nhưng tôi có một ý tưởng. Hãy cho tôi ngày 24 để cập nhật câu trả lời của tôi.
Błażej

Không có vấn đề gì, Một lựa chọn là tôi cần truyền sự kiện người nghe đến từng đoạn con từ đoạn cha mẹ. nhưng đó không phải là một cách lý tưởng bởi vì tôi cần phải làm điều này cho mọi mảnh vỡ.
Kishan Vaghela

@KishanVaghela Tôi đã suy nghĩ về người quản lý trong hoạt động. Bạn có thể thêm / xóa các đoạn với giao diện đó. Nhưng với cách tiếp cận này, bạn phải triển khai trình quản lý trong mọi phân đoạn có các phần con. Vì vậy, có lẽ giải pháp tốt hơn sẽ tạo người quản lý như singleton. Sau đó, bạn sẽ phải thêm / xóa các đoạn / đối tượng và trong 'onBackPression', bạn gọi phương thức 'onBackPression' của trình quản lý này. Phương thức đó sẽ gọi phương thức 'onClick' trong mọi đối tượng được thêm vào trình quản lý. Điều đó ít nhiều rõ ràng với bạn?
Błażej

8

Bạn nên thêm giao diện cho dự án của bạn như dưới đây;

public interface OnBackPressed {

     void onBackPressed();
}

Và sau đó, bạn nên thực hiện giao diện này trên đoạn của bạn;

public class SampleFragment extends Fragment implements OnBackPressed {

    @Override
    public void onBackPressed() {
        //on Back Pressed
    }

}

Và bạn có thể kích hoạt sự kiện onBackPression này trong các hoạt động của bạn trên sự kiện onBackPression như bên dưới;

public class MainActivity extends AppCompatActivity {
       @Override
        public void onBackPressed() {
                Fragment currentFragment = getSupportFragmentManager().getFragments().get(getSupportFragmentManager().getBackStackEntryCount() - 1);
                if (currentFragment instanceof OnBackPressed) {  
                    ((OnBackPressed) currentFragment).onBackPressed();
                }
                super.onBackPressed();
        }
}

Đó là phương pháp agood, nhưng hãy cẩn thận, đừng gọi getActivity().onBackPressed();vì nó sẽ gọi ngoại lệ: "java.lang.StackOverflowError: stack size 8MB".
CoolMind

8

Đây chỉ là một mã nhỏ sẽ thực hiện thủ thuật:

 getActivity().onBackPressed();

Hy vọng nó sẽ giúp được ai đó :)


4
Ông yêu cầu ghi đè hành vi, không chỉ đơn giản là gọi phương thức hoạt động.
mwieczorek

2
Tôi đã đọc nó một cách cẩn thận, cảm ơn. Giải pháp của bạn chỉ đơn giản là chuyển phương thức onBackPression trở lại Hoạt động, khi anh ta hỏi cách ghi đè.
mwieczorek

7

Nếu bạn sử dụng EventBus, có lẽ đây là một giải pháp đơn giản hơn nhiều:

Trong đoạn của bạn:

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    EventBus.getDefault().register(this);
}

@Override
public void onDetach() {
    super.onDetach();
    EventBus.getDefault().unregister(this);
}


// This method will be called when a MessageEvent is posted
public void onEvent(BackPressedMessage type){
    getSupportFragmentManager().popBackStack();
}

và trong lớp Activity của bạn, bạn có thể định nghĩa:

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

@Override
public void onStop() {
    EventBus.getDefault().unregister(this);
    super.onStop();
}

// This method will be called when a MessageEvent is posted
public void onEvent(BackPressedMessage type){
    super.onBackPressed();
}

@Override
public void onBackPressed() {
    EventBus.getDefault().post(new BackPressedMessage(true));
}

BackPressionMessage.java chỉ là một đối tượng POJO

Điều này là siêu sạch và không có rắc rối giao diện / thực hiện.


7

đây là giải pháp của tôi:

trong MyActivity.java:

public interface OnBackClickListener {
        boolean onBackClick();
    }

    private OnBackClickListener onBackClickListener;

public void setOnBackClickListener(OnBackClickListener onBackClickListener) {
        this.onBackClickListener = onBackClickListener;
    }

@Override
    public void onBackPressed() {
        if (onBackClickListener != null && onBackClickListener.onBackClick()) {
            return;
        }
        super.onBackPressed();
    }

và trong Fragment:

((MyActivity) getActivity()).setOnBackClickListener(new MyActivity.OnBackClickListener() {
    @Override
    public boolean onBackClick() {
        if (condition) {
            return false;
        }

        // some codes

        return true;
    }
});

5

Sử dụng thành phần Điều hướng, bạn có thể làm như thế này :

Java

public class MyFragment extends Fragment {

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

    // This callback will only be called when MyFragment is at least Started.
    OnBackPressedCallback callback = new OnBackPressedCallback(true /* enabled by default */) {
        @Override
        public void handleOnBackPressed() {
            // Handle the back button event
        }
    });
    requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);

    // The callback can be enabled or disabled here or in handleOnBackPressed()
}
...
}

Kotlin

class MyFragment : Fragment() {

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // This callback will only be called when MyFragment is at least Started.
    val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
        // Handle the back button event
    }

    // The callback can be enabled or disabled here or in the lambda
}
...
}

4

Làm thế nào về việc sử dụng onDestroyView ()?

@Override
public void onDestroyView() {
    super.onDestroyView();
}

5
Điều gì về việc đi đến một đoạn khác từ đoạn thực tế, điều gì sẽ xảy ra khi sử dụng phương pháp của bạn? :)
Choletski

Câu trả lời vô dụng nhất. Nó giống như viết i = ihoặc if (1 < 0) {}.
CoolMind

4
@Override
public void onResume() {
    super.onResume();

    getView().setFocusableInTouchMode(true);
    getView().requestFocus();
    getView().setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
                // handle back button
                replaceFragmentToBackStack(getActivity(), WelcomeFragment.newInstance(bundle), tags);

                return true;
            }

            return false;
        }
    });
}

Chỉ khi các đoạn không có chế độ xem có thể tập trung (chẳng hạn như chỉnh sửa văn bản), hãy để các đoạn đó tự xử lý nút quay lại như câu trả lời này. Mặt khác, hãy để hoạt động chứa các đoạn thực hiện bằng OnBackPress () khi câu trả lời đầu tiên được viết; vì chế độ xem có thể lấy nét sẽ đánh cắp trọng tâm của đoạn. Tuy nhiên, chúng ta có thể lấy lại tiêu điểm cho phân đoạn bằng cách lấy nét rõ ràng tất cả các chế độ xem có thể lấy nét bằng phương pháp clearF Focus () và lấy nét lại thành phân đoạn.
Nguyễn Tấn Đạt

4
public class MyActivity extends Activity {

    protected OnBackPressedListener onBackPressedListener;

    public interface OnBackPressedListener {
        void doBack();
    }

    public void setOnBackPressedListener(OnBackPressedListener onBackPressedListener) {
        this.onBackPressedListener = onBackPressedListener;
    }

    @Override
    public void onBackPressed() {
        if (onBackPressedListener != null)
            onBackPressedListener.doBack();
        else
            super.onBackPressed();
    } 

    @Override
    protected void onDestroy() {
        onBackPressedListener = null;
        super.onDestroy();
    }
}

trong đoạn của bạn thêm vào phần sau, đừng quên thực hiện giao diện của tính chính.

public class MyFragment extends Framgent implements MyActivity.OnBackPressedListener {
    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
         ((MyActivity) getActivity()).setOnBackPressedListener(this);
    }

@Override
public void doBack() {
    //BackPressed in activity will call this;
}

}

4

Trong giải pháp của tôi (Kotlin);

Tôi đang sử dụng chức năng onBackAlternative làm tham số trên BaseActivity .

Cơ sở

abstract class BaseActivity {

    var onBackPressAlternative: (() -> Unit)? = null

    override fun onBackPressed() {
        if (onBackPressAlternative != null) {
            onBackPressAlternative!!()
        } else {
            super.onBackPressed()
        }
    }
}

Tôi có một chức năng để thiết lập onBackPressAlternative trên BaseFragment .

BaseFragment

abstract class BaseFragment {

     override fun onStart() {
        super.onStart()
        ...
        setOnBackPressed(null) // Add this
     }

      //Method must be declared as open, for overriding in child class
     open fun setOnBackPressed(onBackAlternative: (() -> Unit)?) {
         (activity as BaseActivity<*, *>).onBackPressAlternative = onBackAlternative
     }
}

Sau đó, onBackPressAlternative của tôi đã sẵn sàng để sử dụng trên các đoạn.

Mảnh vỡ phụ

override fun setOnBackPressed(onBackAlternative: (() -> Unit)?) {
    (activity as BaseActivity<*, *>).onBackPressAlternative = {
        // TODO Your own custom onback function 
    }
}

3

Không thực hiện phương thức ft.addToBackStack () để khi bạn nhấn nút quay lại, hoạt động của bạn sẽ kết thúc.

proAddAccount = new ProfileAddAccount();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, proAddAccount);
//fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();


3

Chỉ cần làm theo các bước sau:

Luôn luôn trong khi thêm một đoạn,

fragmentTransaction.add(R.id.fragment_container, detail_fragment, "Fragment_tag").addToBackStack(null).commit();

Sau đó, trong hoạt động chính, ghi đè onBackPressed()

if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
    getSupportFragmentManager().popBackStack();
} else {
    finish();
}

Để xử lý nút quay lại trong ứng dụng của bạn,

Fragment f = getActivity().getSupportFragmentManager().findFragmentByTag("Fragment_tag");
if (f instanceof FragmentName) {
    if (f != null) 
        getActivity().getSupportFragmentManager().beginTransaction().remove(f).commit()               
}

Đó là nó!


3

Trong vòng đời hoạt động, luôn có nút quay lại Android xử lý các giao dịch FragmentManager khi chúng tôi sử dụng FragmentActivity hoặc AppCompatActivity.

Để xử lý backstack, chúng ta không cần phải xử lý số backstack của nó hoặc gắn thẻ bất cứ thứ gì nhưng chúng ta nên tập trung trong khi thêm hoặc thay thế một đoạn. Vui lòng tìm các đoạn mã sau để xử lý các trường hợp nút quay lại,

    public void replaceFragment(Fragment fragment) {

        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        if (!(fragment instanceof HomeFragment)) {
            transaction.addToBackStack(null);
        }
        transaction.replace(R.id.activity_menu_fragment_container, fragment).commit();
    }

Ở đây, tôi sẽ không thêm ngăn xếp trở lại cho đoạn nhà của mình vì đó là trang chủ của ứng dụng của tôi. Nếu thêm addToBackStack vào HomeFragment thì ứng dụng sẽ chờ để loại bỏ tất cả các lỗi trong tính nhạy cảm, sau đó chúng tôi sẽ có màn hình trống để tôi giữ điều kiện sau,

if (!(fragment instanceof HomeFragment)) {
            transaction.addToBackStack(null);
}

Bây giờ, bạn có thể thấy đoạn được thêm trước đó trên acitvity và ứng dụng sẽ thoát khi đến HomeFragment. bạn cũng có thể xem các đoạn sau.

@Override
public void onBackPressed() {

    if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
        closeDrawer();
    } else {
        super.onBackPressed();
    }
}

3

Fragment: Tạo một BaseFragment đặt một phương thức:

 public boolean onBackPressed();

Hoạt động:

@Override
public void onBackPressed() {
    List<Fragment> fragments = getSupportFragmentManager().getFragments();
    if (fragments != null) {
        for (Fragment fragment : fragments) {
            if (!fragment.isVisible()) continue;

            if (fragment instanceof BaseFragment && ((BaseFragment) fragment).onBackPressed()) {
                return;
            }
        }
    }

    super.onBackPressed();
}

Hoạt động của bạn sẽ chạy trên các mảnh được đính kèm và có thể nhìn thấy và gọi onBackPression () trên mỗi một trong số chúng và hủy bỏ nếu một trong số chúng trả về 'true' (nghĩa là nó đã được xử lý, do đó không có hành động nào nữa).


Thưa ông, đây là cách thanh lịch nhất và hoạt động hoàn hảo!
asozcan

3

Theo ghi chú phát hành AndroidX , androidx.activity 1.0.0-alpha01được phát hành và giới thiệu ComponentActivity, một lớp cơ sở mới của hiện có FragmentActivityAppCompatActivity. Và phiên bản này mang đến cho chúng ta một tính năng mới:

Bây giờ bạn có thể đăng ký OnBackPressedCallbackthông qua addOnBackPressedCallbackđể nhận onBackPressed()cuộc gọi lại mà không cần ghi đè phương thức trong hoạt động của mình.


Bạn có thể cho thấy một ví dụ về điều này? Tôi không nhận được phương pháp đó để giải quyết.
Bryan Bryce

1
@BryanBryce Tôi không tìm thấy ví dụ nào ngoài tài liệu chính thức: developer.android.com/reference/androidx/activity/
trộm

Phương thức sẽ không giải quyết b / c AppCompatActivity thực sự đang mở rộng từ androidx.core.app.ComponentActivity thay vì androidx.activity.ComponentActivity.
wooldridgetm

3

Bạn có thể đăng ký đoạn trong hoạt động để xử lý nhấn lại:

interface BackPressRegistrar {
    fun registerHandler(handler: BackPressHandler)
    fun unregisterHandler(handler: BackPressHandler)
}

interface BackPressHandler {
    fun onBackPressed(): Boolean
}

sử dụng:

Trong đoạn:

private val backPressHandler = object : BackPressHandler {
    override fun onBackPressed(): Boolean {
        showClosingWarning()
        return false
    }
}

override fun onResume() {
    super.onResume()
    (activity as? BackPressRegistrar)?.registerHandler(backPressHandler)
}

override fun onStop() {
    (activity as? BackPressRegistrar)?.unregisterHandler(backPressHandler)
    super.onStop()
}

Trong hoạt động:

class MainActivity : AppCompatActivity(), BackPressRegistrar {


    private var registeredHandler: BackPressHandler? = null
    override fun registerHandler(handler: BackPressHandler) { registeredHandler = handler }
    override fun unregisterHandler(handler: BackPressHandler) { registeredHandler = null }

    override fun onBackPressed() {
        if (registeredHandler?.onBackPressed() != false) super.onBackPressed()
    }
}

3

Việc cung cấp điều hướng trở lại tùy chỉnh bằng cách xử lý onBackPression giờ đây dễ dàng hơn với các cuộc gọi lại bên trong đoạn.

class MyFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val onBackPressedCallback = object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                if (true == conditionForCustomAction) {
                    myCustomActionHere()
                } else  NavHostFragment.findNavController(this@MyFragment).navigateUp();    
        }
    }
    requireActivity().onBackPressedDispatcher.addCallback(
        this, onBackPressedCallback
    )
    ...
}

Nếu bạn muốn hành động quay lại mặc định dựa trên một số điều kiện, bạn có thể sử dụng:

 NavHostFragment.findNavController(this@MyFragment).navigateUp();

3

Bên trong phương thức onCreate của đoạn thêm vào như sau:

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

    OnBackPressedCallback callback = new OnBackPressedCallback(true) {
        @Override
        public void handleOnBackPressed() {
            //Handle the back pressed
        }
    };
    requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
}

sau khi thêm công cụ sao lưu myFragment này nhưng hiện tại hoạt động sao lưu không hoạt động
Sunil Chaudhary

2

Giải pháp tốt nhất,

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;

public class BaseActivity extends AppCompatActivity {
    @Override
    public void onBackPressed() {

        FragmentManager fm = getSupportFragmentManager();
        for (Fragment frag : fm.getFragments()) {
            if (frag == null) {
                super.onBackPressed();
                finish();
                return;
            }
            if (frag.isVisible()) {
                FragmentManager childFm = frag.getChildFragmentManager();
                if (childFm.getFragments() == null) {
                    super.onBackPressed();
                    finish();
                    return;
                }
                if (childFm.getBackStackEntryCount() > 0) {
                    childFm.popBackStack();
                    return;
                }
                else {

                    fm.popBackStack();
                    if (fm.getFragments().size() <= 1) {
                        finish();
                    }
                    return;
                }

            }
        }
    }
}
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.