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


158

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

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

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

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

BR 마틴

답변:


23

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

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

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


7
하지마 그리고 프레임 워크는 거의 쓸모가 없습니다. 그것은 당신에게 이런 종류의 일에 대한 자동 지원을 제공하지 않습니다. 내가 말했듯이 어쨌든 뒤 행동을 조심스럽게 제어 해야하는 매우 특수한 상황을 제외하고는 적절한 사용자 경험을 초래할 것이라고 상상할 수 없습니다.
hackbod

9
이러한 유형의 탐색에는 각 탭에 탭과 페이지 계층 구조가 있으며 iPhone 응용 프로그램에서 매우 일반적입니다 (App Store 및 iPod 응용 프로그램 확인 가능). 나는 그들의 사용자 경험이 꽤 괜찮다는 것을 알았습니다.
Dmitry Ryadnenko

13
이건 미친 짓이야 iPhone에는 뒤로 버튼이 없습니다. 탭으로 프래그먼트를 구현하는 매우 간단한 코드를 보여주는 API 데모가 있습니다. 질문에 대한 질문은 각 탭마다 다른 백 스택을 갖는 것에 관한 것이었고, 내 대답은 프레임 워크 가 뒤로 버튼의 기능을 의미 론적 으로 크롤링 하는 사용자 경험 일 가능성 이 있기 때문에 이것을 자동으로 제공 하지 않는다는 것입니다. 원하는 경우 의미 체계를 쉽게 쉽게 구현할 수 있습니다.
hackbod

4
다시 말하지만 iPhone에는 뒤로 버튼이 없으므로 Android와 같은 의미로 백 스택 동작이 없습니다. 또한 "시간이 많이 활동하고 자신 저장 단지 스틱 더 나은"하지 않습니다 어떤 활동을하면 자신의 다른 다시 스택과 함께 UI에 탭을 유지 넣어하지 않기 때문에, 여기에 의미를; 실제로 활동의 백 스택 관리는 Fragment 프레임 워크에서 제공하는 것보다 유연성이 떨어집니다.
hackbod

22
@hackbod 귀하의 요점을 따르려고하지만 사용자 정의 백 스택 동작을 구현하는 데 문제가 있습니다. 나는 이것의 디자인에 관여했다는 것이 얼마나 쉬운 지에 대한 확실한 통찰력을 가질 것이라는 것을 알고 있습니다. OP의 사용 사례에 대한 데모 앱이있을 수 있습니다. 특히 매우 일반적인 상황이므로 특히 이러한 요청을하는 고객을 위해 iOS 앱을 작성하고 이식해야하는 사람들에게는 특히 그렇습니다. 각 FragmentActivity 내에서 조각 백 스택.
Richard Le Mesurier

138

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

이와 같은 화면 흐름이 필요했습니다 (각 탭에 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으로 푸시했습니다 .


2
모든 프래그먼트에 대한 데이터 저장, 모든 프래그먼트 재생성, 스택 재 구축 ... 간단한 오리엔테이션 변경을위한 많은 작업.
Michael Eilers Smith

3
@omegatai는 귀하의 의견에 전적으로 동의합니다. 모든 문제는 안드로이드가 우리를 위해 스택을 관리하지 않기 때문에 ( iOS가 수행하는 방향과 여러 조각이있는 방향 변경 또는 탭이 산들 바람 ) 우리를이 Q /의 원래 토론으로 안내합니다. 실. 다시는 좋지
않아요

1
@Renjith 이것은 탭을 전환 할 때마다 조각이 다시 만들어지기 때문입니다. 한 번도 생각하지 않아도 조각이 탭 스위치에서 재사용됩니다. A 탭에서 B로 전환하면 A 탭에서 메모리가 해제됩니다. 따라서 데이터를 활동에 저장하고 매번 서버에서 가져 오기 전에 활동에 데이터가 있는지 확인하십시오.
Krishnabhadra

2
@Krishnabhadra Ok, 훨씬 좋아 보인다. 내가 틀렸다면 정정하겠습니다. 귀하의 예에 따라 하나의 활동과 따라서 하나의 번들이 있습니다. BaseFragment (프로젝트 참조)에서 어댑터 인스턴스를 작성하고 거기에 데이터를 저장하십시오. 뷰를 만들 때마다 사용하십시오.
Renjith

1
작동합니다. 고마워 전체 프로젝트를 업로드하는 것이 좋습니다. :-)
Vinay W

96

최근에 앱에 대해 설명한 것과 동일한 동작을 구현해야했습니다. 응용 프로그램의 화면과 전체 흐름은 이미 정의되었으므로 우리는 그것을 고수해야했습니다 (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());

그래서 그것은 거의 다입니다. 나는 이것이 매우 정식 (그리고 대부분 좋지 않은) 솔루션이 아니라고 확신합니다. 노련한 안드로이드 개발자 에게이 기능을 달성하는 더 좋은 방법이 무엇인지 물어보고 싶습니다. 당신이 어떤 설명 일부 링크 또는 자료에 날 지점 수 있다면 안드로이드에서 "다, 나는 감사하겠습니다 이 (탭, 탭에서 중첩 된 화면 등)에 접근하는 안드로이드 방법. 의견 에서이 답변을 찢어 주시기 바랍니다 :)

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


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


13
큰 대답이지만 거의 볼 수 없다고 생각합니다. 나는 이전의 답변에서 대화에서 볼 수 있듯이 정확히 동일한 경로를 선택했으며 나는 당신처럼 그것에 만족하지 않습니다. 이 API는 주요 사용 사례를 다루지 않기 때문에 Google은 실제로이 조각을 망쳤다 고 생각합니다. 또 다른 문제는 조각을 다른 조각에 포함시킬 수 없다는 것입니다.
Dmitry Ryadnenko

의견을 보내 주셔서 감사합니다. 예, 조각 API에 대해 더 동의 할 수 없었습니다. 나는 이미 중첩 된 프래그먼트의 문제에 부딪쳤다 (그래서 우리는 "하나의 프래그먼트를 다른 프래그먼트로 교체"접근 방식으로 갔다).
전염병

1
나는 모든 활동을 통해 이것을 구현했습니다. 나는 내가 가진 것을 좋아하지 않았고 조각을 시도 할 것입니다. 그것은 당신의 경험의 반대입니다! 각 탭에서 자식보기의 수명주기를 처리하고 자체 뒤로 버튼을 구현하기 위해 활동으로 많은 구현이 있습니다. 또한 모든 뷰에 대한 참조를 유지할 수는 없습니다. 그렇지 않으면 메모리가 폭발합니다. Fragments가 1) 메모리를 명확하게 분리하여 Fragments의 수명주기를 지원하고 2) 뒤로 버튼 기능을 구현하는 데 도움이됩니다.이 프로세스에 조각을 사용하면 Tablet에서 실행하기가 쉽지 않습니까?
gregm

사용자가 탭을 전환하면 어떻게됩니까? 프래그먼트 백 스택이 삭제됩니까? 백 스택을 유지하는 방법?
gregm

1
@gregm 내가했던 것처럼 1 개의 탭 <-> 1 활동으로 이동하면 활동이 실제로 유지되기 때문에 탭을 전환 할 때 각 탭의 백 스택이 유지됩니다. 그들은 단지 일시 정지되고 재개된다. TabActivity에서 탭을 전환 할 때 활동을 파괴하고 다시 만들 수있는 방법이 있는지 모르겠습니다. 그러나 내가 제안처럼 당신이 활동 내부의 조각을 교체 할 경우, 그들은 되어 파괴 (그리고 가기 backstack이 튀어 때 다시는 생성). 따라서 언제든지 탭당 최대 하나의 프래그먼트가 활성화됩니다.
전염병


6

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

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

2

부인 성명:


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


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

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

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

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


누구든지이 작업을 수행하고 AlphabetIndexer와 같은 SectionIndexer를 사용하는 경우 어댑터를 교체 한 후 빠른 스크롤이 작동하지 않을 수 있습니다. 불행한 버그의 일종이지만 새로운 인덱서로 어댑터를 교체해도 FastScroll에서 사용하는 섹션 목록은 업데이트되지 않습니다. 해결 방법이 있습니다. 문제해결 방법에
courtf

2

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

https://github.com/SebastianBaltesObjectCode/PersistentFragmentTabs

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


2

안드로이드가 1 개의 백 스택만을 처리하기 때문에 이것은 복잡한 문제이지만, 이것은 가능합니다. 찾고있는 것을 정확하게 수행하는 Tab Stacker라는 라이브러리를 만드는 데 며칠이 걸렸습니다. 각 탭의 조각 기록. 오픈 소스이며 완전히 문서화되어 있으며 gradle과 함께 쉽게 포함될 수 있습니다. 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하므로 BaseTabFragment.java를 BaseTabListFragment.java에 복제하고 ListFragment를 확장했습니다. 그런 다음 코드에서 항상 BaseTabFragment를 기대하는 다양한 부분을 변경해야했습니다. 더 좋은 방법이 있습니까?
primehalo

불행히도 ListFragment에 대해서는 생각하지 않았습니다. 기술적으로는 올바른 솔루션이지만 TabFragment 및 해당 인스턴스의 BaseTabListFragment에 대한 추가 검사가 필요합니다. 내부에서 ListView와 함께 Fragment를 사용하는 또 다른 방법은 정확히 구현 된 ListFragment와 동일합니다. 생각해 볼게요 저를 가리켜 주셔서 감사합니다!
kasurd

1

간단한 해결책 :

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

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.


-1

이 스레드는 매우 흥미롭고 유용했습니다.
설명과 코드에 대해 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);
    }
}

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


이것은 잘못이다. 번들로 onCreate를 호출하면 해당 조각은 화면에 표시되는 것과 동일하지 않으며 setRetainInstance를 사용하지 않는 한 이전 조각이 누출됩니다. 그리고 ActivityManager가 액티비티를 "저장"하면 조각이 직렬화 가능하거나 소포 가능하지 않기 때문에 사용자가 액티비티로 돌아올 때 충돌이 발생합니다.
sergio91pt

-1

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>

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.