ভিউপ্যাজার এবং টুকরা - খণ্ডের রাজ্য সঞ্চয় করার সঠিক উপায় কী?


492

কিছু মডিউলগুলিতে ইউআই যুক্তি আলাদা করার জন্য খণ্ডগুলি খুব সুন্দর বলে মনে হচ্ছে very তবে ViewPagerএর লাইফসাইকেলের সাথে আমার কাছে এখনও দুর্গন্ধ রয়েছে। তাই গুরু ভাবনাগুলি খারাপভাবে প্রয়োজন!

সম্পাদন করা

;-) নীচে বোবা সমাধান দেখুন

ব্যাপ্তি

প্রধান ক্রিয়াকলাপে ViewPagerটুকরো টুকরো রয়েছে। এই টুকরোগুলি অন্যান্য (সাবমাইন) ক্রিয়াকলাপগুলির জন্য কিছুটা আলাদা যুক্তি প্রয়োগ করতে পারে, তাই খণ্ডগুলির ডেটা ক্রিয়াকলাপের মধ্যে একটি কলব্যাক ইন্টারফেসের মাধ্যমে পূরণ করা হয়। এবং প্রথম প্রবর্তনে সবকিছু ঠিকঠাক কাজ করে, তবে! ...

সমস্যা

যখন ক্রিয়াকলাপটি পুনরায় তৈরি করা হয় (যেমন ওরিয়েন্টেশন পরিবর্তনের উপর) তাই ViewPagerএর টুকরো টুকরো করুন। কোড (আপনি নীচে পাবেন) বলছে যে প্রতিটি সময় কার্যকলাপ তৈরি হওয়ার সময় আমি একটি নতুন ViewPagerটুকরো টুকরো টুকরো হিসাবে অ্যাডাপ্টার তৈরি করার চেষ্টা করি (সম্ভবত এটিই সমস্যা) তবে ফ্রেগমেন্টম্যানেজার ইতিমধ্যে এই সমস্ত টুকরোকে কোথাও (কোথায়?) সঞ্চিত আছে এবং তাদের জন্য বিনোদন ব্যবস্থা শুরু করে। তাই বিনোদনের ব্যবস্থাটি ক্রিয়াকলাপের বাস্তবায়িত পদ্ধতির মাধ্যমে ডেটা শুরু করার জন্য আমার কলব্যাক ইন্টারফেসের কল দিয়ে "পুরানো" টুকরাটির অনাটাচ, অনক্রেটভিউ, ইত্যাদি কল করে। তবে এই পদ্ধতিটি সদ্য নির্মিত খণ্ডকে নির্দেশ করে যা ক্রিয়াকলাপের অনক্রিয়েট পদ্ধতির মাধ্যমে তৈরি।

সমস্যা

হতে পারে আমি ভুল নিদর্শনগুলি ব্যবহার করছি তবে অ্যান্ড্রয়েড 3 প্রো বইতেও এর বেশি নেই have সুতরাং, দয়া করে , আমাকে একটি-দু'টি পাঞ্চ দিন এবং এটি সঠিক উপায়ে কীভাবে করবেন তা নির্দেশ করুন। অনেক ধন্যবাদ!

কোড

প্রধান কাজ

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);
}

বেসপেজারঅ্যাক্টিভিটি ওরফে সহায়ক

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

সমাধান

বোবা সমাধান হ'ল পুটফ্রেগমেন্টের সাহায্যে সেভআইনস্ট্যান্সস্টেটের (হোস্ট ক্রিয়াকলাপের) ভিতরে থাকা টুকরোগুলি সংরক্ষণ এবং গেটফ্রেগমেন্টের মাধ্যমে ওনক্রিটের ভিতরে। তবে আমার এখনও একটি অদ্ভুত অনুভূতি আছে যে জিনিসগুলি এমনভাবে কাজ করা উচিত নয় ... নীচের কোডটি দেখুন:

    @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();
    ...
}

1
আমি এখনই আশ্চর্য হই: আমার কি খুব আলাদা পদ্ধতির ব্যবহার করা উচিত বা onCreate () এ ব্যবহার করার জন্য onSaaveInstancestate এর মাধ্যমে মূল ক্রিয়াকলাপের (ড্যাশবোর্ড) টুকরো সংরক্ষণ করার চেষ্টা করা উচিত? সেই টুকরোগুলি সংরক্ষণ এবং অনক্রিটে বান্ডিল থেকে পাওয়ার কোনও উপযুক্ত উপায় কি? তারা parcelable ... হবে বলে মনে হচ্ছে না
Oleksii Malovanyi

2
২ য় পন্থা কাজ করে - "সলিউশন" দেখুন। কিন্তু এটি কোডের একটি কুৎসিত অংশ বলে মনে হচ্ছে, তাই না?
ওলেক্সেই মালোভানই

1
অ্যান্ড্রয়েড ট্যাগটি পরিষ্কার করার প্রচেষ্টার স্বার্থে (বিশদটি এখানে: meta.stackexchange.com/questions/100529/… ), আপনার সমাধানটি উত্তর হিসাবে পোস্ট করা এবং নির্বাচিত হিসাবে চিহ্নিত করার বিষয়ে আপনি কি আপত্তি করবেন? এইভাবে এটি কোনও উত্তরবিহীন প্রশ্ন হিসাবে দেখাবে না :)
আলেকজান্ডার

1
হ্যাঁ, মনে হয় ঠিক আছে। আমার চেয়ে বেশি স্মিথের জন্য প্রত্যাশিত ...
ওলেকসেই মালোভানই

1
বোবা সমাধান এমনকি কাজ করে? এটি আমাকে একটি নাল পয়েন্টার ব্যতিক্রম দেয় ..
ঝেন লিউ

উত্তর:


451

FragmentPagerAdapterফ্রেমমেন্টম্যানেজারে যখন একটি খণ্ড যুক্ত করা হয় তখন এটি নির্দিষ্ট অবস্থানের ভিত্তিতে একটি বিশেষ ট্যাগ ব্যবহার করে যে খণ্ডটি স্থাপন করা হবে। FragmentPagerAdapter.getItem(int position)কেবল তখনই ডাকা হয় যখন সেই পজিশনের জন্য কোনও খণ্ড বিদ্যমান না থাকে। ঘোরার পরে, অ্যান্ড্রয়েড লক্ষ্য করবে যে এটি ইতিমধ্যে এই নির্দিষ্ট অবস্থানের জন্য একটি টুকরা তৈরি করেছে / সংরক্ষণ করেছে এবং তাই এটি FragmentManager.findFragmentByTag()একটি নতুন তৈরি করার পরিবর্তে কেবল এর সাথে আবার সংযোগ স্থাপনের চেষ্টা করে । এইগুলি ব্যবহার করার সময় সমস্ত কিছুই নিখরচায় আসে FragmentPagerAdapterএবং এই কারণেই আপনার খণ্ডটির আরম্ভের কোডটি getItem(int)পদ্ধতির অভ্যন্তরে রাখা স্বাভাবিক ।

এমনকি যদি আমরা একটি ব্যবহার না করতাম তবে FragmentPagerAdapterপ্রতিবারই নতুন খণ্ড তৈরি করা ভাল ধারণা নয় Activity.onCreate(Bundle)। যেমন আপনি লক্ষ্য করেছেন, ফ্রেগমেন্ট ম্যানেজারে যখন কোনও খণ্ড যুক্ত করা হবে তখন ঘোরার পরে এটি আপনার জন্য পুনরায় তৈরি করা হবে এবং এটি আবার যুক্ত করার দরকার নেই। টুকরা নিয়ে কাজ করার সময় এটি করা ত্রুটির সাধারণ কারণ of

খণ্ডগুলির সাথে কাজ করার সময় একটি সাধারণ পদ্ধতি হ'ল:

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

    ...

    CustomFragment fragment;
    if (savedInstanceState != null) {
        fragment = (CustomFragment) getSupportFragmentManager().findFragmentByTag("customtag");
    } else {
        fragment = new CustomFragment();
        getSupportFragmentManager().beginTransaction().add(R.id.container, fragment, "customtag").commit(); 
    }

    ...

}

একটি ব্যবহার করার সময় FragmentPagerAdapter, আমরা অ্যাডাপ্টারের সাথে টুকরো টুকরো টুকরো টিকিয়ে রাখি এবং উপরের পদক্ষেপগুলি সম্পাদন করতে হবে না। ডিফল্টরূপে, এটি কেবলমাত্র বর্তমান ফ্রেমের সামনে এবং পিছনে একটি ফ্র্যাগমেন্ট প্রিললোড করবে (যদিও আপনি এটি ব্যবহার না করে এটি তাদের ধ্বংস করে না FragmentStatePagerAdapter)। এটি ViewPager.setOffscreenPageLimit (int) দ্বারা নিয়ন্ত্রিত হয় । এ কারণে, অ্যাডাপ্টারের বাইরের অংশগুলিতে সরাসরি কল করার পদ্ধতিগুলি বৈধ হওয়ার গ্যারান্টিযুক্ত নয়, কারণ এগুলি এমনকি জীবিতও না হতে পারে।

একটি দীর্ঘ গল্প সংক্ষিপ্ত কাটাতে, আপনার পরবর্তী সমাধানটি putFragmentপরে রেফারেন্স পেতে সক্ষম হবার জন্য এতটা পাগল নয়, এবং যাইহোক (উপরে) খণ্ডগুলি ব্যবহার করার সাধারণ পদ্ধতির মতো নয়। অন্যথায় রেফারেন্স পাওয়া মুশকিল কারণ খণ্ডটি অ্যাডাপ্টারের দ্বারা যুক্ত করা হয়েছে, এবং আপনি ব্যক্তিগতভাবে নয়। আপনি নিশ্চিত যে offscreenPageLimitএটি উপস্থিত থাকার উপর নির্ভর করে যেহেতু আপনার কাঙ্ক্ষিত টুকরোগুলি সর্বদা লোড করার জন্য যথেষ্ট পরিমাণে রয়েছে তা নিশ্চিত করুন। এটি ভিউপ্যাজারের অলস লোডিং ক্ষমতার বাইরে চলে যায় তবে আপনি নিজের অ্যাপ্লিকেশনটির জন্য যা চান তা মনে হয়।

আরেকটি পন্থা হ'ল FragmentPageAdapter.instantiateItem(View, int)সুপার কল থেকে ফিরে আসা খণ্ডটি ফেরত দেওয়ার আগে একটি রেফারেন্স ওভাররাইড করা এবং সংরক্ষণ করা (এটি ইতিমধ্যে উপস্থিত থাকলে খণ্ডটি অনুসন্ধান করার যুক্তি রয়েছে)।

পূর্ণাঙ্গ চিত্রের জন্য ফ্রেগমেন্টপাগারএডাপ্টার (সংক্ষিপ্ত) এবং ভিউপ্যাজারের (লম্বা) কয়েকটি উত্স দেখুন ।


7
শেষ অংশ পছন্দ। টুকরো টুকরো করার জন্য একটি ক্যাশে ছিল এবং এর ভিতরে ক্যাশে যুক্তিযুক্ত পুটটি সরিয়ে নিয়েছিল FragmentPageAdapter.instantiateItem(View, int)। অবশেষে একটি দীর্ঘস্থায়ী বাগটি স্থির করে যা কেবল ঘূর্ণায়মান / কনফিগার পরিবর্তনে উপস্থিত হয় এবং আমাকে পাগল করে
তোলে

2
এটি ঘূর্ণন পরিবর্তন নিয়ে আমার একটি সমস্যা সমাধান করেছে। এটি সম্ভবত যে আমি সন্ধানের জন্য দেখতে পাচ্ছি না তবে এটি কি নথিভুক্ত করা হয়েছে? অর্থাত্ পূর্ববর্তী অবস্থা থেকে পুনরুদ্ধার করতে ট্যাগ ব্যবহার করছেন? এটি সম্ভবত একটি সুস্পষ্ট উত্তর, তবে আমি অ্যান্ড্রয়েড বিকাশে মোটামুটি নতুন।

1
@ ইন্ডাস্ট্রিয়াল-অ্যান্টিডিপ্রেসেন্ট এটি সেই ধারকটির আইডি (যেমন ফ্রেমলআউট) যাতে এতে খণ্ড যুক্ত করা যায়।
অ্যানটোনেট

1
বিটিডব্লিউ, আমার জন্য FragmentPageAdapter.instantiateItem(ViewGroup, int)বরং এটি ছিল FragmentPageAdapter.instantiateItem(View, int)
প্রদর্শন নাম

1
বিটিডব্লু আপনি কি জানেন যে টুকরাটি স্ক্রিনে সজ্জিত হয়ে গেলে কোন (যদি থাকে) ভগ্নাংশের লাইফসাইকেল পদ্ধতিটি বলা হয়? এটা নাকি onDetach()অন্য কিছু?
ygeher

36

আমি এমন একটি প্রস্তাব দিতে চাই যা antonytএর দুর্দান্ত উত্তর এবং প্রসারিত FragmentPageAdapter.instantiateItem(View, int)রেফারেন্সগুলি সংরক্ষণ করার জন্য ওভাররাইডিংয়ের উল্লেখকে প্রসারিত করে Fragmentsযাতে আপনি পরে সেগুলি নিয়ে কাজ করতে পারেন। এটির সাথেও কাজ করা উচিত FragmentStatePagerAdapter; বিশদ জন্য নোট দেখুন।


এটির মাধ্যমে অভ্যন্তরীণ সেটটির উপর নির্ভর করে না যে কীভাবে Fragmentsফিরে আসে তার একটি রেফারেন্স কীভাবে পাবেন তার একটি সাধারণ উদাহরণ এখানে । কী ওভাররাইড হয় সেখানে এবং তথ্যসূত্র সংরক্ষণ পরিবর্তে এর মধ্যে ।FragmentPagerAdaptertagsFragmentsinstantiateItem()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এটি প্রযোজ্য নয় ।tagsFragments

@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এবং এর পরিবর্তে তাদের পুনরুদ্ধারের জন্য সঠিক API গুলি ব্যবহার করে। এইভাবে এমনকি tagভবিষ্যতের সংস্করণগুলির পরিবর্তনগুলি SupportLibraryআপনি এখনও নিরাপদ থাকবেন।


ভুলবেন না যে, আপনার নকশা উপর নির্ভর করে Activity, 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...
}

মেমরির ঘাটতিতে অ্যান্ড্রয়েড ফ্র্যাগমেন্টম্যানেজারকে অব্যবহৃত টুকরো ধ্বংস করতে বলবে, ঠিক? যদি আমি ঠিক থাকি তবে আপনার দ্বিতীয় সংস্করণটি কাজ করবে, প্রথমটি ব্যর্থ হতে পারে। (বর্তমান অ্যান্ড্রয়েড সংস্করণগুলি এটি করে কিনা তা জানি না, তবে আমাদের কাছে এটি আশা করা উচিত বলে মনে হয়))
মরিটজ উভয়েই

1
FragmetPagerAdapterপ্রতিটি স্ক্রিন ঘোরানোর উপর ক্রিয়াকলাপে একটি অন ক্রিয়েট তৈরির বিষয়ে । এটি কি ভুল কারণ এটি ইতিমধ্যে সংযুক্ত টুকরোগুলিকে পুনরায় ব্যবহার করে বাইপাস করতে পারেFragmentPagerAdapter
বাহমান

ওভাররাইডিং instantiateItem()হ'ল উপায়; এটি আমাকে স্ক্রিনের ঘূর্ণন পরিচালনা করতে এবং ক্রিয়াকলাপ এবং অ্যাডাপ্টার পুনরায় চালু হওয়ার সাথে সাথে আমার বিদ্যমান ফ্রেগমেন্টের উদাহরণগুলি পুনরুদ্ধার করতে সহায়তা করেছিল; আমি কোডটিতে নিজেকে একটি অনুস্মারক হিসাবে মন্তব্য রেখেছি: ঘোরানোর পরে, অনুরোধ করা getItem()হয় না; শুধুমাত্র এই পদ্ধতিটি instantiateItem()বলা হয়। নতুন বাস্তবায়নের পরিবর্তে instantiateItem()প্রকৃত বাস্তবায়নটি ঘূর্ণনের পরে টুকরাগুলি পুনরায় সংযুক্ত করে (প্রয়োজনীয় হিসাবে)!
হ্যালোইমকিভো

আমি Fragment createdFragment = (Fragment) super.instantiateItem..প্রথম সমাধানে নাল পয়েন্টার পাচ্ছি ।
অ্যালেক্স এস

এই সমস্যার সমস্ত নকল / বৈকল্পিক পুনরাবৃত্তিগুলির মাধ্যমে শিকার করার পরে, এটি ছিল সেরা সমাধান। (আরও প্রাসঙ্গিক শিরোনাম সহ অন্য কিউ থেকে ক্রস-রেফারেন্সড, সুতরাং এর জন্য আপনাকে ধন্যবাদ!)
মান্ডিসাডাব্লু

18

আমি আপনার প্রশ্নের অন্য একটি তুলনামূলক সহজ সমাধান পেয়েছি।

আপনার কাছ থেকে দেখতে পারেন FragmentPagerAdapter সোর্স কোড , টুকরা দ্বারা পরিচালিত FragmentPagerAdapterসঞ্চয় FragmentManagerব্যবহার উত্পন্ন ট্যাগ অধীনে:

String tag="android:switcher:" + viewId + ":" + index;

viewIdহয় container.getId(), containerআপনার হয় ViewPagerউদাহরণস্বরূপ। indexটুকরা এর অবস্থান। সুতরাং আপনি অবজেক্ট আইডি সংরক্ষণ করতে পারেন outState:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("viewpagerid" , mViewPager.getId() );
}

@Override
    protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null)
        viewpagerid=savedInstanceState.getInt("viewpagerid", -1 );  

    MyFragmentPagerAdapter titleAdapter = new MyFragmentPagerAdapter (getSupportFragmentManager() , this);        
    mViewPager = (ViewPager) findViewById(R.id.pager);
    if (viewpagerid != -1 ){
        mViewPager.setId(viewpagerid);
    }else{
        viewpagerid=mViewPager.getId();
    }
    mViewPager.setAdapter(titleAdapter);

আপনি যদি এই খণ্ডটির সাথে যোগাযোগ করতে চান তবে আপনি যদি তা থেকে পেতে পারেন FragmentManagerযেমন:

getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewpagerid + ":0")

21
হুঁ আমি মনে করি না যে এটি কোনও অভ্যন্তরীণ ট্যাগ নামকরণ কনভেনশনে আপনার জবাব দেওয়ার মতো একটি উত্তম উপায় যা কোনওভাবেই চিরকাল স্থায়ী হয় না।
ডোরি

1
অভ্যন্তরীণ উপর নির্ভর করে না এমন সমাধানের সন্ধানকারীদের জন্য tag, আমার উত্তরটি চেষ্টা করে দেখুন ।
টনি চ্যান

16

আমি সম্ভবত কিছুটা আলাদা ক্ষেত্রে বিকল্প বিকল্প দিতে চাই, কারণ আমার উত্তরগুলির অনেক অনুসন্ধান আমাকে এই থ্রেডে নিয়ে গেছে।

আমার কেস - আমি পৃষ্ঠাগুলি গতিশীলভাবে তৈরি / যুক্ত করছি এবং সেগুলিকে ভিউপ্যাজারে স্লাইড করছি, তবে যখন ঘোরানো হবে (onConfigrationChange) আমি একটি নতুন পৃষ্ঠাটি শেষ করি কারণ অবশ্যই অনক্রিটকে আবার বলা হয়। তবে আমি ঘোরার আগে তৈরি হওয়া সমস্ত পৃষ্ঠাগুলির রেফারেন্স রাখতে চাই।

সমস্যা - আমি তৈরি প্রতিটি খণ্ডের জন্য আমার অনন্য শনাক্তকারী নেই, সুতরাং রেফারেন্সের একমাত্র উপায় ছিল কোনওভাবে কোনও ঘূর্ণন / কনফিগারেশন পরিবর্তনের পরে পুনরুদ্ধার করার জন্য একটি অ্যারে রেফারেন্স স্টোর করা।

কার্যসংক্রান্ত - মূল ধারণা কার্যকলাপ (যার মধ্যে প্রদর্শন টুকরা) বিদ্যমান টুকরা উল্লেখ অ্যারে পরিচালনা আছে যেহেতু এই কার্যকলাপ onSaveInstanceState এ বান্ডিল ব্যবহার করতে পারে ছিল

public class MainActivity extends FragmentActivity

সুতরাং এই ক্রিয়াকলাপের মধ্যে, আমি খোলা পৃষ্ঠাগুলি ট্র্যাক করার জন্য একটি ব্যক্তিগত সদস্য হিসাবে ঘোষণা করি declare

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

এটি প্রতিবার onSaveInstanceState আপডেট করা হয় এবং অনক্রিটটিতে পুনরুদ্ধার করা হয়

@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);
}

এগুলি মূল ক্রিয়াকলাপে প্রয়োজনীয় পরিবর্তনগুলি ছিল এবং কাজ করার জন্য আমার ফ্রেগমেন্টপেজার অ্যাডাপ্টারের মধ্যে সদস্য এবং পদ্ধতিগুলির প্রয়োজন ছিল, তাই এর মধ্যে

public class ViewPagerAdapter extends FragmentPagerAdapter

একটি অভিন্ন নির্মাণ (মেইনএকটিভিটির উপরে যেমন দেখানো হয়েছে)

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

এবং তারপরে এটি অনক্রিয়েটে যুক্ত করুন যাতে খণ্ডগুলি ধ্বংস হয় না

setRetainInstance(true);

আমি এখনও টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরা তবে এটি আমার পক্ষে কাজ করে এবং আমি আশা করি আমার ক্ষেত্রেও একইরকম ক্ষেত্রে অন্যদের জন্য সহায়ক হতে পারে।


2
+RetainInstance (সত্য) এর জন্য +1 - আমি যে কালো যাদুটি খুঁজছিলাম!
শব্দরুপ

ফ্রেগমেন্টস্টেটপেজারএডাপ্টার (ভি 13) ব্যবহার করে এটি আরও সহজ হয়েছিল। রাজ্যের পুনরুদ্ধার এবং মুক্তির সাথে আপনার জন্য কোন চুক্তি।
কসাই

পরিষ্কার বিবরণ এবং খুব সুন্দর সমাধান। ধন্যবাদ!
ব্রুনো বিরি

8

আমার সমাধানটি অত্যন্ত অভদ্র তবে কাজ করে: আমার টুকরোগুলি রক্ষণাবেক্ষণকৃত ডেটা থেকে গতিশীলভাবে তৈরি হওয়ার কারণে, আমি PageAdapterকল করার আগে কেবল সমস্ত টুকরোটি সরিয়ে ফেলি super.onSaveInstanceState()এবং তারপরে সেগুলি ক্রিয়াকলাপ তৈরিতে পুনরায় তৈরি করি:

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putInt("viewpagerpos", mViewPager.getCurrentItem() );
    mSectionsPagerAdapter.removeAllfragments();
    super.onSaveInstanceState(outState);
}

আপনি এগুলিকে সরাতে পারবেন না onDestroy(), অন্যথায় আপনি এই ব্যতিক্রমটি পান:

java.lang.IllegalStateException: এর পরে এই ক্রিয়াটি সম্পাদন করতে পারবেন না onSaveInstanceState

পৃষ্ঠা অ্যাডাপ্টারের কোড এখানে:

public void removeAllfragments()
{
    if ( mFragmentList != null ) {
        for ( Fragment fragment : mFragmentList ) {
            mFm.beginTransaction().remove(fragment).commit();
        }
        mFragmentList.clear();
        notifyDataSetChanged();
    }
}

আমি কেবল বর্তমান পৃষ্ঠাটি সংরক্ষণ করেছি এবং onCreate()খণ্ডগুলি তৈরি হওয়ার পরে এটি পুনরুদ্ধার করব ।

if (savedInstanceState != null)
    mViewPager.setCurrentItem( savedInstanceState.getInt("viewpagerpos", 0 ) );  

আমি জানি না কেন, তবে নোটিফাইড ডেটাসেট চেঞ্জড (); অ্যাপ্লিকেশনে ক্রাশ হওয়ার কারণ
মালাচিয়াজ

4

এটা কি BasePagerAdapter? আপনার স্ট্যান্ডার্ড পেজার অ্যাডাপ্টারগুলির একটি ব্যবহার করা উচিত - হয় FragmentPagerAdapterবা FragmentStatePagerAdapterআপনি যে খন্ডগুলি আর প্রয়োজন ViewPagerহয় না তার উপর নির্ভর করে হয় (প্রাক্তন) এর আশেপাশে রাখা হয় বা তাদের রাষ্ট্র সংরক্ষণ করা হয় (পরে) এবং পুনরায় তৈরি করা হয় আবার দরকার।

ব্যবহারের জন্য নমুনা কোডটি এখানেViewPager পাওয়া যাবে

এটি সত্য যে ক্রিয়াকলাপের দৃষ্টান্ত জুড়ে একটি ভিউ পেজারে টুকরোগুলির পরিচালনা সামান্য জটিল, কারণ FragmentManagerফ্রেমওয়ার্কটিতে রাষ্ট্রটি সংরক্ষণ এবং পেজার তৈরির যে কোনও সক্রিয় টুকরো পুনরুদ্ধারের যত্ন নেয়। এর সবকিছুর অর্থ হ'ল অ্যাডাপ্টারটি যখন আরম্ভ করা হয় তখন নিশ্চিত হওয়া দরকার যে এটি পুনরুদ্ধার করে যা কিছু পুনরুদ্ধার করা টুকরো রয়েছে তার সাথে। আপনি কোডটি দেখতে পারেন FragmentPagerAdapterবা FragmentStatePagerAdapterএটি কীভাবে হয় তা দেখতে।


1
বেসপাগারএডাপ্টার কোডটি আমার প্রশ্নের মধ্যে উপলব্ধ। যেহেতু কেউ দেখতে পাচ্ছে এটি কেবল শিরোনামপ্রবাইডার বাস্তবায়নের উদ্দেশ্যে ফ্রেগমেন্টপেজারএডাপ্টারকে প্রসারিত করে । সুতরাং সবকিছু ইতিমধ্যে অ্যান্ড্রয়েড দেব প্রস্তাবিত পদ্ধতির সাথে কাজ করে চলেছে।
ওলেকসেই মালোভানই

আমি "ফ্রেগমেন্টস্টেটপেজারএডাপ্টার" সম্পর্কে অবগত ছিলাম না। আপনি আমাকে আক্ষরিক ঘন্টা বাঁচাতে। ধন্যবাদ।
ননোসোস

2

যদি কারও যদি তাদের ফ্রেগমেন্টস্টেটপেজার অ্যাডাপ্টারে সমস্যা হয় তবে তার খণ্ডগুলির রাজ্যটি সঠিকভাবে পুনরুদ্ধার না করে ... অর্থাত্ ... ফ্র্যাগমেন্টস্টেটপেজার অ্যাডাপ্টারের দ্বারা রাষ্ট্র থেকে পুনঃস্থাপনের পরিবর্তে নতুন খণ্ড তৈরি করা হচ্ছে ...

আপনি কল করার ViewPager.setOffscreenPageLimit()আগে আপনি কল করেছেন তা নিশ্চিত করুনViewPager.setAdapter(fragmentStatePagerAdapter)

কল করার পরে ViewPager.setOffscreenPageLimit()... ভিউপেজার সাথে সাথে তার অ্যাডাপ্টারের দিকে তাকাবে এবং এর খণ্ডগুলি পাওয়ার চেষ্টা করবে। ভিউপ্যাজারে সেভড ইনস্ট্যান্সস্টেট থেকে টুকরো পুনরুদ্ধার করার সুযোগ পাওয়ার আগেই এটি ঘটতে পারে (সুতরাং নতুন নতুন টুকরো তৈরি করা যায় যা সেভিডআইনস্ট্যান্সস্টেট থেকে নতুন করে শুরু করা যায় না কারণ তারা নতুন're


0

আমি এই সহজ এবং মার্জিত সমাধান নিয়ে এসেছি। এটি ধরে নিয়েছে যে ক্রিয়াকলাপটি খণ্ডগুলি তৈরি করার জন্য দায়বদ্ধ এবং অ্যাডাপ্টারটি কেবল তাদের পরিবেশন করে।

এটি অ্যাডাপ্টারের কোড ( 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();
    }
} 

এই থ্রেডের পুরো সমস্যাটি "পুরানো" খণ্ডগুলির একটি রেফারেন্স পাচ্ছে, তাই আমি এই কোডটি ক্রিয়াকলাপের অনক্রিটটিতে ব্যবহার করি।

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

অবশ্যই আপনি প্রয়োজনে এই কোড টিউন আরও সূক্ষ্ম করতে পারেন, উদাহরণস্বরূপ নিশ্চিত করা যে খণ্ডগুলি একটি নির্দিষ্ট শ্রেণীর উদাহরণস্বরূপ।


0

ওরিয়েন্টেশন পরিবর্তনের পরে টুকরোগুলি পেতে আপনাকে .getTag () ব্যবহার করতে হবে।

    getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewPagerId + ":" + positionOfItemInViewPager)

আরও কিছুটা হ্যান্ডলিংয়ের জন্য আমি ভিউপ্যাজারআইডি এবং ফ্রেগমেন্টক্লাসের মাধ্যমে যে কোনও অবস্থানের টুকরো টুকরো পেতে আমার পেজএডাপ্টারের জন্য নিজস্ব অ্যারেলিস্ট লিখেছি:

public class MyPageAdapter extends FragmentPagerAdapter implements Serializable {
private final String logTAG = MyPageAdapter.class.getName() + ".";

private ArrayList<MyPageBuilder> fragmentPages;

public MyPageAdapter(FragmentManager fm, ArrayList<MyPageBuilder> fragments) {
    super(fm);
    fragmentPages = fragments;
}

@Override
public Fragment getItem(int position) {
    return this.fragmentPages.get(position).getFragment();
}

@Override
public CharSequence getPageTitle(int position) {
    return this.fragmentPages.get(position).getPageTitle();
}

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


public int getItemPosition(Object object) {
    //benötigt, damit bei notifyDataSetChanged alle Fragemnts refrehsed werden

    Log.d(logTAG, object.getClass().getName());
    return POSITION_NONE;
}

public Fragment getFragment(int position) {
    return getItem(position);
}

public String getTag(int position, int viewPagerId) {
    //getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.shares_detail_activity_viewpager + ":" + myViewPager.getCurrentItem())

    return "android:switcher:" + viewPagerId + ":" + position;
}

public MyPageBuilder getPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
    return new MyPageBuilder(pageTitle, icon, selectedIcon, frag);
}


public static class MyPageBuilder {

    private Fragment fragment;

    public Fragment getFragment() {
        return fragment;
    }

    public void setFragment(Fragment fragment) {
        this.fragment = fragment;
    }

    private String pageTitle;

    public String getPageTitle() {
        return pageTitle;
    }

    public void setPageTitle(String pageTitle) {
        this.pageTitle = pageTitle;
    }

    private int icon;

    public int getIconUnselected() {
        return icon;
    }

    public void setIconUnselected(int iconUnselected) {
        this.icon = iconUnselected;
    }

    private int iconSelected;

    public int getIconSelected() {
        return iconSelected;
    }

    public void setIconSelected(int iconSelected) {
        this.iconSelected = iconSelected;
    }

    public MyPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
        this.pageTitle = pageTitle;
        this.icon = icon;
        this.iconSelected = selectedIcon;
        this.fragment = frag;
    }
}

public static class MyPageArrayList extends ArrayList<MyPageBuilder> {
    private final String logTAG = MyPageArrayList.class.getName() + ".";

    public MyPageBuilder get(Class cls) {
        // Fragment über FragmentClass holen
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return super.get(indexOf(item));
            }
        }
        return null;
    }

    public String getTag(int viewPagerId, Class cls) {
        // Tag des Fragment unabhängig vom State z.B. nach bei Orientation change
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return "android:switcher:" + viewPagerId + ":" + indexOf(item);
            }
        }
        return null;
    }
}

সুতরাং খণ্ডগুলি সহ কেবল একটি মাইপেজআরলিলিস্ট তৈরি করুন:

    myFragPages = new MyPageAdapter.MyPageArrayList();

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_data_frag),
            R.drawable.ic_sd_storage_24dp,
            R.drawable.ic_sd_storage_selected_24dp,
            new WidgetDataFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_color_frag),
            R.drawable.ic_color_24dp,
            R.drawable.ic_color_selected_24dp,
            new WidgetColorFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_textsize_frag),
            R.drawable.ic_settings_widget_24dp,
            R.drawable.ic_settings_selected_24dp,
            new WidgetTextSizeFrag()));

এবং তাদের ভিউপাগারে যুক্ত করুন:

    mAdapter = new MyPageAdapter(getSupportFragmentManager(), myFragPages);
    myViewPager.setAdapter(mAdapter);

এর পরে আপনি ওরিয়েন্টেশনটির ক্লাস ব্যবহার করে সঠিক খণ্ডটি পরিবর্তন করতে পারেন:

        WidgetDataFrag dataFragment = (WidgetDataFrag) getSupportFragmentManager()
            .findFragmentByTag(myFragPages.getTag(myViewPager.getId(), WidgetDataFrag.class));

-30

যোগ করুন:

   @SuppressLint("ValidFragment")

আপনার ক্লাসের আগে

এটি কিছু কাজ করে না এটি:

@SuppressLint({ "ValidFragment", "HandlerLeak" })
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.