android-fragments - حدثت مشكلات في نظام Android Fragment back stack




back-stack (8)

لدي مشكلة كبيرة في الطريقة التي يبدو بها الجزء الخلفي من شظية الروبوت وستكون ممتنة لأية مساعدة يتم تقديمها.

تخيل أن لديك 3 أجزاء

[1] [2] [3]

أريد أن يتمكن المستخدم من التنقل [1] > [2] > [3] ولكن في طريق العودة (الضغط على زر الرجوع) [3] > [1] .

كما كنت أتصور أن يتم تحقيق ذلك عن طريق عدم استدعاء addToBackStack(..) عند إنشاء المعاملة التي تجلب جزءًا [2] إلى حامل الأجزاء المحدد في XML.

واقع هذا يبدو كما لو أنه إذا لم أكن أرغب في الظهور مرة أخرى عندما يقوم المستخدم بالضغط على الزر مرة أخرى على [3] ، فلا يجب أن أتصل بـ addToBackStack في المعاملة التي تعرض addToBackStack [3] . هذا يبدو غير بديهي تماما (ربما قادم من عالم iOS).

على أي حال إذا كنت أفعل ذلك بهذه الطريقة ، عندما أذهب من [1] > [2] ثم أضغط مرة أخرى للعودة إلى [1] كما هو متوقع.

إذا ذهبت [1] > [2] > [3] ، ثم اضغط للرجوع ، فأنا أقفز مرة أخرى إلى [1] (كما هو متوقع). الآن يحدث السلوك الغريب عندما أحاول وأقفز إلى [2] مرة أخرى من [1] . أولاً وقبل كل شيء يتم عرض [3] لفترة وجيزة قبل ظهور [2] . إذا قمت بالضغط على هذه النقطة [3] ، فسيتم عرضها ، وإذا قمت بالضغط مرة أخرى ، يتم إنهاء التطبيق.

هل يمكن لأي أحد أن يساعدني على فهم ما يجري هنا؟


وهنا ملف تخطيط XML لنشاطي الرئيسي:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          android:orientation="vertical" >

<fragment
        android:id="@+id/headerFragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        class="com.fragment_test.FragmentControls" >
    <!-- Preview: [email protected]/details -->
</fragment>
<FrameLayout
        android:id="@+id/detailFragment"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"

        />



تحديث هذه هي الشفرة التي أستخدمها للبناء من خلال التسلسل الهرمي للملاحة

    Fragment frag;
    FragmentTransaction transaction;


    //Create The first fragment [1], add it to the view, BUT Dont add the transaction to the backstack
    frag = new Fragment1();

    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.commit();

    //Create the second [2] fragment, add it to the view and add the transaction that replaces the first fragment to the backstack
    frag = new Fragment2();

    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.addToBackStack(null);
    transaction.commit();


    //Create third fragment, Dont add this transaction to the backstack, because we dont want to go back to [2] 
    frag = new Fragment3();
    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.commit();


     //END OF SETUP CODE-------------------------
    //NOW:
    //Press back once and then issue the following code:
    frag = new Fragment2();
    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.addToBackStack(null);
    transaction.commit();

    //Now press back again and you end up at fragment [3] not [1]

تشكرات


Answers

بعد ردArvis قررت أن حفر أعمق ولقد كتبت مقالة التكنولوجيا حول هذا هنا: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping-due-to-backstack-nightmare-in-android/

للمطورين الكسالى حولها. يتألف الحل الخاص بي من إضافة المعاملات إلى backstack دائمًا وتنفيذ FragmentManager.popBackStackImmediate() إضافية عند الحاجة (تلقائيًا).

الرمز هو عدد قليل جدًا من أسطر التعليمات البرمجية ، وفي المثال الذي كنت أرغب في تخطيه من C إلى A بدون الرجوع إلى "B" إذا لم يكن المستخدم أكثر تعمقًا في backstack (على سبيل المثال ، من C navigates إلى D).

ومن ثم فإن الشفرة المرفقة ستعمل كما يلي A -> B -> C (back) -> A & A -> B -> C -> D (back) -> C (back) -> B (back) -> A

أين

fm.beginTransaction().replace(R.id.content, new CFragment()).commit()

صدرت من "ب" إلى "ج" كما في السؤال.

حسنا ، حسنا هنا هو الكود :)

public static void performNoBackStackTransaction(FragmentManager fragmentManager, String tag, Fragment fragment) {
  final int newBackStackLength = fragmentManager.getBackStackEntryCount() +1;

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

  fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
      int nowCount = fragmentManager.getBackStackEntryCount();
      if (newBackStackLength != nowCount) {
        // we don't really care if going back or forward. we already performed the logic here.
        fragmentManager.removeOnBackStackChangedListener(this);

        if ( newBackStackLength > nowCount ) { // user pressed back
          fragmentManager.popBackStackImmediate();
        }
      }
    }
  });
}

وأنا أعلم أنه quetion قديم ولكن حصلت على نفس المشكلة وإصلاحه مثل هذا:

أولاً ، أضف Fragment1 إلى BackStack مع اسم (على سبيل المثال "Frag1"):

frag = new Fragment1();

transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.addToBackStack("Frag1");
transaction.commit();

وبعد ذلك ، كلما أردت العودة إلى Fragment1 (حتى بعد إضافة 10 أجزاء فوقها) ، فقط اتصل popBackStackImmediate بالاسم:

getSupportFragmentManager().popBackStackImmediate("Frag1", 0);

آمل أن يساعد شخص ما :)


إذا كنت تعاني من addToBackStack () و popBackStack () ثم استخدم ببساطة

FragmentTransaction ft =getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, new HomeFragment(), "Home");
ft.commit();`

في نشاطك في OnBackPressed () تعرّف على التسعيرة بعلامة ثم نفذ الأشياء الخاصة بك

Fragment home = getSupportFragmentManager().findFragmentByTag("Home");

if (home instanceof HomeFragment && home.isVisible()) {
    // do you stuff
}

لمزيد من المعلومات https://github.com/DattaHujare/NavigationDrawer لا أستخدم أبداً addToBackStack () لمعالجة الجزء.


أظن ، عندما قرأت قصتك أن [3] هو أيضا على الظهر. هذا يفسر لماذا تراه وامض.

الحل هو أن لا تضبط أبداً [3] على المكدس.


شرح: ما الذي يحدث هنا؟

إذا وضعنا في اعتبارنا أن .replace() يساوي .remove().add() التي نعرفها بالوثائق:

استبدال جزء موجود تمت إضافته إلى حاوية. هذا هو في الأساس نفس استدعاء remove(Fragment) لكافة الأجزاء المضافة حالياً التي تمت إضافتها مع نفس containerViewId ثم قم add(int, Fragment, String) بنفس الوسيطات المعطاة هنا.

إذن ما يحدث هو مثل هذا (أقوم بإضافة الأرقام إلى الشريحة لجعلها أكثر وضوحًا):

// transaction.replace(R.id.detailFragment, frag1);
Transaction.remove(null).add(frag1)  // frag1 on view

// transaction.replace(R.id.detailFragment, frag2).addToBackStack(null);
Transaction.remove(frag1).add(frag2).addToBackStack(null)  // frag2 on view

// transaction.replace(R.id.detailFragment, frag3);
Transaction.remove(frag2).add(frag3)  // frag3 on view

(هنا كل الأشياء المضللة تبدأ في الحدوث)

تذكر أن .addToBackStack() يتم حفظ المعاملة فقط وليس الجزء نفسه! حتى الآن لدينا frag3 على تخطيط:

< press back button >
// System pops the back stack and find the following saved back entry to be reversed:
// [Transaction.remove(frag1).add(frag2)]
// so the system makes that transaction backward!!!
// tries to remove frag2 (is not there, so it ignores) and re-add(frag1)
// make notice that system doesn't realise that there's a frag3 and does nothing with it
// so it still there attached to view
Transaction.remove(null).add(frag1) //frag1, frag3 on view (OVERLAPPING)

// transaction.replace(R.id.detailFragment, frag2).addToBackStack(null);
Transaction.remove(frag3).add(frag2).addToBackStack(null)  //frag2 on view

< press back button >
// system makes saved transaction backward
Transaction.remove(frag2).add(frag3) //frag3 on view

< press back button >
// no more entries in BackStack
< app exits >

حل ممكن

جرب تنفيذ FragmentManager.BackStackChangedListener لمشاهدة التغييرات في المكدس الخلفي وتطبيق المنطق الخاص بك في onBackStackChanged() :


أولا وقبل كل شيء بفضلArvis لتفسير افتتاح العين.

أنا أفضل حل مختلف للإجابة المقبولة هنا لهذه المشكلة. أنا لا أحب العبث مع تجاوز السلوك مرة أخرى أي أكثر من الضروري للغاية وعندما حاولت إضافة وإزالة أجزاء لوحدي دون التقديم الخلفي المكدس عند الضغط على الزر الخلفي وجدت نفسي في جزء الجحيم :) إذا كنت. إضافة f2 على f1 عندما تقوم بإزالته لن تطالبك f1 بأي من طرق الاسترجاع مثل onResume ، onStart ، وما يمكن أن يكون مؤسفًا جدًا.

على أي حال هذا هو كيف أفعل ذلك:

حاليا على الشاشة ليست سوى جزء F1.

f1 -> f2

Fragment2 f2 = new Fragment2();
this.getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content,f2).addToBackStack(null).commit();

لا شيء خارج عن المألوف هنا. من في جزء f2 يأخذك هذا الرمز إلى F3 تجزئة.

f2 -> f3

Fragment3 f3 = new Fragment3();
getActivity().getSupportFragmentManager().popBackStack();
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();

لست متأكدًا من قراءة المستندات إذا كان من المفترض أن يعمل هذا ، فهذه طريقة المعاملة المنبثقة يقال إنها غير متزامنة ، وربما تكون أفضل طريقة للاتصال بـ popBackStackImmediate (). ولكن بقدر ما استطيع ان اقول على أجهزتي انها تعمل بلا كلل.

سيكون البديل المذكور:

final FragmentActivity activity = getActivity();
activity.getSupportFragmentManager().popBackStackImmediate();
activity.getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();

هنا سيكون هناك في الواقع موجزة تعود إلى f1 beofre الانتقال إلى f3 ، لذلك هناك خلل طفيف هناك.

هذا هو كل ما عليك فعله ، لا تحتاج إلى تجاوز سلوك الكومة مرة أخرى ...


حق!!! بعد الكثير من سحب الشعر لقد توصلت أخيرا إلى كيفية جعل هذا العمل يعمل بشكل صحيح.

يبدو كما لو أن جزء [3] لا يتم إزالته من العرض عند الضغط على ظهره لذا عليك القيام به يدويا!

أولا وقبل كل شيء ، لا تستخدم استبدال () ولكن بدلا من ذلك استخدام إزالة وإضافة بشكل منفصل. يبدو كما لو أن الاستبدال () لا يعمل بشكل صحيح.

الجزء التالي من هذا هو تجاوز أسلوب onKeyDown وإزالة الجزء الحالي في كل مرة يتم فيها الضغط على زر الرجوع.

@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
    if (keyCode == KeyEvent.KEYCODE_BACK)
    {
        if (getSupportFragmentManager().getBackStackEntryCount() == 0)
        {
            this.finish();
            return false;
        }
        else
        {
            getSupportFragmentManager().popBackStack();
            removeCurrentFragment();

            return false;
        }



    }

    return super.onKeyDown(keyCode, event);
}


public void removeCurrentFragment()
{
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

    Fragment currentFrag =  getSupportFragmentManager().findFragmentById(R.id.detailFragment);


    String fragName = "NONE";

    if (currentFrag!=null)
        fragName = currentFrag.getClass().getSimpleName();


    if (currentFrag != null)
        transaction.remove(currentFrag);

    transaction.commit();

}

أتمنى أن يساعدك هذا!


وبما أن الأسئلة حول أفضل الممارسات ، أود أن أضيف ، في كثير من الأحيان فكرة جيدة لاستخدام نهج هجين لإنشاء جزء عند العمل مع بعض خدمات الويب REST

لا يمكننا تمرير كائنات معقدة ، على سبيل المثال بعض نماذج المستخدمين ، لحالة عرض جزء المستخدم

ولكن ما يمكننا القيام به ، هو التحقق من onCreate هذا المستخدم! = فارغ وما لم يكن - ثم إحضاره من طبقة البيانات ، وإلا - استخدم القائمة.

وبهذه الطريقة ، نكتسب القدرة على إعادة الإنشاء من قبل userId في حالة الترفيه المجزأ من Android و snappiness لإجراءات المستخدم ، بالإضافة إلى القدرة على إنشاء أجزاء من خلال الإمساك بالشيء نفسه أو معرفه فقط

شيء ما يحب هذا:

public class UserFragment extends Fragment {
    public final static String USER_ID="user_id";
    private User user;
    private long userId;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        userId = getArguments().getLong(USER_ID);
        if(user==null){
            //
            // Recreating here user from user id(i.e requesting from your data model,
            // which could be services, direct request to rest, or data layer sitting
            // on application model
            //
             user = bringUser();
        }
    }

    public static UserFragment newInstance(User user, long user_id){
        UserFragment userFragment = new UserFragment();
        Bundle args = new Bundle();
        args.putLong(USER_ID,user_id);
        if(user!=null){
            userFragment.user=user;
        }
        userFragment.setArguments(args);
        return userFragment;

    }

    public static UserFragment newInstance(long user_id){
        return newInstance(null,user_id);
    }

    public static UserFragment newInstance(User user){
        return newInstance(user,user.id);
    }
}




android android-fragments back-stack