development

Fragments를 사용하여 Android의 각 탭마다 별도의 Back Stack

big-blog 2020. 6. 7. 00:49
반응형

Fragments를 사용하여 Android의 각 탭마다 별도의 Back Stack


Android 앱에서 탐색 탭을 구현하려고합니다. TabActivity와 ActivityGroup은 더 이상 사용되지 않으므로 대신 Fragments를 사용하여 구현하고 싶습니다.

각 탭마다 하나의 조각을 설정 한 다음 탭을 클릭하면 조각을 전환하는 방법을 알고 있습니다. 그러나 각 탭마다 별도의 백 스택을 어떻게 가질 수 있습니까?

예를 들어 조각 A와 B는 탭 1 아래에 있고 조각 C와 D는 탭 2 아래에 있습니다. 앱이 시작될 때 조각 A가 표시되고 탭 1이 선택됩니다. 그런 다음 단편 A가 단편 B로 대체 될 수 있습니다. 탭 2가 선택되면 단편 C가 표시되어야합니다. 탭 1이 선택된 경우 단편 B가 다시 표시되어야합니다. 이때 Back 버튼을 사용하여 Fragment A를 표시 할 수 있습니다.

또한 장치를 회전 할 때 각 탭의 상태를 유지하는 것이 중요합니다.

BR 마틴


프레임 워크는 현재 자동으로이를 수행하지 않습니다. 각 탭마다 고유 한 백 스택을 구축하고 관리해야합니다.

솔직히 말해서, 이것은 정말로 의심스러운 일처럼 보입니다. 적절한 UI가 생성되는 것을 상상할 수 없습니다. 뒤 키가 탭에 따라 다른 일을 할 경우, 특히 키가 정상에있을 때 전체 키를 닫는 정상적인 동작이있는 경우 특히 그렇습니다. 스택 ... 불쾌한 소리.

웹 브라우저 UI와 같은 것을 구축하려는 경우 사용자에게 자연스러운 UX를 얻으려면 컨텍스트에 따라 많은 미묘한 동작 조정이 필요하므로 자신의 백 스택을 반드시 수행해야합니다. 프레임 워크의 일부 기본 구현에 의존하기보다는 관리. 예를 들어 뒤로 키가 다양한 방법으로 표준 브라우저와 상호 작용하는 방식에주의를 기울이십시오. 브라우저의 각 "창"은 기본적으로 탭입니다.


나는이 질문에 몹시 늦었다. 그러나이 스레드는 매우 유익하고 나에게 도움이되었으므로 여기에 두 개의 펜스를 게시하는 것이 좋습니다.

이와 같은 화면 흐름이 필요했습니다 (각 탭에 2 개의 탭과 2 개의보기가있는 최소한의 디자인).

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

나는 과거에도 같은 요구 사항을 가지고 있었고 TabActivityGroup(그 당시에는 더 이상 사용되지 않는)과 활동을 사용하여 수행했습니다 . 이번에는 Fragments를 사용하고 싶었습니다.

이것이 내가 한 일입니다.

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

앱의 모든 조각은이 Base 클래스를 확장 할 수 있습니다. 특별한 조각을 사용하려면 이것에 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. Ok, Main Tab Activity- 코드로 주석을 작성하십시오 ..

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 (탭 A의 첫 번째 조각, 모든 탭과 유사)

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

이것은 가장 세련되고 올바른 방법이 아닐 수 있습니다. 하지만 제 경우에는 아름답게 작동했습니다. 또한 세로 모드에서만이 요구 사항이있었습니다. 두 방향을 모두 지원하는 프로젝트에서이 코드를 사용할 필요가 없었습니다. 그래서 내가 어떤 도전에 직면하고 있는지 말할 수 없습니다 ..

편집하다 :

누구든지 전체 프로젝트를 원한다면 샘플 프로젝트를 github으로 푸시했습니다 .


최근에 앱에 대해 설명한 것과 동일한 동작을 구현해야했습니다. 응용 프로그램의 화면과 전체 흐름은 이미 정의되어 있으므로 우리는 그것을 고수해야했습니다 (iOS 앱 클론입니다 ...). 운 좋게도 화면상의 뒤로 버튼을 제거했습니다. :)

우리는 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를 확장합니다. 각 탭은 활동과 연관되어 있으며 ActivityInTab1, 2 및 3이라고 말하면 각 화면은 조각이됩니다.

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

각 ActivityInTab은 한 번에 하나의 조각 만 보유하며 한 조각을 다른 조각으로 대체하는 방법을 알고 있습니다 (ActvityGroup과 거의 동일). 멋진 점은이 방법으로 각 탭에 대해 별도의 백 스택을 관리하는 것이 매우 쉽다는 것입니다.

각 ActivityInTab의 기능은 상당히 동일합니다. 한 조각에서 다른 조각으로 이동하고 백 스택을 유지 관리하는 방법을 알고 있으므로 기본 클래스에 넣습니다. 간단히 ActivityInTab이라고하겠습니다.

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>

보다시피, 각 탭의 뷰 레이아웃은 동일합니다. 그 이유는 각 조각을 보유 할 content라는 FrameLayout이기 때문입니다. 조각은 각 화면을 볼 수있는 조각입니다.

보너스 포인트를 위해 사용자가 뒤로를 누를 때 확인 대화 상자를 표시하는 작은 코드도 추가했으며 더 이상 돌아갈 조각이 없습니다.

// 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의 Activity)는 자체 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());

그래서 그것은 거의 다입니다. 나는 이것이 매우 표준 적이 지 않은 솔루션이라고 확신한다. 그래서 노련한 안드로이드 개발자들에게이 기능을 달성하기위한 더 나은 방법이 무엇인지 물어보고 싶다. Android 에서이 작업을 수행 할 수 있는 방법을 설명하는 링크 나 자료를 알려 주시면 감사하겠습니다 (탭, 탭의 중첩 된 화면 등). 의견 에서이 답변을 찢어 주시기 바랍니다 :)

이 솔루션이 좋지 않다는 신호로 최근에는 애플리케이션에 탐색 기능을 추가해야했습니다. 사용자를 한 탭에서 다른 탭과 중첩 된 화면으로 이동시키는 기괴한 버튼. 누가 알 수 있느냐에 따라 프로그래밍 방식으로 엉덩이를 아프게하는데, 누가 문제를 알고 언제 단편과 활동이 실제로 인스턴스화되고 초기화되는지를 다루기 때문입니다. 해당 화면과 탭이 모두보기 일뿐이라면 훨씬 쉬울 것이라고 생각합니다.


마지막으로, 방향 변경에서 살아남 아야한다면, setArguments / getArguments를 사용하여 프래그먼트를 생성하는 것이 중요합니다. 프래그먼트 생성자에서 인스턴스 변수를 설정하면 문제가 발생합니다. 그러나 다행스럽게도 수정하기가 쉽습니다. 생성자에서 setArguments의 모든 것을 저장 한 다음 onCreate에서 getArguments를 사용하여 해당 항목을 검색하면됩니다.


ChildFragmentManager로 쉽게 달성 할 수 있습니다.

관련 프로젝트와 관련된 게시물입니다. 구경하다,

http://tausiq.wordpress.com/2014/06/06/android-multiple-fragments-stack-in-each-viewpager-tab/


조각에 대한 강한 참조를 저장하는 것은 올바른 방법이 아닙니다.

FragmentManager는 putFragment(Bundle, String, Fragment)및을 제공합니다 saveFragmentInstanceState(Fragment).

백 스택을 구현하기에 충분합니다.


putFragment조각을 교체하는 대신을 사용 하여 이전 조각을 분리하고 새 조각을 추가합니다. 이것이 프레임 워크가 백 스택에 추가 된 대체 트랜잭션에 수행하는 작업입니다. putFragment인덱스를 현재 활성 프래그먼트 목록에 저장하고 해당 프래그먼트는 방향 변경 중에 프레임 워크에 의해 저장됩니다.

두 번째 방법 인을 사용 saveFragmentInstanceState하면 전체 조각 상태를 번들에 저장하여 분리하지 않고 실제로 제거 할 수 있습니다. 이 방법을 사용하면 필요할 때마다 Fragment를 팝할 수 있으므로 백 스택을보다 쉽게 ​​조작 할 수 있습니다.


이 사용 사례에 두 번째 방법을 사용했습니다.

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

기권:


나는 이것이 표준 안드로이드 물건 인 것처럼 보이는 비슷한 유형의 문제에 대해 작업 한 관련 솔루션을 게시하는 가장 좋은 장소라고 생각합니다. 모든 사람의 문제를 해결할 수는 없지만 도움이 될 수 있습니다.


프래그먼트들 사이의 주요 차이점이 이들을 백업하는 데이터 (즉, 큰 레이아웃 차이가 많지 않음) 인 경우 실제로 프래그먼트를 교체 할 필요는 없지만 기본 데이터를 교체하고 뷰를 새로 고치기 만하면됩니다.

이 방법에 대한 한 가지 가능한 예에 대한 설명은 다음과 같습니다.

ListViews를 사용하는 앱이 있습니다. 목록의 각 항목은 몇 개의 자식이있는 부모입니다. 항목을 탭하면 원래 목록과 동일한 ActionBar 탭 내에서 해당 하위 항목으로 새 목록을 열어야합니다. 이 중첩 목록은 레이아웃이 매우 유사하지만 (여기에서 조건부 조정이있을 수 있지만) 데이터가 다릅니다.

이 응용 프로그램은 초기 부모 목록 아래에 여러 계층의 자손을 가지고 있으며 사용자가 첫 번째를 넘어 특정 깊이에 액세스하려고 시도 할 때까지 서버에서 데이터를 가질 수도 있고 없을 수도 있습니다. 목록은 데이터베이스 커서로 구성되며 프래그먼트는 커서 로더 및 커서 어댑터를 사용하여 목록보기를 목록 항목으로 채우므로 클릭이 등록 될 때 발생하는 모든 작업은 다음과 같습니다.

1) 목록에 추가되는 새 항목보기 및 새 커서가 리턴 한 열과 일치하는 적절한 '받는 사람'및 '보낸 사람'필드를 사용하여 새 어댑터를 작성하십시오.

2)이 어댑터를 ListView의 새 어댑터로 설정하십시오.

3) 클릭 한 항목을 기반으로 새 URI를 작성하고 새 URI (및 투영)로 커서 로더를 다시 시작하십시오. 이 예에서 URI는 UI에서 전달 된 선택 인수를 사용하여 특정 쿼리에 매핑됩니다.

4) URI에서 새 데이터가로드되면 어댑터와 연관된 커서를 새 커서로 바꾸면 목록이 새로 고쳐집니다.

트랜잭션을 사용하지 않기 때문에 이와 관련된 백 스택이 없으므로 계층 구조를 벗어날 때 직접 빌드하거나 쿼리를 반대로 재생해야합니다. 이 작업을 시도했을 때 쿼리는 oNBackPressed ()에서 계층의 최상위에 올 때까지 다시 수행 할 수있을 정도로 빠르며,이 시점에서 프레임 워크가 뒤로 버튼을 다시 인수합니다.

비슷한 상황에 처해 있다면 http://developer.android.com/guide/topics/ui/layout/listview.html 문서를 읽으십시오.

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

나는 이것이 누군가를 돕기를 바랍니다!


나는 정확히 같은 문제가 있었고 스택 탭, 백업 및 업 탐색을 다루고 잘 테스트되고 문서화 된 오픈 소스 github 프로젝트를 구현했습니다.

https://github.com/SebastianBaltesObjectCode/PersistentFragmentTabs

탐색 탭과 조각 전환 및 위 / 뒤 탐색 처리를위한 간단하고 작은 프레임 워크입니다. 각 탭에는 고유 한 조각 스택이 있습니다. ActionBarSherlock을 사용하며 API 레벨 8과 다시 호환됩니다.


안드로이드가 1 개의 백 스택만을 처리하기 때문에 이것은 복잡한 문제이지만, 이것은 가능합니다. 찾고있는 것을 정확하게 수행하는 Tab Stacker라는 라이브러리를 만드는 데 며칠이 걸렸습니다. 각 탭의 조각 기록. 오픈 소스이며 완전히 문서화되어 있으며 gradle과 함께 쉽게 포함될 수 있습니다. github에서 라이브러리를 찾을 수 있습니다 : https://github.com/smart-fun/TabStacker

샘플 앱을 다운로드하여 해당 동작이 요구 사항에 해당하는지 확인할 수도 있습니다.

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

질문이 있으시면 언제든지 주저하지 마십시오.


누군가가 찾고 있고 자신의 요구에 가장 적합한 솔루션을 선택하려고 할 때를 대비하여 자체 솔루션을 제안하고 싶습니다.

https://github.com/drusak/tabactivity

라이브러리를 생성하는 목적은 매우 평범합니다. iPhone처럼 구현하십시오.

주요 장점 :

  • TabLayout과 함께 android.support.design 라이브러리를 사용하십시오.
  • 각 탭에는 FragmentManager를 사용하여 고유 한 스택이 있습니다 (프래그먼트 참조를 저장하지 않고).
  • 딥 링크 지원 (특정 탭과 특정 조각 레벨을 열어야하는 경우);
  • 탭 상태 저장 / 복원;
  • 탭에서 단편의 적응 적 수명주기 방법;
  • 귀하의 요구에 맞게 구현하기가 쉽습니다.

간단한 해결책 :

탭 / 루트보기 호출을 변경할 때마다 :

fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);

BackStack이 지워집니다. 루트 조각을 변경하기 전에 이것을 호출하십시오.

그리고 이것으로 조각을 추가하십시오 :

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.


이 스레드는 매우 흥미롭고 유용했습니다.
설명과 코드에 대해 Krishnabhadra에게 감사드립니다. 코드를 사용하고 조금 개선하여 변경 구성 (주로 회전)에서 스택, currentTab 등을 유지할 수 있습니다.
에뮬레이터에서는 테스트되지 않은 실제 4.0.4 및 2.3.6 디바이스에서 테스트

"AppMainTabActivity.java"에서이 코드 부분을 변경하면 나머지는 동일하게 유지됩니다. 아마 Krishnabhadra는 이것을 그의 코드에 추가 할 것입니다.

데이터 복구시 생성 :

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

이것이 다른 사람들에게 도움이되기를 바랍니다.


HashMap을 기반으로 백 스택을 사용하지 않는 것이 좋습니다. "활동을 유지하지 마십시오"모드에 많은 버그가 있습니다. 조각 스택에 깊이있는 경우 상태를 올바르게 복원하지 못합니다. 또한 중첩 된 맵 조각에서 실행됩니다 (exeption : Fragment no view found for ID). background \ foreground 앱 이후 Coz HashMap>은 null입니다.

조각의 백 스택 작업을 위해 위의 코드를 최적화합니다.

하단의 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>

여기에 이미지 설명을 입력하십시오

참고 URL : https://stackoverflow.com/questions/6987334/separate-back-stack-for-each-tab-in-android-using-fragments

반응형