Fragments का उपयोग करके Android में प्रत्येक टैब के लिए अलग बैक स्टैक


158

मैं एंड्रॉइड ऐप में नेविगेशन के लिए टैब लागू करने का प्रयास कर रहा हूं। चूंकि TabActivity और ActivityGroup को पदावनत किया गया है इसलिए मैं इसके बजाय Fragments का उपयोग करके इसे लागू करना चाहूंगा।

मुझे पता है कि प्रत्येक टैब के लिए एक टुकड़ा कैसे सेट करें और तब टैब क्लिक करने पर टुकड़े को स्विच करें। लेकिन मैं प्रत्येक टैब के लिए एक अलग बैक स्टैक कैसे रख सकता हूं?

एक उदाहरण के लिए फ्रैगमेंट ए और बी टैब 1 के तहत होगा और टैब 2 के तहत फ्रैगमेंट सी और डी। जब ऐप शुरू किया जाता है तो फ्रैगमेंट ए दिखाया जाता है और टैब 1 का चयन किया जाता है। तब फ्रैगमेंट ए को फ्रैगमेंट बी से बदला जा सकता है। जब टैब 2 का चयन किया जाता है तो फ्रैगमेंट सी प्रदर्शित किया जाना चाहिए। यदि टैब 1 चुना गया है, तो फ्रैगमेंट बी को एक बार फिर से प्रदर्शित किया जाना चाहिए। इस बिंदु पर फ्रैगमेंट ए दिखाने के लिए बैक बटन का उपयोग करना संभव होना चाहिए।

इसके अलावा, यह महत्वपूर्ण है कि डिवाइस को घुमाए जाने पर प्रत्येक टैब के लिए स्थिति बनी रहे।

बीआर मार्टिन

जवाबों:


23

वर्तमान में ढांचा आपके लिए स्वचालित रूप से ऐसा नहीं करेगा। आपको प्रत्येक टैब के लिए अपने स्वयं के बैक स्टैक बनाने और प्रबंधित करने की आवश्यकता होगी।

सच कहूं, तो ऐसा लगता है कि वास्तव में एक संदिग्ध बात है। मैं इसकी कल्पना नहीं कर सकता जिसके परिणामस्वरूप एक अच्छा यूआई है - यदि बैक कुंजी टैब पर निर्भर करता है कि मैं अलग-अलग चीजें करने जा रहा हूं, खासकर अगर बैक की भी पूरी गतिविधि को बंद करने का अपना सामान्य व्यवहार है जब शीर्ष पर है ढेर ... बुरा लगता है।

यदि आप एक वेब ब्राउज़र यूआई जैसा कुछ बनाने की कोशिश कर रहे हैं, तो उपयोगकर्ता के लिए स्वाभाविक रूप से एक UX प्राप्त करने के लिए संदर्भ के आधार पर व्यवहार के बहुत सारे सूक्ष्म जुड़ाव शामिल हैं, इसलिए आपको निश्चित रूप से अपना खुद का बैक स्टैक करने की आवश्यकता होगी प्रबंधन ढांचे में कुछ डिफ़ॉल्ट कार्यान्वयन पर भरोसा करने के बजाय। एक उदाहरण के लिए यह ध्यान देने की कोशिश करें कि विभिन्न तरीकों से मानक ब्राउज़र के साथ पीछे की बातचीत किस तरह से आपके अंदर और बाहर जा सकती है। (ब्राउज़र में "प्रत्येक विंडो" अनिवार्य रूप से एक टैब है।)


7
ऐसा मत करो। और ढांचा शायद ही बेकार है। यह आपको इस तरह की चीज के लिए स्वचालित समर्थन नहीं देता है, जैसा कि मैंने कहा कि मैं बहुत विशिष्ट स्थितियों को छोड़कर एक सभ्य उपयोगकर्ता अनुभव के परिणामस्वरूप कल्पना नहीं कर सकता हूं, जहां आपको वैसे भी पीछे के व्यवहार को सावधानीपूर्वक नियंत्रित करने की आवश्यकता होगी।
21

9
इस प्रकार का नेविगेशन, फिर आपके पास टैब के पृष्ठों का पदानुक्रम और पदानुक्रम है, उदाहरण के लिए iPhone एप्लिकेशन के लिए बहुत आम है (आप ऐप स्टोर और आईपॉड ऐप की जांच कर सकते हैं)। मुझे उनका उपयोगकर्ता अनुभव काफी सभ्य लगता है।
दिमित्री रयदेंको

13
यह पागल है। IPhone में बैक बटन भी नहीं है। टैब में टुकड़े को लागू करने के लिए एपीआई डेमो बहुत सरल कोड दिखा रहे हैं। यह सवाल पूछा जा रहा था कि प्रत्येक टैब के लिए अलग-अलग बैक स्टैक हैं, और मेरी प्रतिक्रिया यह है कि फ्रेमवर्क स्वचालित रूप से इसकी आपूर्ति नहीं करता है क्योंकि बैक बटन क्या करता है, इसके लिए शब्दार्थ यह एक भद्दा उपयोगकर्ता अनुभव होगा। यदि आप चाहते हैं तो आप अपने आप को आसानी से वापस शब्दार्थ लागू कर सकते हैं
हैकबॉड

4
फिर से, iPhone में बैक बटन नहीं है, इसलिए यह एंड्रॉइड की तरह शब्दार्थ व्यवहार नहीं करता है। इसके अलावा, "केवल गतिविधियों के साथ रहना और अपने आप को बचाने के लिए बेहतर समय" यहां कोई मतलब नहीं है, क्योंकि गतिविधियां आपको यूआई में टैब को अपने स्वयं के अलग-अलग स्टैक के साथ बनाए रखने की अनुमति नहीं देती हैं; वास्तव में गतिविधियों का स्टैक प्रबंधन फ्रैगमेंट फ्रेमवर्क द्वारा प्रदान की गई तुलना में कम लचीला है।
हैकबोड

22
@hackbod मैं आपकी बातों का पालन करने की कोशिश कर रहा हूं, लेकिन कस्टम बैक-स्टैक व्यवहार को लागू करने में परेशानी हो रही है। मुझे पता है कि इस के डिजाइन में शामिल किया गया है, आपके पास एक ठोस अंतर्दृष्टि होगी कि यह कितना आसान हो सकता है। क्या यह संभव है कि ओपी के उपयोग के मामले के लिए एक डेमो ऐप है, क्योंकि यह वास्तव में एक बहुत ही सामान्य स्थिति है, विशेष रूप से हम में से उन लोगों के लिए जो इन अनुरोध करने वाले क्लाइंट के लिए iOS ऐप लिखना और पोर्ट करना चाहते हैं .... अलग से प्रबंधन। प्रत्येक FragmentActivity के भीतर टुकड़ा बैकस्ट।
रिचर्ड ले मेसुरियर

138

मुझे इस सवाल पर बहुत देर हो रही है। लेकिन जब से यह धागा बहुत जानकारीपूर्ण और मेरे लिए उपयोगी रहा है मैंने सोचा कि मैं अपने दो पेंस को यहां बेहतर तरीके से पोस्ट करता हूं।

मुझे इस तरह एक स्क्रीन फ्लो की आवश्यकता थी (प्रत्येक टैब में 2 टैब और 2 विचारों के साथ एक न्यूनतर डिजाइन),

tabA
    ->  ScreenA1, ScreenA2
tabB
    ->  ScreenB1, ScreenB2

मेरे पास अतीत में समान आवश्यकताएं थीं, और मैंने इसका उपयोग किया था TabActivityGroup किया था (जो उस समय भी पदावनत हो गया था) और गतिविधियाँ। इस बार मैं फ्रेग्मेंट्स का उपयोग करना चाहता था।

तो यह है कि मैं यह कैसे किया है।

1. बेस फ्रैगमेंट क्लास बनाएं

public class BaseFragment extends Fragment {
    AppMainTabActivity mActivity;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mActivity = (AppMainTabActivity) this.getActivity();
    }

    public void onBackPressed(){
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data){
    }
}

आपके ऐप के सभी टुकड़े इस बेस क्लास का विस्तार कर सकते हैं। यदि आप विशेष अंशों का उपयोग करना चाहते हैं ListFragmentतो आपको इसके लिए भी एक आधार वर्ग बनाना चाहिए। आप के उपयोग के बारे में स्पष्ट हो जाएगा onBackPressed()औरonActivityResult() यदि आप पोस्ट को पूरा पढ़ेंगे ..

2. प्रोजेक्ट में हर जगह सुलभ कुछ टैब पहचानकर्ता बनाएं

public class AppConstants{
    public static final String TAB_A  = "tab_a_identifier";
    public static final String TAB_B  = "tab_b_identifier";

    //Your other constants, if you have them..
}

यहाँ समझाने के लिए कुछ नहीं ..

3. ठीक है, मुख्य टैब गतिविधि- कृपया कोड में टिप्पणियों के माध्यम से जाएं ..

public class AppMainFragmentActivity extends FragmentActivity{
    /* Your Tab host */
    private TabHost mTabHost;

    /* A HashMap of stacks, where we use tab identifier as keys..*/
    private HashMap<String, Stack<Fragment>> mStacks;

    /*Save current tabs identifier in this..*/
    private String mCurrentTab;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.app_main_tab_fragment_layout);

        /*  
         *  Navigation stacks for each tab gets created.. 
         *  tab identifier is used as key to get respective stack for each tab
         */
        mStacks             =   new HashMap<String, Stack<Fragment>>();
        mStacks.put(AppConstants.TAB_A, new Stack<Fragment>());
        mStacks.put(AppConstants.TAB_B, new Stack<Fragment>());

        mTabHost                =   (TabHost)findViewById(android.R.id.tabhost);
        mTabHost.setOnTabChangedListener(listener);
        mTabHost.setup();

        initializeTabs();
    }


    private View createTabView(final int id) {
        View view = LayoutInflater.from(this).inflate(R.layout.tabs_icon, null);
        ImageView imageView =   (ImageView) view.findViewById(R.id.tab_icon);
        imageView.setImageDrawable(getResources().getDrawable(id));
        return view;
    }

    public void initializeTabs(){
        /* Setup your tab icons and content views.. Nothing special in this..*/
        TabHost.TabSpec spec    =   mTabHost.newTabSpec(AppConstants.TAB_A);
        mTabHost.setCurrentTab(-3);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_home_state_btn));
        mTabHost.addTab(spec);


        spec                    =   mTabHost.newTabSpec(AppConstants.TAB_B);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_status_state_btn));
        mTabHost.addTab(spec);
    }


    /*Comes here when user switch tab, or we do programmatically*/
    TabHost.OnTabChangeListener listener    =   new TabHost.OnTabChangeListener() {
      public void onTabChanged(String tabId) {
        /*Set current tab..*/
        mCurrentTab                     =   tabId;

        if(mStacks.get(tabId).size() == 0){
          /*
           *    First time this tab is selected. So add first fragment of that tab.
           *    Dont need animation, so that argument is false.
           *    We are adding a new fragment which is not present in stack. So add to stack is true.
           */
          if(tabId.equals(AppConstants.TAB_A)){
            pushFragments(tabId, new AppTabAFirstFragment(), false,true);
          }else if(tabId.equals(AppConstants.TAB_B)){
            pushFragments(tabId, new AppTabBFirstFragment(), false,true);
          }
        }else {
          /*
           *    We are switching tabs, and target tab is already has atleast one fragment. 
           *    No need of animation, no need of stack pushing. Just show the target fragment
           */
          pushFragments(tabId, mStacks.get(tabId).lastElement(), false,false);
        }
      }
    };


    /* Might be useful if we want to switch tab programmatically, from inside any of the fragment.*/
    public void setCurrentTab(int val){
          mTabHost.setCurrentTab(val);
    }


    /* 
     *      To add fragment to a tab. 
     *  tag             ->  Tab identifier
     *  fragment        ->  Fragment to show, in tab identified by tag
     *  shouldAnimate   ->  should animate transaction. false when we switch tabs, or adding first fragment to a tab
     *                      true when when we are pushing more fragment into navigation stack. 
     *  shouldAdd       ->  Should add to fragment navigation stack (mStacks.get(tag)). false when we are switching tabs (except for the first time)
     *                      true in all other cases.
     */
    public void pushFragments(String tag, Fragment fragment,boolean shouldAnimate, boolean shouldAdd){
      if(shouldAdd)
          mStacks.get(tag).push(fragment);
      FragmentManager   manager         =   getSupportFragmentManager();
      FragmentTransaction ft            =   manager.beginTransaction();
      if(shouldAnimate)
          ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
      ft.replace(R.id.realtabcontent, fragment);
      ft.commit();
    }


    public void popFragments(){
      /*    
       *    Select the second last fragment in current tab's stack.. 
       *    which will be shown after the fragment transaction given below 
       */
      Fragment fragment             =   mStacks.get(mCurrentTab).elementAt(mStacks.get(mCurrentTab).size() - 2);

      /*pop current fragment from stack.. */
      mStacks.get(mCurrentTab).pop();

      /* We have the target fragment in hand.. Just show it.. Show a standard navigation animation*/
      FragmentManager   manager         =   getSupportFragmentManager();
      FragmentTransaction ft            =   manager.beginTransaction();
      ft.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right);
      ft.replace(R.id.realtabcontent, fragment);
      ft.commit();
    }   


    @Override
    public void onBackPressed() {
        if(mStacks.get(mCurrentTab).size() == 1){
          // We are already showing first fragment of current tab, so when back pressed, we will finish this activity..
          finish();
          return;
        }

        /*  Each fragment represent a screen in application (at least in my requirement, just like an activity used to represent a screen). So if I want to do any particular action
         *  when back button is pressed, I can do that inside the fragment itself. For this I used AppBaseFragment, so that each fragment can override onBackPressed() or onActivityResult()
         *  kind of events, and activity can pass it to them. Make sure just do your non navigation (popping) logic in fragment, since popping of fragment is done here itself.
         */
        ((AppBaseFragment)mStacks.get(mCurrentTab).lastElement()).onBackPressed();

        /* Goto previous fragment in navigation stack of this tab */
            popFragments();
    }


    /*
     *   Imagine if you wanted to get an image selected using ImagePicker intent to the fragment. Ofcourse I could have created a public function
     *  in that fragment, and called it from the activity. But couldn't resist myself.
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(mStacks.get(mCurrentTab).size() == 0){
            return;
        }

        /*Now current fragment on screen gets onActivityResult callback..*/
        mStacks.get(mCurrentTab).lastElement().onActivityResult(requestCode, resultCode, data);
    }
}

4. app_main_tab_fragment_layout.xml (यदि कोई रुचि रखता है तो।)

<?xml version="1.0" encoding="utf-8"?>
<TabHost
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="0"/>

        <FrameLayout
            android:id="@+android:id/realtabcontent"
            android:layout_width="fill_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>

        <TabWidget
            android:id="@android:id/tabs"
            android:orientation="horizontal"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0"/>

    </LinearLayout>
</TabHost>

5. AppTabAFirstFragment.java (टैब ए में पहला टुकड़ा, सभी टैब के लिए सिमलीयर)

public class AppTabAFragment extends BaseFragment {
    private Button mGotoButton;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view       =   inflater.inflate(R.layout.fragment_one_layout, container, false);

        mGoToButton =   (Button) view.findViewById(R.id.goto_button);
        mGoToButton.setOnClickListener(listener);

        return view;
    }

    private OnClickListener listener        =   new View.OnClickListener(){
        @Override
        public void onClick(View v){
            /* Go to next fragment in navigation stack*/
            mActivity.pushFragments(AppConstants.TAB_A, new AppTabAFragment2(),true,true);
        }
    }
}

यह सबसे पॉलिश और सही तरीका नहीं हो सकता है। लेकिन इसने मेरे मामले में खूबसूरती से काम किया। इसके अलावा मुझे केवल पोर्ट्रेट मोड में इसकी आवश्यकता थी। मुझे कभी भी इस कोड का उपयोग किसी परियोजना में दोनों उन्मुखीकरण का समर्थन करने के लिए नहीं करना पड़ा। इसलिए मैं यह नहीं कह सकता कि मुझे किस तरह की चुनौतियों का सामना करना पड़ता है।

संपादित करें:

अगर किसी को एक पूर्ण परियोजना चाहिए, तो मैंने एक नमूना परियोजना को जीथब पर धकेल दिया है ।


2
हर टुकड़े के लिए डेटा संग्रहीत करना, उनमें से हर एक को फिर से बनाना, ढेर का पुनर्निर्माण करना ... एक साधारण अभिविन्यास परिवर्तन के लिए इतना काम।
माइकल इलर्स स्मिथ

3
@omegatai आपके साथ पूरी तरह से सहमत है .. सभी समस्याएँ उत्पन्न होती हैं, क्योंकि एंड्रॉइड हमारे लिए स्टैक का प्रबंधन नहीं करता है ( जो iOS करता है और कई खंडों के साथ ओरिएंटेशन परिवर्तन या टैब एक हवा है ) और जो हमें इस क्यू में मूल चर्चा में वापस ले जाता है / एक धागा। अब इससे अच्छा कोई नहीं जा सकता ..
कृष्णभद्र

1
@Renjith इसका कारण यह है कि हर बार जब आप टैब स्विच करते हैं, तो खंडन को फिर से बनाया जाता है .. एक बार भी मत सोचो, टैब स्विच के दौरान आपके टुकड़े का पुन: उपयोग किया जाता है। जब मैं A टैब से B पर स्विच करता हूं, A टैब को मेमोरी से मुक्त किया जाता है। इसलिए अपने डेटा को गतिविधि में सहेजें, और हर बार जांचें कि गतिविधि में सर्वर से प्राप्त करने का प्रयास करने से पहले डेटा है या नहीं।
कृष्णभद्र '

2
@ कृष्णभद्र ठीक है, जो बहुत बेहतर लगता है। गलत होने पर मुझे सही करने दें। आपके उदाहरण के अनुसार, केवल एक गतिविधि है और इसलिए एक बंडल है। बेसफ्रेगमेंट (अपनी परियोजना का उल्लेख करते हुए) में एडेप्टर इंस्टेंस बनाएं और उसमें डेटा को सेव करें। जब भी दृश्य बनाना हो तो उनका उपयोग करें।
रंजीत

1
काम करने के लिए मिल गया। बहुत बहुत धन्यवाद। पूरे प्रोजेक्ट को अपलोड करना एक अच्छा विचार था! :-)
विनय डब्ल्यू।

96

हमें वास्तव में उसी व्यवहार को लागू करना था जिसे आप हाल ही में किसी एप्लिकेशन के लिए वर्णन करते हैं। स्क्रीन और एप्लिकेशन के समग्र प्रवाह को पहले से ही परिभाषित किया गया था इसलिए हमें इसके साथ रहना पड़ा (यह एक आईओएस ऐप क्लोन है ...)। सौभाग्य से, हम ऑन-स्क्रीन बैक बटन से छुटकारा पाने में कामयाब रहे :)

हमने TabActivity, FragmentActivities (हम टुकड़े के लिए समर्थन पुस्तकालय का उपयोग कर रहे थे) और Fragments के मिश्रण का उपयोग करके समाधान को हैक किया। पूर्वव्यापी में, मुझे पूरा यकीन है कि यह सबसे अच्छा वास्तुकला निर्णय नहीं था, लेकिन हम काम करने में सफल रहे। अगर मुझे इसे फिर से करना पड़ा, तो मैं शायद एक और गतिविधि-आधारित समाधान (कोई टुकड़े नहीं) करने की कोशिश करूंगा, या कोशिश करूंगा और टैब के लिए केवल एक ही गतिविधि होगी और बाकी सभी दृश्यों को देखने दूंगा (जो मुझे लगता है कि बहुत अधिक हैं समग्र गतिविधियों की तुलना में पुन: प्रयोज्य)।

इसलिए आवश्यकताएं प्रत्येक टैब में कुछ टैब और नेस्टेबल स्क्रीन रखने की थीं:

tab 1
  screen 1 -> screen 2 -> screen 3
tab 2
  screen 4
tab 3
  screen 5 -> 6

आदि...

तो कहते हैं: उपयोगकर्ता टैब 1 में शुरू होता है, स्क्रीन 1 से स्क्रीन 2 तक फिर स्क्रीन 3 पर नेविगेट करता है, वह तब टैब 3 पर स्विच करता है और स्क्रीन 4 से 6 तक नेविगेट करता है; यदि टैब 1 पर वापस स्विच किया जाता है, तो उसे स्क्रीन 3 को फिर से देखना चाहिए और यदि उसने वापस दबाया तो उसे स्क्रीन 2 पर वापस आना चाहिए; वापस फिर से और वह स्क्रीन 1 में है; टैब 3 पर जाएँ और वह फिर से स्क्रीन 6 में है।

आवेदन में मुख्य गतिविधि MainTabActivity है, जो TabActivity का विस्तार करती है। प्रत्येक टैब एक गतिविधि के साथ जुड़ा हुआ है, बताएं ActivateTab1, 2 और 3. और फिर प्रत्येक स्क्रीन एक टुकड़ा होगा:

MainTabActivity
  ActivityInTab1
    Fragment1 -> Fragment2 -> Fragment3
  ActivityInTab2
    Fragment4
  ActivityInTab3
    Fragment5 -> Fragment6

प्रत्येक गतिविधिइनटैब एक समय में केवल एक ही टुकड़ा रखता है, और जानता है कि एक टुकड़े को दूसरे एक के लिए कैसे बदलना है (एक एक्टविटीग्रुप के समान ही)। अच्छी बात यह है कि इस तरह से प्रत्येक टैब के लिए अलग-अलग बैक स्टैक को मंटेन करना काफी आसान है।

प्रत्येक एक्टिविटी इनटैब के लिए कार्यक्षमता काफी समान थी: पता है कि एक टुकड़े से दूसरे में कैसे नेविगेट करें और एक बैक स्टैक बनाए रखें, इसलिए हमने इसे बेस क्लास में रखा। आइए इसे सिर्फ़ एक्टिविटीइनटैब कहें:

abstract class ActivityInTab extends FragmentActivity { // FragmentActivity is just Activity for the support library.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_in_tab);
    }

    /**
     * Navigates to a new fragment, which is added in the fragment container
     * view.
     * 
     * @param newFragment
     */
    protected void navigateTo(Fragment newFragment) {
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction ft = manager.beginTransaction();

        ft.replace(R.id.content, newFragment);

        // Add this transaction to the back stack, so when the user presses back,
        // it rollbacks.
        ft.addToBackStack(null);
        ft.commit();
    }

}

Activity_in_tab.xml सिर्फ यह है:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/content"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:isScrollContainer="true">
</RelativeLayout>

जैसा कि आप देख सकते हैं, प्रत्येक टैब के लिए दृश्य लेआउट समान था। ऐसा इसलिए है क्योंकि यह केवल एक फ्रेम-आउट नामक सामग्री है जो प्रत्येक टुकड़े को धारण करेगी। अंश वे होते हैं जिनमें प्रत्येक स्क्रीन का दृश्य होता है।

बस बोनस अंक के लिए, हमने उपयोगकर्ता को वापस दबाए जाने पर एक पुष्टिकरण संवाद दिखाने के लिए कुछ छोटे कोड भी जोड़े हैं और वापस जाने के लिए अधिक टुकड़े नहीं हैं:

// In ActivityInTab.java...
@Override
public void onBackPressed() {
    FragmentManager manager = getSupportFragmentManager();
    if (manager.getBackStackEntryCount() > 0) {
        // If there are back-stack entries, leave the FragmentActivity
        // implementation take care of them.
        super.onBackPressed();
    } else {
        // Otherwise, ask user if he wants to leave :)
        showExitDialog();
    }
}

यह बहुत ज्यादा सेटअप है। जैसा कि आप देख सकते हैं, प्रत्येक FragmentActivity (या बस Android में गतिविधि> 3) सभी बैक-स्टैकिंग का ख्याल रख रही है, जिसके साथ वह खुद FragmentManager है।

ActivityInTab1 जैसी गतिविधि वास्तव में सरल होगी, यह सिर्फ यह दिखाएगा कि यह पहला टुकड़ा है (यानी स्क्रीन):

public class ActivityInTab1 extends ActivityInTab {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        navigateTo(new Fragment1());
    }
}

फिर, अगर एक टुकड़े को दूसरे टुकड़े में नेविगेट करने की आवश्यकता होती है, तो उसे थोड़ा बुरा कास्टिंग करना पड़ता है ... लेकिन यह उतना बुरा नहीं है:

// In Fragment1.java for example...
// Need to navigate to Fragment2.
((ActivityIntab) getActivity()).navigateTo(new Fragment2());

तो यह बहुत सुंदर है। मुझे पूरा यकीन है कि यह एक बहुत ही विहित नहीं है (और ज्यादातर निश्चित रूप से बहुत अच्छा नहीं) समाधान है, इसलिए मैं अनुभवी एंड्रॉइड डेवलपर्स से पूछना चाहता हूं कि इस कार्यक्षमता को प्राप्त करने के लिए एक बेहतर दृष्टिकोण क्या होगा, और यदि यह "यह कैसे नहीं है" किया "एंड्रॉयड में, मैं अगर तुम मुझे कुछ लिंक या सामग्री बताते हैं कि जो है को इंगित कर सकता है कि सराहना करेंगे इस (टैब, टैब में नेस्टेड स्क्रीन, आदि) के पास जाने एंड्रॉयड रास्ता। टिप्पणी में इस जवाब को अलग करने के लिए स्वतंत्र महसूस करें :)

एक संकेत के रूप में कि यह समाधान बहुत अच्छा नहीं है, हाल ही में मुझे कुछ नेविगेशन कार्यक्षमता को एप्लिकेशन में जोड़ना था। कुछ विचित्र बटन जो उपयोगकर्ता को एक टैब से दूसरे और नेस्टेड स्क्रीन में ले जाने चाहिए। उस प्रोग्राम को करना बट में एक दर्द था, क्योंकि कौन जानता है-कौन-कौन समस्याएँ हैं और इनसे निपटना कब टुकड़े और गतिविधियाँ वास्तव में तुरंत और आरंभिक हैं। मुझे लगता है कि यह बहुत आसान होता अगर उन स्क्रीन और टैब में सिर्फ व्यू होते।


अंत में, यदि आपको अभिविन्यास परिवर्तन से बचने की आवश्यकता है, तो यह महत्वपूर्ण है कि आपके टुकड़े setArguments / getArguments का उपयोग करके बनाए जाते हैं। यदि आप अपने टुकड़ों के कंस्ट्रक्टर्स में इंस्टेंस वेरिएबल सेट करते हैं तो आप खराब हो जाएंगे। लेकिन सौभाग्य से यह वास्तव में ठीक करना आसान है: बस निर्माता में सेटआर्गुमेंट्स में सब कुछ बचाएं और फिर उन चीजों को पुनः प्राप्त करें जिनके साथ onAreate का उपयोग करें।


13
महान जवाब लेकिन मुझे लगता है कि बहुत कम लोग इसे देखेंगे। मैंने बिल्कुल वही रास्ता चुना (जैसा कि आप पिछले उत्तर में बातचीत से देख सकते हैं) और मैं आपकी तरह इससे खुश नहीं हूं। मुझे लगता है कि Google वास्तव में इस टुकड़े के साथ खराब हो गया है क्योंकि यह एपीआई प्रमुख उपयोग के मामलों को कवर नहीं करता है। एक और समस्या जिसे आप चला सकते हैं, वह है टुकड़े को दूसरे टुकड़े में एम्बेड करने की असंभवता।
बजे दिमित्री रयदेंको

टिप्पणी के लिए धन्यवाद बोल्डर। हाँ, मैं टुकड़े API के बारे में अधिक सहमत नहीं हो सकता। मैं पहले से ही नेस्टेड अंशों की समस्या में भाग चुका हूं (इसीलिए हम "एक टुकड़े को दूसरे के साथ बदलना" दृष्टिकोण के लिए चले गए)।
महामारी

1
मैंने इसे सभी गतिविधियों के माध्यम से लागू किया है। मुझे पसंद नहीं आया कि मुझे क्या मिला और मैं फ्रेग्मेंट्स की कोशिश करने जा रहा हूं। यह आपके अनुभव के विपरीत है! प्रत्येक टैब में बच्चे के विचारों के जीवनचक्र को संभालने के लिए और अपने स्वयं के बैक बटन को लागू करने के लिए गतिविधियों के साथ बहुत सारे कार्यान्वयन हैं। इसके अलावा, आप केवल सभी विचारों का संदर्भ नहीं रख सकते हैं या आप स्मृति को उड़ा देंगे। मुझे उम्मीद है कि Fragments will: 1) मेमोरी के स्पष्ट पृथक्करण के साथ Fragments के जीवनचक्र का समर्थन करें और 2) बैक बटन फंक्शनलिटी को लागू करने में मदद करें, यदि आप इस प्रक्रिया के लिए टुकड़ों का उपयोग करते हैं तो टैबलेट पर चलाना आसान नहीं होगा?
ग्रिग्म

उपयोगकर्ता द्वारा टैब बंद करने पर क्या होता है? क्या फ्रैग्मेंट बैकस्ट डिलीट हो जाता है? कैसे सुनिश्चित करें कि बैकस्ट बनी हुई है?
gregm

1
यदि आप 1 टैब पर जाते हैं, तो आप ऐसा कर सकते हैं, जैसा कि मैंने किया था, क्योंकि प्रत्येक टैब के लिए बैकस्टैक तब बना रहेगा, क्योंकि गतिविधियाँ वास्तव में जीवित रहती हैं; उन्हें केवल रोका गया और फिर से शुरू किया गया। मुझे पता नहीं है कि टैबएक्टिविटी में टैब बंद होने पर गतिविधियों को नष्ट करने और फिर से बनाने का एक तरीका है। हालांकि, अगर आप गतिविधियों के अंदर के टुकड़े बनाते हैं, जैसे कि मैंने सुझाया, तो वे नष्ट हो जाते हैं (और बैकस्टैक पॉप होने पर फिर से बनाया जाता है)। तो आपके पास किसी भी समय प्रति टैब में अधिकतम एक टुकड़ा होगा।
1


6

टुकड़ों के लिए मजबूत संदर्भ संग्रहीत करना सही तरीका नहीं है।

FragmentManager प्रदान करता है putFragment(Bundle, String, Fragment)और saveFragmentInstanceState(Fragment)

या तो एक बैकस्टैक को लागू करने के लिए पर्याप्त है।


उपयोग putFragmentकरने के बजाय, एक टुकड़े को बदलने के बजाय, आप पुराने को अलग करते हैं और नए को जोड़ते हैं। यह वही है जो फ्रेमवर्क एक बदले हुए लेन-देन को करता है जिसे बैकस्ट में जोड़ा जाता है। putFragmentएक सूचकांक को सक्रिय फ़्रैगमेंट की वर्तमान सूची में संग्रहीत करता है और उन फ़्रैगमेंट को अभिविन्यास परिवर्तनों के दौरान फ्रेमवर्क द्वारा सहेजा जाता है।

दूसरा तरीका, उपयोग करते हुए saveFragmentInstanceState, पूरे टुकड़े अवस्था को एक बंडल में सहेजता है, जिससे आप वास्तव में इसे हटा सकते हैं, न कि अलग करने के बजाय। इस दृष्टिकोण का उपयोग करने से बैक स्टैक को हेरफेर करना आसान हो जाता है, जैसा कि आप जब चाहें तब एक फ्रैगमेंट पॉप कर सकते हैं।


मैंने इस usecase के लिए दूसरी विधि का उपयोग किया:

SignInFragment ----> SignUpFragment ---> ChooseBTDeviceFragment
               \                          /
                \------------------------/

मैं नहीं चाहता कि उपयोगकर्ता तीसरे बटन से, बैक बटन दबाकर, साइन अप स्क्रीन पर लौटें। मैं उनके (उपयोग onCreateAnimation) के बीच फ्लिप एनिमेशन भी करता हूं , इसलिए हैकी समाधान काम नहीं करेगा, उपयोगकर्ता के बिना कम से कम कुछ स्पष्ट नहीं है।

यह कस्टम बैकस्टैक के लिए एक वैध उपयोग मामला है, जो उपयोगकर्ता अपेक्षा करता है ...

private static final String STATE_BACKSTACK = "SetupActivity.STATE_BACKSTACK";

private MyBackStack mBackStack;

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

    if (state == null) {
        mBackStack = new MyBackStack();

        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction tr = fm.beginTransaction();
        tr.add(R.id.act_base_frg_container, new SignInFragment());
        tr.commit();
    } else {
        mBackStack = state.getParcelable(STATE_BACKSTACK);
    }
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putParcelable(STATE_BACKSTACK, mBackStack);
}

private void showFragment(Fragment frg, boolean addOldToBackStack) {
    final FragmentManager fm = getSupportFragmentManager();
    final Fragment oldFrg = fm.findFragmentById(R.id.act_base_frg_container);

    FragmentTransaction tr = fm.beginTransaction();
    tr.replace(R.id.act_base_frg_container, frg);
    // This is async, the fragment will only be removed after this returns
    tr.commit();

    if (addOldToBackStack) {
        mBackStack.push(fm, oldFrg);
    }
}

@Override
public void onBackPressed() {
    MyBackStackEntry entry;
    if ((entry = mBackStack.pop()) != null) {
        Fragment frg = entry.recreate(this);

        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction tr = fm.beginTransaction();
        tr.replace(R.id.act_base_frg_container, frg);
        tr.commit();

        // Pop it now, like the framework implementation.
        fm.executePendingTransactions();
    } else {
        super.onBackPressed();
    }
}

public class MyBackStack implements Parcelable {

    private final List<MyBackStackEntry> mList;

    public MyBackStack() {
        mList = new ArrayList<MyBackStackEntry>(4);
    }

    public void push(FragmentManager fm, Fragment frg) {
        push(MyBackStackEntry.newEntry(fm, frg);
    }

    public void push(MyBackStackEntry entry) {
        if (entry == null) {
            throw new NullPointerException();
        }
        mList.add(entry);
    }

    public MyBackStackEntry pop() {
        int idx = mList.size() - 1;
        return (idx != -1) ? mList.remove(idx) : null;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        final int len = mList.size();
        dest.writeInt(len);
        for (int i = 0; i < len; i++) {
            // MyBackStackEntry's class is final, theres no
            // need to use writeParcelable
            mList.get(i).writeToParcel(dest, flags);
        }
    }

    protected MyBackStack(Parcel in) {
        int len = in.readInt();
        List<MyBackStackEntry> list = new ArrayList<MyBackStackEntry>(len);
        for (int i = 0; i < len; i++) {
            list.add(MyBackStackEntry.CREATOR.createFromParcel(in));
        }
        mList = list;
    }

    public static final Parcelable.Creator<MyBackStack> CREATOR =
        new Parcelable.Creator<MyBackStack>() {

            @Override
            public MyBackStack createFromParcel(Parcel in) {
                return new MyBackStack(in);
            }

            @Override
            public MyBackStack[] newArray(int size) {
                return new MyBackStack[size];
            }
    };
}

public final class MyBackStackEntry implements Parcelable {

    public final String fname;
    public final Fragment.SavedState state;
    public final Bundle arguments;

    public MyBackStackEntry(String clazz, 
            Fragment.SavedState state,
            Bundle args) {
        this.fname = clazz;
        this.state = state;
        this.arguments = args;
    }

    public static MyBackStackEntry newEntry(FragmentManager fm, Fragment frg) {
        final Fragment.SavedState state = fm.saveFragmentInstanceState(frg);
        final String name = frg.getClass().getName();
        final Bundle args = frg.getArguments();
        return new MyBackStackEntry(name, state, args);
    }

    public Fragment recreate(Context ctx) {
        Fragment frg = Fragment.instantiate(ctx, fname);
        frg.setInitialSavedState(state);
        frg.setArguments(arguments);
        return frg;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(fname);
        dest.writeBundle(arguments);

        if (state == null) {
            dest.writeInt(-1);
        } else if (state.getClass() == Fragment.SavedState.class) {
            dest.writeInt(0);
            state.writeToParcel(dest, flags);
        } else {
            dest.writeInt(1);
            dest.writeParcelable(state, flags);
        }
    }

    protected MyBackStackEntry(Parcel in) {
        final ClassLoader loader = getClass().getClassLoader();
        fname = in.readString();
        arguments = in.readBundle(loader);

        switch (in.readInt()) {
            case -1:
                state = null;
                break;
            case 0:
                state = Fragment.SavedState.CREATOR.createFromParcel(in);
                break;
            case 1:
                state = in.readParcelable(loader);
                break;
            default:
                throw new IllegalStateException();
        }
    }

    public static final Parcelable.Creator<MyBackStackEntry> CREATOR =
        new Parcelable.Creator<MyBackStackEntry>() {

            @Override
            public MyBackStackEntry createFromParcel(Parcel in) {
                return new MyBackStackEntry(in);
            }

            @Override
            public MyBackStackEntry[] newArray(int size) {
                return new MyBackStackEntry[size];
            }
    };
}

2

अस्वीकरण:


मुझे लगता है कि यह एक संबंधित समाधान पोस्ट करने के लिए सबसे अच्छी जगह है जो मैंने इसी तरह की समस्या के लिए काम किया है जो कि सुंदर मानक एंड्रॉइड सामान प्रतीत होता है। यह सभी के लिए समस्या को हल करने वाला नहीं है, लेकिन यह कुछ मदद कर सकता है।


यदि आपके टुकड़ों के बीच प्राथमिक अंतर केवल डेटा है जो उन्हें बैकअप दे रहा है (यानी, बहुत बड़े लेआउट अंतर नहीं हैं), तो आपको वास्तव में टुकड़े को बदलने की आवश्यकता नहीं हो सकती है, लेकिन केवल अंतर्निहित डेटा को स्वैप करें और दृश्य को ताज़ा करें।

इस दृष्टिकोण के लिए एक संभावित उदाहरण का वर्णन इस प्रकार है:

मेरे पास एक ऐप है जो लिस्ट व्यू का उपयोग करता है। सूची में प्रत्येक आइटम कुछ बच्चों की संख्या के साथ एक अभिभावक है। जब आप आइटम को टैप करते हैं, तो एक नई सूची को उन बच्चों के साथ खोलने की आवश्यकता होती है, जो मूल सूची के समान एक्शनबार टैब के भीतर हैं। इन नेस्टेड सूचियों में एक समान लेआउट है (कुछ सशर्त यहाँ और वहाँ शायद), लेकिन डेटा अलग है।

इस ऐप में प्रारंभिक मूल सूची के नीचे संतानों की कई परतें हैं और जब उपयोगकर्ता पहले से परे किसी निश्चित गहराई तक पहुंचने का प्रयास करता है, तब तक हमारे पास सर्वर से डेटा नहीं हो सकता है या नहीं हो सकता है। क्योंकि सूची एक डेटाबेस कर्सर से निर्मित है, और टुकड़े एक लोडर और कर्सर एडाप्टर का उपयोग सूची आइटम के साथ सूची दृश्य को पॉप्युलेट करने के लिए करते हैं, यह सब तब होता है जब एक क्लिक पंजीकृत होने की आवश्यकता होती है:

1) नए 'से' और 'से' फ़ील्ड के साथ एक नया एडेप्टर बनाएं जो सूची में जोड़े जा रहे नए आइटम दृश्यों और नए कर्सर द्वारा लौटाए गए कॉलम से मेल खाएगा।

2) इस एडॉप्टर को ListView के नए एडेप्टर के रूप में सेट करें।

3) क्लिक किए गए आइटम के आधार पर एक नया यूआरआई बनाएं और नए यूआरआई (और प्रोजेक्शन) के साथ कर्सर लोडर को पुनरारंभ करें। इस उदाहरण में, यूआरआई को यूआई से पास किए गए चयन आर्ग के साथ विशिष्ट प्रश्नों के लिए मैप किया जाता है।

4) जब यूआरआई से नया डेटा लोड किया गया है, तो एडेप्टर से जुड़े कर्सर को नए कर्सर पर स्वैप करें, और फिर सूची ताज़ा हो जाएगी।

लेन-देन का उपयोग नहीं कर रहे हैं, इसलिए इसके साथ कोई बैकस्टैक जुड़ा नहीं है, इसलिए आपको पदानुक्रम से बाहर निकलते समय या तो अपना स्वयं का निर्माण करना होगा, या रिवर्स में प्रश्नों को खेलना होगा। जब मैंने यह कोशिश की, तो प्रश्न काफी तेज थे कि मैं उन्हें ओएनबैकपाइड () में तब तक फिर से करता हूं जब तक कि मैं पदानुक्रम के शीर्ष पर नहीं हूं, जिस बिंदु पर फ्रेम बैक बटन पर फिर से काम करता है।

यदि आप खुद को एक समान स्थिति में पाते हैं, तो डॉक्स पढ़ना सुनिश्चित करें: http://developer.android.com/guide/topics/ui/layout/listview.html

http://developer.android.com/reference/android/support/v4/app/LoaderManager.LoaderCallbacks.html

मुझे उम्मीद है इससे किसी को सहायता मिलेगी!


यदि कोई ऐसा कर रहा है, और एक सेक्शनएक्सएक्सर (जैसे कि अल्फाबेटइंडेक्सर) का भी उपयोग कर रहा है, तो आप देख सकते हैं कि एडेप्टर को बदलने के बाद, आपका फास्ट स्क्रॉल काम नहीं करता है। एक दुर्भाग्यपूर्ण बग की तरह, लेकिन एडेप्टर की जगह, यहां तक ​​कि एक ब्रांड के नए इंडेक्सर के साथ, फास्टस्क्रोल द्वारा उपयोग किए जाने वाले अनुभागों की सूची को अपडेट नहीं करता है। वर्कअराउंड है, तो कृपया देखें: समस्या और वर्कअराउंड
कोर्टऑफ

2

मुझे वास्तव में एक ही समस्या थी और एक खुला स्रोत गितुब परियोजना लागू की जो स्टैक्ड टैब, बैक और अप नेविगेशन को कवर करती है और इसका अच्छी तरह से परीक्षण और दस्तावेजीकरण किया जाता है:

https://github.com/SebastianBaltesObjectCode/PersistentFragmentTabs

यह नेविगेशन टैब और टुकड़ा स्विचिंग और ऊपर और पीछे नेविगेशन की हैंडलिंग के लिए एक सरल और छोटा ढांचा है। प्रत्येक टैब के टुकड़ों का अपना स्टैक होता है। यह ActionBarSherlock का उपयोग करता है और API स्तर 8 पर वापस संगत है।


2

यह एक जटिल समस्या है क्योंकि एंड्रॉइड केवल 1 बैक स्टैक को संभालता है, लेकिन यह संभव है। टैब स्टैकर नामक एक पुस्तकालय बनाने में मुझे कई दिन लग गए, जो आपको वही दिख रहा है: प्रत्येक टैब के लिए एक खंड इतिहास। यह खुला स्रोत है और पूरी तरह से प्रलेखित है, और आसानी से ग्रेडेल के साथ शामिल किया जा सकता है। आप पुस्तकालय को github: https://github.com/smart-fun/TabStacker पर पा सकते हैं

आप यह देखने के लिए नमूना एप्लिकेशन डाउनलोड कर सकते हैं कि व्यवहार आपकी आवश्यकताओं के अनुरूप है:

https://play.google.com/apps/testing/fr.arnaudguyon.tabstackerapp

यदि आपके पास कोई प्रश्न है तो एक मेल ड्रॉप करने में संकोच न करें।


2

यदि कोई देख रहा है तो मैं अपना खुद का समाधान सुझाना चाहता हूं और उसकी जरूरतों के लिए सबसे अच्छा प्रयास करना चाहता हूं।

https://github.com/drusak/tabactivity

पुस्तकालय बनाने का उद्देश्य काफी सामान्य है - इसे iPhone की तरह लागू करें।

मुख्य लाभ:

  • TabLayout के साथ android.support.design लाइब्रेरी का उपयोग करें;
  • प्रत्येक टैब में FragmentManager (टुकड़ों के संदर्भों को सहेजे बिना) का उपयोग करके अपना स्वयं का स्टैक है;
  • गहरी जोड़ने के लिए समर्थन (जब आपको विशिष्ट टैब खोलने की आवश्यकता होती है और इसमें विशिष्ट टुकड़े का स्तर होता है);
  • टैब की स्थिति को बचाने / बहाल करने;
  • टैब में टुकड़ों के अनुकूल जीवन चक्र के तरीके;
  • अपनी आवश्यकताओं के लिए लागू करना काफी आसान है।

धन्यवाद, यह काफी मददगार रहा है। मुझे ListFragments के अतिरिक्त s का उपयोग करने की आवश्यकता है Fragmentइसलिए मैंने BaseTabLragFragment.java से BaseTabFragment.java को डुप्लिकेट किया और इसे ListFragment का विस्तार किया। फिर मुझे कोड में विभिन्न भागों को बदलना पड़ा, जहां हमेशा एक बेसटैब्रैगमेंट की अपेक्षा की जाती थी। क्या कोई बेहतर तरीका है?
प्रथालो

दुर्भाग्य से, ListFragment के बारे में नहीं सोचा। तकनीकी रूप से यह सही समाधान है, लेकिन इसके लिए TabFragment और इसके उदाहरण के लिए अतिरिक्त जाँच की आवश्यकता होगी। BaseTabListFragment। एक और दृष्टिकोण लिस्ट व्यू के अंदर फ्रैगमेंट का उपयोग करने के लिए (बिल्कुल लिस्ट फ़्रेग्मेंट के समान)। मैं इस पर सोचूंगा। मुझे उस पर इशारा करने के लिए धन्यवाद!
कासुर्द

1

एक सरल उपाय:

हर बार जब आप टैब / रूट व्यू कॉल बदलते हैं:

fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);

यह बैकस्टैक को साफ कर देगा। रूट टुकड़ा बदलने से पहले इसे कॉल करना याद रखें।

और इसके साथ अंश जोड़ें:

FragmentTransaction transaction = getFragmentManager().beginTransaction();
NewsDetailsFragment newsDetailsFragment = NewsDetailsFragment.newInstance(newsId);
transaction.add(R.id.content_frame, newsDetailsFragment).addToBackStack(null).commit();

नोट .addToBackStack(null)और transaction.addजैसे उदाहरण के साथ बदला जा सकता है transaction.replace


-1

यह धागा बहुत ही रोचक और उपयोगी था।
आपके स्पष्टीकरण और कोड के लिए धन्यवाद कृष्णभद्र, मैं आपके कोड का उपयोग करता हूं और थोड़ा सुधार किया है, जिससे स्टैक, करंटटैब, आदि को बदलने की अनुमति मिलती है ... परिवर्तन कॉन्फ़िगरेशन (मुख्य रूप से घूर्णन) से।
एक वास्तविक 4.0.4 और 2.3.6 उपकरणों पर परीक्षण किया गया, एमुलेटर पर परीक्षण नहीं किया गया

मैं "AppMainTabActivity.java" पर कोड के इस हिस्से को बदलता हूं, बाकी सभी समान रहते हैं। शायद कृष्णभद्र इसे अपने कोड में जोड़ देंगे।

डेटा पुनर्प्राप्त करें:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.app_main_tab_fragment_layout);

    /*  
     *  Navigation stacks for each tab gets created..
     *  tab identifier is used as key to get respective stack for each tab
     */

  //if we are recreating this activity...
    if (savedInstanceState!=null) {
         mStacks = (HashMap<String, Stack<Fragment>>) savedInstanceState.get("stack");
         mCurrentTab = savedInstanceState.getString("currentTab");
    }
    else {
    mStacks = new HashMap<String, Stack<Fragment>>();
    mStacks.put(AppConstants.TAB_A, new Stack<Fragment>());
    mStacks.put(AppConstants.TAB_B, new Stack<Fragment>());

    }

    mTabHost = (TabHost)findViewById(android.R.id.tabhost);
    mTabHost.setup();

    initializeTabs();

  //set the listener the last, to avoid overwrite mCurrentTab everytime we add a new Tab
    mTabHost.setOnTabChangedListener(listener);
}

चरों को सहेजें और बंडल में रखें:

 //Save variables while recreating
@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putSerializable("stack", mStacks);
    outState.putString("currentTab", mCurrentTab);
    //outState.putInt("tabHost",mTabHost);
}

यदि पिछला CurrentTab मौजूद है, तो इसे सेट करें, अन्यथा एक नया Tab_A बनाएं:

public void initializeTabs(){
    /* Setup your tab icons and content views.. Nothing special in this..*/
    TabHost.TabSpec spec    =   mTabHost.newTabSpec(AppConstants.TAB_A);

    spec.setContent(new TabHost.TabContentFactory() {
        public View createTabContent(String tag) {
            return findViewById(R.id.realtabcontent);
        }
    });
    spec.setIndicator(createTabView(R.drawable.tab_a_state_btn));
    mTabHost.addTab(spec);


    spec                    =   mTabHost.newTabSpec(AppConstants.TAB_B);
    spec.setContent(new TabHost.TabContentFactory() {
        public View createTabContent(String tag) {
            return findViewById(R.id.realtabcontent);
        }
    });
    spec.setIndicator(createTabView(R.drawable.tab_b_state_btn));
    mTabHost.addTab(spec);

//if we have non default Tab as current, change it
    if (mCurrentTab!=null) {
        mTabHost.setCurrentTabByTag(mCurrentTab);
    } else {
        mCurrentTab=AppConstants.TAB_A;
        pushFragments(AppConstants.TAB_A, new AppTabAFirstFragment(), false,true);
    }
}

मुझे उम्मीद है कि इससे अन्य लोगों को मदद मिलेगी।


ये गलत है। जब ऑनक्रिएट को एक बंडल के साथ बुलाया जाता है, तो वे फ्रेगमेंट वही नहीं होंगे जो स्क्रीन पर दिखाए जाने वाले हैं और आप पुराने को लीक कर रहे हैं, जब तक कि आप सेटट्रैनइन्स्टेंस का उपयोग नहीं कर रहे हैं। और अगर एक्टिविटमैनेजर आपकी एक्टिविटी को "सेव" करता है, क्योंकि जब कोई फ्रैगमेंट सीरियलाइज नहीं होता है और न ही पार्सलेबल होता है, जब यूजर आपकी एक्टिविटी में लौटता है, तो यह क्रैश हो जाएगा।
Sergio91pt

-1

मैं अनुशंसा करूंगा कि HashMap के आधार पर बैकस्टैक का उपयोग न करें> "गतिविधियों को न रखें" मोड में बहुत सारे बग हैं। यदि आप टुकड़े के ढेर में गहराई से स्थित हैं, तो यह राज्य को सही ढंग से बहाल नहीं करेगा। और नेस्टेड मैप फ्रैगमेंट (एक्साइज के साथ: आईडी के लिए कोई दृश्य नहीं मिला है) में भी दीवानगी होगी। Coz HashMap> पृष्ठभूमि \ अग्रभूमि एप्लिकेशन के बाद शून्य हो जाएगा

मैं खंड के बैकस्टैक के साथ काम के लिए ऊपर दिए गए कोड का अनुकूलन करता हूं

यह नीचे TabView है

मुख्य गतिविधि कक्षा

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
import android.widget.TabHost;
import android.widget.TextView;

import com.strikersoft.nida.R;
import com.strikersoft.nida.abstractActivity.BaseActivity;
import com.strikersoft.nida.screens.tags.mapTab.MapContainerFragment;
import com.strikersoft.nida.screens.tags.searchTab.SearchFragment;
import com.strikersoft.nida.screens.tags.settingsTab.SettingsFragment;

public class TagsActivity extends BaseActivity {
    public static final String M_CURRENT_TAB = "M_CURRENT_TAB";
    private TabHost mTabHost;
    private String mCurrentTab;

    public static final String TAB_TAGS = "TAB_TAGS";
    public static final String TAB_MAP = "TAB_MAP";
    public static final String TAB_SETTINGS = "TAB_SETTINGS";

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
        getActionBar().hide();
        setContentView(R.layout.tags_activity);

        mTabHost = (TabHost) findViewById(android.R.id.tabhost);

        mTabHost.setup();

        if (savedInstanceState != null) {
            mCurrentTab = savedInstanceState.getString(M_CURRENT_TAB);
            initializeTabs();
            mTabHost.setCurrentTabByTag(mCurrentTab);
            /*
            when resume state it's important to set listener after initializeTabs
            */
            mTabHost.setOnTabChangedListener(listener);
        } else {
            mTabHost.setOnTabChangedListener(listener);
            initializeTabs();
        }
    }

    private View createTabView(final int id, final String text) {
        View view = LayoutInflater.from(this).inflate(R.layout.tabs_icon, null);
        ImageView imageView = (ImageView) view.findViewById(R.id.tab_icon);
        imageView.setImageDrawable(getResources().getDrawable(id));
        TextView textView = (TextView) view.findViewById(R.id.tab_text);
        textView.setText(text);
        return view;
    }

    /*
    create 3 tabs with name and image
    and add it to TabHost
     */
    public void initializeTabs() {

        TabHost.TabSpec spec;

        spec = mTabHost.newTabSpec(TAB_TAGS);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_tag_drawable, getString(R.string.tab_tags)));
        mTabHost.addTab(spec);

        spec = mTabHost.newTabSpec(TAB_MAP);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_map_drawable, getString(R.string.tab_map)));
        mTabHost.addTab(spec);


        spec = mTabHost.newTabSpec(TAB_SETTINGS);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_settings_drawable, getString(R.string.tab_settings)));
        mTabHost.addTab(spec);

    }

    /*
    first time listener will be trigered immediatelly after first: mTabHost.addTab(spec);
    for set correct Tab in setmTabHost.setCurrentTabByTag ignore first call of listener
    */
    TabHost.OnTabChangeListener listener = new TabHost.OnTabChangeListener() {
        public void onTabChanged(String tabId) {

            mCurrentTab = tabId;

            if (tabId.equals(TAB_TAGS)) {
                pushFragments(SearchFragment.getInstance(), false,
                        false, null);
            } else if (tabId.equals(TAB_MAP)) {
                pushFragments(MapContainerFragment.getInstance(), false,
                        false, null);
            } else if (tabId.equals(TAB_SETTINGS)) {
                pushFragments(SettingsFragment.getInstance(), false,
                        false, null);
            }

        }
    };

/*
Example of starting nested fragment from another fragment:

Fragment newFragment = ManagerTagFragment.newInstance(tag.getMac());
                TagsActivity tAct = (TagsActivity)getActivity();
                tAct.pushFragments(newFragment, true, true, null);
 */
    public void pushFragments(Fragment fragment,
                              boolean shouldAnimate, boolean shouldAdd, String tag) {
        FragmentManager manager = getFragmentManager();
        FragmentTransaction ft = manager.beginTransaction();
        if (shouldAnimate) {
            ft.setCustomAnimations(R.animator.fragment_slide_left_enter,
                    R.animator.fragment_slide_left_exit,
                    R.animator.fragment_slide_right_enter,
                    R.animator.fragment_slide_right_exit);
        }
        ft.replace(R.id.realtabcontent, fragment, tag);

        if (shouldAdd) {
            /*
            here you can create named backstack for realize another logic.
            ft.addToBackStack("name of your backstack");
             */
            ft.addToBackStack(null);
        } else {
            /*
            and remove named backstack:
            manager.popBackStack("name of your backstack", FragmentManager.POP_BACK_STACK_INCLUSIVE);
            or remove whole:
            manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
             */
            manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        }
        ft.commit();
    }

    /*
    If you want to start this activity from another
     */
    public static void startUrself(Activity context) {
        Intent newActivity = new Intent(context, TagsActivity.class);
        newActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(newActivity);
        context.finish();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putString(M_CURRENT_TAB, mCurrentTab);
        super.onSaveInstanceState(outState);
    }

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

tags_activity.xml

<

?xml version="1.0" encoding="utf-8"?>
<TabHost
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="0"/>
        <FrameLayout
            android:id="@+android:id/realtabcontent"
            android:background="@drawable/bg_main_app_gradient"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>
        <TabWidget
            android:id="@android:id/tabs"
            android:background="#EAE7E1"
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0"/>
    </LinearLayout>
</TabHost>

tags_icon.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/tabsLayout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@drawable/bg_tab_gradient"
    android:gravity="center"
    android:orientation="vertical"
    tools:ignore="contentDescription" >

    <ImageView
        android:id="@+id/tab_icon"
        android:layout_marginTop="4dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView 
        android:id="@+id/tab_text"
        android:layout_marginBottom="3dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/tab_text_color"/>

</LinearLayout>

यहां छवि विवरण दर्ज करें

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.