简体   繁体   English

Android中选项卡式视图传呼器的单独后退导航

[英]Separate Back Navigation for a Tabbed View Pager in Android

What I want. 我想要的是。

In a tab sliding menu context, I want to replace a fragment to another inside a tab, and maintaining the tab menu, and also the current tab. 在选项卡滑动菜单上下文中,我想将一个片段替换为选项卡内的另一个片段,并保持选项卡菜单以及当前选项卡。 When sliding to another tab and returning to original, I want the last fragment to be displayed. 当滑到另一个选项卡并返回原始位置时,我希望显示最后一个片段。

For example, I have tab_a with Fragment_1 , tab_b with Fragment_4 and tab_c with Fragment_7 . 例如,我有tab_aFragment_1tab_bFragment_4tab_cFragment_7 Now I want a button in Fragment_1 that opens me Fragment_2 , but I want to be able to swipe to the fragments of the tab_b and tab_c . 现在,我想使用Fragment_1中的按钮来打开Fragment_2 ,但我希望能够滑动到tab_btab_c的片段。 And when I return to tab_a, Fragment_2 must be displayed. 当我返回tab_a时,必须显示Fragment_2

在此处输入图片说明

MainActivity
    |
    |
ContainerFragment
    |
    |
    |_ _ _ Tab A
    |        |_ _ _ Fragment 1
    |        |
    |        |_ _ _ Fragment 2
    |        |
    |        |_ _ _ Fragment 3
    |        |
    |        |_ _ _ ...
    |        
    |_ _ _ Tab B
    |        |_ _ _ Fragment 4
    |        |
    |        |_ _ _ Fragment 5
    |        |
    |        |_ _ _ Fragment 6
    |        |
    |        |_ _ _ ...
    |
    |_ _ _ Tab C
    |        |_ _ _ Fragment 7
    |        |
    |        |_ _ _ Fragment 8
    |        |
    |        |_ _ _ Fragment 8
    |        |
    |        |_ _ _ ...

My current main activity. 我当前的主要活动。

Xml Xml

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.design.widget.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:tabMode="fixed"
            app:tabGravity="fill"/>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"  />

</android.support.design.widget.CoordinatorLayout>

Code

public class MainActivity extends AppCompatActivity {

    private TabLayout tabLayout;

    private final static int[] tabIcons = {
            R.drawable.ic_tab_a,
            R.drawable.ic_tab_b,
            R.drawable.ic_tab_c
    };

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

        ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
        setupViewPager(viewPager);

        tabLayout = (TabLayout) findViewById(R.id.tabs);
        tabLayout.setupWithViewPager(viewPager);
        setupTabIcons();
    }

    private void setupTabIcons() {
        tabLayout.getTabAt(0).setIcon(tabIcons[0]);
        tabLayout.getTabAt(1).setIcon(tabIcons[1]);
        tabLayout.getTabAt(2).setIcon(tabIcons[2]);
    }

    private void setupViewPager(ViewPager viewPager) {
        ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
        adapter.addFrag(new Fragment1());
        adapter.addFrag(new Fragment4());
        adapter.addFrag(new Fragment7());
        viewPager.setAdapter(adapter);
    }

    class ViewPagerAdapter extends FragmentPagerAdapter {

        private final List<Fragment> mFragmentList = new ArrayList<>();

        ViewPagerAdapter(FragmentManager manager) {
            super(manager);
        }

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

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

        void addFrag(Fragment fragment) {
            mFragmentList.add(fragment);
        }
    }
}

Fragments 碎片

Xml Xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:background="@android:color/holo_blue_light"
    android:id="@+id/fragment_1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="myContext">

    <Button
        android:text="Go to next Fragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:id="@+id/button_nextFrag" />

    <FrameLayout
        android:id="@+id/tab1_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

Code

public class Fragment1 extends Fragment {

    private void goNextFragment() {
        FragmentTransaction trans = getFragmentManager().beginTransaction();
        trans.replace(R.id.tab1_container, new Fragment2());
        trans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
        trans.addToBackStack(null);
        trans.commit();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment.
        View root = inflater.inflate(R.layout.fragment_1, container, false);
        // Go to next Fragment Button.
        final Button nextFrag = (Button) root.findViewById(R.id.button_nextFrag);
        nextFrag .setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                goNextFragment();
            }
        });
        return root;
    }
}

Edit: 编辑:

I've got some solutions, but none is perfect. 我有一些解决方案,但没有一个是完美的。 If I put a Framelayaout in MainActivity, Fragment_1 is correctly replaced to Fragment_2 , but it is also in tab_b and tab_c . 如果我在MainActivity中放置Framelayaout,则Fragment_1被正确替换为Fragment_2 ,但它也会同时位于tab_btab_c If the FrameLayout is in Fragment_1 , I can swipe the other tabs correctly, but the replacement goes not good, cause Fragment_2 is opened, but Fragment_1 is also there. 如果FrameLayout在Fragment_1 ,则可以正确滑动其他选项卡,但是替换效果不佳,因为Fragment_2已打开,但Fragment_1也存在。

Based on what you explained I Implement following: 根据您的解释,我实现了以下内容:

1- In your Activity create your Tabs and ViewPager : 1-在“活动”中创建TabsViewPager

    mPager = (ViewPager) findViewById(R.id.vp_pager);
    mPager.setOffscreenPageLimit(3);
    PagerAdapter mAdapter = new PagerAdapter(getSupportFragmentManager(), getContext());
    mPager.setAdapter(mAdapter);
    //tab.setupWithViewPager(mPager);

2- In each Tabs create ViewPager (I assume you want tabs inside tabs if you don't need tabs just removed them), Implementation of each fragment would be something like : 2-在每个选项卡中创建ViewPager (我假设您需要在tabs如果您不需要将选项卡删除的话),每个fragment实现将类似于:

    ViewPager mPager = (ViewPager) rootView.findViewById(R.id.vp_pager);
    mPager.setOffscreenPageLimit(3);
    CategoryAdapter mAdapter = new CategoryAdapter(getChildFragmentManager());//make sure this ChildFragmentManager
    mPager.setAdapter(mAdapter);

Also in fragment create a Button which when Click goes to another fragment: 同样在片段中创建一个Button,当Click转到另一个片段时:

 button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mPager.setCurrentItem(1); // go to second fragment
            }
        });

final result would be something like following and when If you go to Home Tabs and then goes back to Category Tab you're your Fragment position not changed. 最终结果将类似于以下内容,当您转到“ 主页”选项卡,然后返回到“ 类别”选项卡时,您的Fragment位置不变。

Two point you should remember: 您应该记住两点:

  1. set mPager.setOffscreenPageLimit(3); 设置mPager.setOffscreenPageLimit(3); if you don't want to destroy your fragment 如果你不想破坏你的碎片
  2. In inner fragment pass getChildFragmentManager() instead of FragmentManager() 在内部片段中,传递getChildFragmentManager()而不是FragmentManager()

在此处输入图片说明

If you don't need Inner ViewPager And you Just want load one instance of Fragment at time here is another option: 如果您不需要Inner ViewPager并且只想一次加载一个Fragment实例,那么这里是另一种选择:

1- Do first item of previous way. 1-做以前的方式的第一项。

2- In your fragment xml put FrameContainer and load your fragment inside it: 2-在片段xml中放入FrameContainer并将片段加载到其中:

 CategoryResultFragment f = CategoryResultFragment.newInstance();
        getFragmentManager().beginTransaction()
                .replace(R.id.frame_result_container, f)
                .commit();

Edit: This is not best approach but I think solve your issue: 编辑:这不是最好的方法,但我认为解决您的问题:

1- Create a static field in your MainActivity like following: 1-在您的MainActivity创建一个静态字段,如下所示:

static String[] type = new String[3]; 

2- When you call onClick in your fragment, update String value in your MainActivity . 2-当您在片段中调用onClick时,更新MainActivity String值。

public static update currentType(int pos,String type);

first value is ViewPager position, Second value is your inner Fragment type (eg: fragment_d); 第一个值为ViewPager位置,第二个值为您的内部Fragment类型(例如:fragment_d);

3- In your ViewPager 3-在您的ViewPager中

 @Override
        public Fragment getItem(int position) {
            switch (position) {
                case 0: // first tab
                    if(type[position] == "fragment_d")
                        return new Fragment_D();
                    else
                        return new Fragment_B();

            }
        }

You can use a ViewPager together with a TabLayout . 您可以使用ViewPager与一起TabLayout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<android.support.v4.view.ViewPager
    android:id="@+id/viewpager"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1"/>
<android.support.design.widget.TabLayout
    android:id="@+id/tablayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>
</LinearLayout>

Then create a class that extends FragmentStatePagerAdapter to initialize the ViewPager with your initial Fragment s: 然后创建一个扩展FragmentStatePagerAdapter的类,以使用初始Fragment初始化ViewPager

public class CustomPagerAdapter extends FragmentStatePagerAdapter {
    private final List<String> tabTitles = new ArrayList<String>() {{
        add("Fragment 1");
        add("Fragment 4");
        add("Fragment 7");
    }};

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

    public CustomPagerAdapter(FragmentManager fragmentManager) {
        super(fragmentManager);

        initializeTabs();
    }

    private void initializeTabs() {
        tabs.add(HostFragment.newInstance(new Fragment1()));
        tabs.add(HostFragment.newInstance(new Fragment4()));
        tabs.add(HostFragment.newInstance(new Fragment7()));
    }

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

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

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

The fragments containing the actual contents should be wrapped by a HostFragment , that uses its child FragmentManager to replace the current fragment with a new one if you want to navigate, or to pop the last fragment from the fragment stack. 包含实际内容的片段应由HostFragment包裹,如果要导航或从片段堆栈中弹出最后一个片段,则使用HostFragment的子FragmentManager将当前片段替换为新片段。 Call its replaceFragment to navigate: 调用其replaceFragment进行导航:

public class HostFragment extends BackStackFragment {
    private Fragment fragment;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        View view = inflater.inflate(R.layout.host_fragment, container, false);
        if (fragment != null) {
            replaceFragment(fragment, false);
        }
        return view;
    }

    public void replaceFragment(Fragment fragment, boolean addToBackstack) {
        if (addToBackstack) {
            getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).addToBackStack(null).commit();
        } else {
            getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).commit();
        }
    }

    public static HostFragment newInstance(Fragment fragment) {
        HostFragment hostFragment = new HostFragment();
        hostFragment.fragment = fragment;
        return hostFragment;
    }
}

The HostFragment should extend the abstract class BackstackFragment HostFragment应该扩展抽象类BackstackFragment

public abstract class BackStackFragment extends Fragment {
    public static boolean handleBackPressed(FragmentManager fm)
    {
        if(fm.getFragments() != null){
            for(Fragment frag : fm.getFragments()){
                if(frag != null && frag.isVisible() && frag instanceof BackStackFragment){
                    if(((BackStackFragment)frag).onBackPressed()){
                        return true;
                    }
                }
            }
        }
        return false;
    }

    protected boolean onBackPressed()
    {
        FragmentManager fm = getChildFragmentManager();
        if(handleBackPressed(fm)){
            return true;
        } else if(getUserVisibleHint() && fm.getBackStackEntryCount() > 0){
            fm.popBackStack();
            return true;
        }
        return false;
    }
}

Wire everthing up in the MainActivity : MainActivity一切连接:

public class MainActivity extends AppCompatActivity {
    private CustomPagerAdapter customPagerAdapter;
    private ViewPager viewPager;
    private TabLayout tabLayout;

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

        setContentView(R.layout.activity_main);

        viewPager = (ViewPager) findViewById(R.id.viewpager);
        tabLayout = (TabLayout) findViewById(R.id.tablayout);
        customPagerAdapter = new CustomPagerAdapter(getSupportFragmentManager());

        // 2 is enough for us; increase if you have more tabs!
        viewPager.setOffscreenPageLimit(2);
        viewPager.setAdapter(customPagerAdapter);
        tabLayout.setupWithViewPager(viewPager);
    }

    @Override
    public void onBackPressed()
    {
        if(!BackStackFragment.handleBackPressed(getSupportFragmentManager())){
            super.onBackPressed();
        }
    }

    public void openNextFragment() {
        HostFragment hostFragment = (HostFragment) customPagerAdapter.getItem(viewPager.getCurrentItem());

        // your logic to change the fragments...
    }
}

This solution is certainly not perfect but gets the job done. 该解决方案当然不是完美的,但可以完成工作。 Read more fluff about it in my blog post on the issue. 在有关此问题的博客文章中阅读有关它的更多信息。

You can change fragments inside page fragment. 您可以在页面片段内更改片段。 For example, TAB_A implements logic of picking fragment 1 or 2, and displaying picked fragment inside. 例如,TAB_A实现拾取片段1或2并在其中显示拾取的片段的逻辑。 Hierarchy looks like: 层次结构如下:

ViewPager -> TAB_A -> fragment 1 or 2 (displaying inside TAB_A). ViewPager-> TAB_A->片段1或2(显示在TAB_A内部)。

Also you should use getChildFragmentManager() to manage fragments inside TAB_A. 另外,您应该使用getChildFragmentManager()来管理TAB_A中的片段。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM