[android] ViewPager والشظايا - ما هي الطريقة الصحيحة لتخزين حالة الشظية؟



Answers

أرغب في تقديم حل يتوسع على antonyt الرائعة antonyt تجاوز FragmentPageAdapter.instantiateItem(View, int) لحفظ المراجع إلى Fragments المنشئة حتى تتمكن من العمل عليها لاحقًا. يجب أن يعمل هذا أيضًا مع FragmentStatePagerAdapter ؛ انظر الملاحظات لمزيد من التفاصيل.

إليك مثال بسيط عن كيفية الحصول على مرجع إلى Fragments التي تم إرجاعها بواسطة FragmentPagerAdapter والتي لا تعتمد على tags الداخلية التي تم تعيينها على Fragments . المفتاح هو تجاوز instantiateItem() وحفظ المراجع هناك بدلاً من getItem() .

public class SomeActivity extends Activity {
    private FragmentA m1stFragment;
    private FragmentB m2ndFragment;

    // other code in your Activity...

    private class CustomPagerAdapter extends FragmentPagerAdapter {
        // other code in your custom FragmentPagerAdapter...

        public CustomPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            // Do NOT try to save references to the Fragments in getItem(),
            // because getItem() is not always called. If the Fragment
            // was already created then it will be retrieved from the FragmentManger
            // and not here (i.e. getItem() won't be called again).
            switch (position) {
                case 0:
                    return new FragmentA();
                case 1:
                    return new FragmentB();
                default:
                    // This should never happen. Always account for each position above
                    return null;
            }
        }

        // Here we can finally safely save a reference to the created
        // Fragment, no matter where it came from (either getItem() or
        // FragmentManger). Simply save the returned Fragment from
        // super.instantiateItem() into an appropriate reference depending
        // on the ViewPager position.
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
            // save the appropriate reference depending on position
            switch (position) {
                case 0:
                    m1stFragment = (FragmentA) createdFragment;
                    break;
                case 1:
                    m2ndFragment = (FragmentB) createdFragment;
                    break;
            }
            return createdFragment;
        }
    }

    public void someMethod() {
        // do work on the referenced Fragments, but first check if they
        // even exist yet, otherwise you'll get an NPE.

        if (m1stFragment != null) {
            // m1stFragment.doWork();
        }

        if (m2ndFragment != null) {
            // m2ndFragment.doSomeWorkToo();
        }
    }
}

أو إذا كنت تفضل العمل باستخدام tags بدلاً من المتغيرات / المراجع الخاصة Fragments الدورة ، يمكنك أيضًا انتزاع tags التي تم تعيينها بواسطة FragmentPagerAdapter بالطريقة نفسها: ملاحظة: لا ينطبق ذلك على FragmentStatePagerAdapter نظرًا لأنه لا يضع tags عند إنشاء Fragments .

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
    // get the tags set by FragmentPagerAdapter
    switch (position) {
        case 0:
            String firstTag = createdFragment.getTag();
            break;
        case 1:
            String secondTag = createdFragment.getTag();
            break;
    }
    // ... save the tags somewhere so you can reference them later
    return createdFragment;
}

لاحظ أن هذه الطريقة لا تعتمد على محاكاة tag الداخلية التي تم تعيينها بواسطة FragmentPagerAdapter وتستخدم بدلاً من ذلك واجهات برمجة التطبيقات المناسبة لاستردادها. بهذه الطريقة حتى إذا تغيرت tag في الإصدارات المستقبلية من SupportLibrary ستظل آمنة.

لا تنس أنه بناءً على تصميم نشاطك ، قد تكون Fragments التي تحاول العمل عليها موجودة أو لا تكون موجودة حتى الآن ، لذلك عليك حساب ذلك بإجراء عمليات فحص null قبل استخدام المراجع.

أيضًا ، إذا كنت تعمل بدلاً من ذلك مع FragmentStatePagerAdapter ، فأنت لا تريد الاحتفاظ بمراجع صلبة إلى Fragments الخاص بك لأنك قد يكون لديك العديد منها ، وسوف تحتفظ بها المراجع الصلبة في الذاكرة. بدلاً من ذلك ، احفظ مراجع Fragment في متغيرات WeakReference بدلاً من المراجع القياسية. مثله:

WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
    // reference hasn't been cleared yet; do work...
}
Question

يبدو أن الأجزاء تبدو لطيفة جدًا لفصل منطق واجهة المستخدم إلى بعض الوحدات. ولكن إلى جانب ViewPager فإن دورة حياته لا تزال ضبابية بالنسبة لي. هناك حاجة ماسة لأفكار المعلم!

تصحيح

انظر الحل البكم أدناه ؛-)

نطاق

النشاط الرئيسي يحتوي على ViewPager مع أجزاء. يمكن لهذه الأجزاء أن تنفذ منطقًا مختلفًا قليلاً لأنشطة أخرى (فرعية) ، بحيث يتم ملء بيانات الأجزاء عبر واجهة رد اتصال داخل النشاط. وكل شيء يعمل بشكل جيد عند الإطلاق الأول ، ولكن! ...

مشكلة

عندما يحصل على إعادة إنشاء النشاط (على سبيل المثال تغيير الاتجاه) حتى شظايا ViewPager . رمز (ستجد أدناه) يقول أنه في كل مرة يتم إنشاء النشاط أحاول إنشاء محول شظايا ViewPager جديد نفس الأجزاء (ربما هذه هي المشكلة) ولكن FragmentManager بالفعل كل هذه الأجزاء المخزنة في مكان ما (أين؟) ويبدأ آلية الترفيه لتلك. لذا فإن آلية الترفيه تستدعي الجزء "القديم" من onAttach ، onCreateView ، وما إلى ذلك مع استدعاء واجهة رد الاتصال لبدء البيانات عبر طريقة النشاط التي تم تنفيذها. ولكن هذه الطريقة تشير إلى الجزء الذي تم إنشاؤه حديثًا والذي يتم إنشاؤه عبر أسلوب onCreate للنشاط.

القضية

ربما أستخدم أنماطًا خاطئة ، ولكن حتى كتاب Android 3 Pro لا يحتوي على الكثير من المعلومات. لذا ، من فضلك ، أعطني لكمة واحدة واشرح كيفية القيام بها بالطريقة الصحيحة. تشكرات!

الشفرة

النشاط الرئيسي

public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {

private MessagesFragment mMessagesFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    setContentView(R.layout.viewpager_container);
    new DefaultToolbar(this);

    // create fragments to use
    mMessagesFragment = new MessagesFragment();
    mStreamsFragment = new StreamsFragment();

    // set titles and fragments for view pager
    Map<String, Fragment> screens = new LinkedHashMap<String, Fragment>();
    screens.put(getApplicationContext().getString(R.string.dashboard_title_dumb), new DumbFragment());
    screens.put(getApplicationContext().getString(R.string.dashboard_title_messages), mMessagesFragment);

    // instantiate view pager via adapter
    mPager = (ViewPager) findViewById(R.id.viewpager_pager);
    mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
    mPager.setAdapter(mPagerAdapter);

    // set title indicator
    TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.viewpager_titles);
    indicator.setViewPager(mPager, 1);

}

/* set of fragments callback interface implementations */

@Override
public void onMessageInitialisation() {

    Logger.d("Dash onMessageInitialisation");
    if (mMessagesFragment != null)
        mMessagesFragment.loadLastMessages();
}

@Override
public void onMessageSelected(Message selectedMessage) {

    Intent intent = new Intent(this, StreamActivity.class);
    intent.putExtra(Message.class.getName(), selectedMessage);
    startActivity(intent);
}

BasePagerActivity ويعرف أيضا باسم المساعد

public class BasePagerActivity extends FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

مشترك كهربائي

public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {

private Map<String, Fragment> mScreens;

public BasePagerAdapter(Map<String, Fragment> screenMap, FragmentManager fm) {

    super(fm);
    this.mScreens = screenMap;
}

@Override
public Fragment getItem(int position) {

    return mScreens.values().toArray(new Fragment[mScreens.size()])[position];
}

@Override
public int getCount() {

    return mScreens.size();
}

@Override
public String getTitle(int position) {

    return mScreens.keySet().toArray(new String[mScreens.size()])[position];
}

// hack. we don't want to destroy our fragments and re-initiate them after
@Override
public void destroyItem(View container, int position, Object object) {

    // TODO Auto-generated method stub
}

}

شظية

public class MessagesFragment extends ListFragment {

private boolean mIsLastMessages;

private List<Message> mMessagesList;
private MessageArrayAdapter mAdapter;

private LoadMessagesTask mLoadMessagesTask;
private OnMessageListActionListener mListener;

// define callback interface
public interface OnMessageListActionListener {
    public void onMessageInitialisation();
    public void onMessageSelected(Message selectedMessage);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    // setting callback
    mListener = (OnMessageListActionListener) activity;
    mIsLastMessages = activity instanceof DashboardActivity;

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    inflater.inflate(R.layout.fragment_listview, container);
    mProgressView = inflater.inflate(R.layout.listrow_progress, null);
    mEmptyView = inflater.inflate(R.layout.fragment_nodata, null);
    return super.onCreateView(inflater, container, savedInstanceState);
}

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

    // instantiate loading task
    mLoadMessagesTask = new LoadMessagesTask();

    // instantiate list of messages
    mMessagesList = new ArrayList<Message>();
    mAdapter = new MessageArrayAdapter(getActivity(), mMessagesList);
    setListAdapter(mAdapter);
}

@Override
public void onResume() {
    mListener.onMessageInitialisation();
    super.onResume();
}

public void onListItemClick(ListView l, View v, int position, long id) {
    Message selectedMessage = (Message) getListAdapter().getItem(position);
    mListener.onMessageSelected(selectedMessage);
    super.onListItemClick(l, v, position, id);
}

/* public methods to load messages from host acitivity, etc... */
}

حل

الحل البكم هو حفظ الأجزاء داخل onSaveInstanceState (من نشاط المضيف) مع putFragment والحصول عليها داخل onCreate عبر getFragment. ولكن لا يزال لدي شعور غريب بأن الأشياء لا ينبغي أن تعمل على هذا النحو ... انظر الكود أدناه:

    @Override
protected void onSaveInstanceState(Bundle outState) {

    super.onSaveInstanceState(outState);
    getSupportFragmentManager()
            .putFragment(outState, MessagesFragment.class.getName(), mMessagesFragment);
}

protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    ...
    // create fragments to use
    if (savedInstanceState != null) {
        mMessagesFragment = (MessagesFragment) getSupportFragmentManager().getFragment(
                savedInstanceState, MessagesFragment.class.getName());
                StreamsFragment.class.getName());
    }
    if (mMessagesFragment == null)
        mMessagesFragment = new MessagesFragment();
    ...
}



أرغب في تقديم حل بديل لحالة مختلفة قليلاً ، نظرًا لأن العديد من عمليات البحث الخاصة بي للحصول على إجابات أبقتني تقود إلى هذا الموضوع.

حالتي - أنا أقوم بإنشاء / إضافة صفحات بشكل ديناميكي وتحريكها إلى ViewPager ، ولكن عند استدارة (onConfigurationChange) ، سأنتهي بصفحة جديدة لأن بالطبع يتم استدعاء OnCreate مرة أخرى. ولكني أريد الاحتفاظ بالإشارة إلى جميع الصفحات التي تم إنشاؤها قبل التناوب.

المشكلة - ليس لديّ معرّفات فريدة لكل جزء أقوم بإنشائه ، لذلك كانت الطريقة الوحيدة للإشارة إلى تخزين المراجع بطريقة ما في المصفوفة لاستعادتها بعد تغيير الاستدارة / التهيئة.

الحل - كان المفهوم الأساسي هو أن يقوم النشاط (الذي يعرض الشظايا) أيضًا بإدارة صفيف المراجع إلى القطع الموجودة ، نظرًا لأن هذا النشاط يمكن أن يستخدم الحزم في onSaveInstanceState

public class MainActivity extends FragmentActivity

في هذا النشاط ، أعلن عن عضو خاص لتتبع الصفحات المفتوحة

private List<Fragment> retainedPages = new ArrayList<Fragment>();

يتم تحديث كل مرة يتم استدعاء onSaveInstanceState واستعادتها في onCreate

@Override
protected void onSaveInstanceState(Bundle outState) {
    retainedPages = _adapter.exportList();
    outState.putSerializable("retainedPages", (Serializable) retainedPages);
    super.onSaveInstanceState(outState);
}

... بمجرد تخزينه ، يمكن استرداده ...

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if (savedInstanceState != null) {
        retainedPages = (List<Fragment>) savedInstanceState.getSerializable("retainedPages");
    }
    _mViewPager = (CustomViewPager) findViewById(R.id.viewPager);
    _adapter = new ViewPagerAdapter(getApplicationContext(), getSupportFragmentManager());
    if (retainedPages.size() > 0) {
        _adapter.importList(retainedPages);
    }
    _mViewPager.setAdapter(_adapter);
    _mViewPager.setCurrentItem(_adapter.getCount()-1);
}

كانت هذه التغييرات ضرورية للنشاط الرئيسي ، ولذلك احتجت إلى أعضاء وطرق داخل FragmentPagerAdapter لكي يعمل ذلك ،

public class ViewPagerAdapter extends FragmentPagerAdapter

بناء مماثل (كما هو موضح أعلاه في MainActivity)

private List<Fragment> _pages = new ArrayList<Fragment>();

ويتم دعم هذه المزامنة (كما هو موضح أعلاه في onSaveInstanceState) بالتحديد بواسطة الطرق

public List<Fragment> exportList() {
    return _pages;
}

public void importList(List<Fragment> savedPages) {
    _pages = savedPages;
}

ثم أخيرا ، في فئة الجزء

public class CustomFragment extends Fragment

لكي يعمل كل هذا ، كان هناك تغييران ، أولاً

public class CustomFragment extends Fragment implements Serializable

ثم إضافة هذا إلى onCreate حتى لا يتم إتلاف الأجزاء

setRetainInstance(true);

ما زلت في عملية التفاف رأسي حول أجزاء دورة حياة أندرويد ، لذلك التحذير هنا هو أنه قد يكون هناك تكرار / عدم كفاءة في هذه الطريقة. لكنه يعمل بالنسبة لي ، وآمل أن يكون من المفيد للآخرين مع حالات مماثلة لي.




إضافة:

   @SuppressLint("ValidFragment")

قبل صفك

انها لا تعمل تفعل شيئا من هذا القبيل:

@SuppressLint({ "ValidFragment", "HandlerLeak" })



لقد توصلت إلى هذا الحل البسيط والأنيق. يفترض أن النشاط مسؤول عن إنشاء الأجزاء ، وأن المحول يخدمها فقط.

هذا هو رمز المحول (لا يوجد شيء غريب هنا ، باستثناء حقيقة أن mFragments عبارة عن قائمة mFragments التي يحتفظ بها النشاط)

class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {

    public MyFragmentPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }

    @Override
    public int getCount() {
        return mFragments.size();
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        TabFragment fragment = (TabFragment)mFragments.get(position);
        return fragment.getTitle();
    }
} 

المشكلة الكاملة لسلسلة الرسائل هذه هي الحصول على مرجع للأجزاء "القديمة" ، لذا استخدم هذا الرمز في onCreate للنشاط.

    if (savedInstanceState!=null) {
        if (getSupportFragmentManager().getFragments()!=null) {
            for (Fragment fragment : getSupportFragmentManager().getFragments()) {
                mFragments.add(fragment);
            }
        }
    }

بالطبع يمكنك زيادة تحسين هذا الرمز إذا لزم الأمر ، على سبيل المثال التأكد من أن الأجزاء هي أمثلة لفئة معينة.




ما هو BasePagerAdapter ؟ يجب عليك استخدام أحد محولات جهاز الاستدعاء القياسي - إما FragmentPagerAdapter أو FragmentStatePagerAdapter ، اعتمادًا على ما إذا كنت ترغب في ViewPager التي لم تعد بحاجة إليها من قبل ViewPager (أو السابقة) أو حفظ حالتها (الأخير) وإعادة - يعالج إذا لزم الأمر مرة أخرى.

يمكن العثور على نموذج كود لاستخدام ViewPager here

صحيح أن إدارة الشظايا في جهاز عرض النداء عبر مثيلات النشاط معقدة بعض الشيء ، لأن FragmentManager في الإطار يهتم بحفظ الحالة واستعادة أي شظايا نشطة صنعتها النداء. كل هذا يعني حقا أن المحول عند تهيئة يحتاج إلى التأكد من أنه يتصل مرة أخرى مع أي شظايا المستعادة هناك. يمكنك الاطلاع على رمز FragmentPagerAdapter أو FragmentStatePagerAdapter لمعرفة كيف يتم ذلك.






Related