【问题标题】:Separate Back Navigation for a Tabbed View Pager in AndroidAndroid中选项卡式视图寻呼机的单独后退导航
【发布时间】:2016-12-25 05:42:24
【问题描述】:

我想要什么。

在选项卡滑动菜单上下文中,我想将一个片段替换为选项卡内的另一个片段,并维护选项卡菜单以及当前选项卡。当滑动到另一个选项卡并返回原始时,我希望显示最后一个片段。

例如,我有tab_aFragment_1tab_bFragment_4tab_cFragment_7。现在我想要Fragment_1 中的一个按钮打开我Fragment_2,但我希望能够滑动到tab_btab_c 的片段。而当我返回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
    |        |
    |        |_ _ _ ...

我目前的主要活动。

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>

代码

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

片段

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>

代码

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

编辑:

我有一些解决方案,但没有一个是完美的。如果我在 MainActivity 中放置 Framelayaout,Fragment_1 会正确替换为Fragment_2,但它也在tab_btab_c 中。如果FrameLayout在Fragment_1,我可以正确滑动其他标签,但是替换不好,因为Fragment_2打开了,但Fragment_1也在。

【问题讨论】:

  • 你的情况到底发生了什么?屏幕方向是否会重新填充初始状态的所有片段?
  • @ReazMurshed 我还没有达到这一点,我仍在正确替换片段。我见过另一种解决方案,其中一些可能包含为每个片段进行大切换,但如果我们考虑一个包含很多片段的大场景,这个解决方案不可能是最好的,我相信必须有一个更好的解决方案。
  • @SantiGil 你的意思是你想要像 Flipboard 应用这样的东西?标签内的标签?
  • 几个月前我写了一篇关于该主题的博客文章:medium.com/@nilan/… :)
  • 我刚刚发布了一个答案! :) 如果您的意思是要弹出两次回退堆栈,则可以再次触发后退按钮事件作为 hack:this.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK));

标签: java android android-fragments android-viewpager android-tablayout


【解决方案1】:

根据您的解释,我实施以下内容:

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- 在每个选项卡中创建 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);

同样在片段中创建一个按钮,当点击进入另一个片段时:

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

最终结果将类似于以下内容,如果您转到 Home 标签,然后返回 Category 标签,您的Fragment 位置不会改变。

你应该记住的两点:

  1. 如果您不想破坏片段,请设置 mPager.setOffscreenPageLimit(3);
  2. 在内部片段中通过getChildFragmentManager() 而不是FragmentManager()

如果您不需要 Inner ViewPager 并且您只想一次加载一个 Fragment 实例,这里是另一种选择:

1- 做前一种方式的第一项。

2- 在您的片段 xml 中放入 FrameContainer 并在其中加载您的片段:

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

编辑:这不是最好的方法,但我认为可以解决您的问题:

1- 在您的 MainActivity 中创建一个静态字段,如下所示:

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

2- 当您在片段中调用 onClick 时,更新您的 MainActivity 中的 String 值。

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

第一个值是 ViewPager 位置,第二个值是你的内部 Fragment 类型(例如:fragment_d);

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

            }
        }

【讨论】:

  • 我正在尝试您的解决方案。第一个mPager.setOffscreenPageLimit(3); 我猜那是因为我有3 个标签tab_atab_btab_c。但是第二个,为什么也是3?
  • @SantiGil 我假设您的内部 ViewPager 有 3 页。 (默认情况下 setOffscreenPageLimit 也是 3 )
  • @SantiGil github.com/AmirHadifar/InnerTabsExample 这个存储库可能会给你一些想法。
  • @SantiGil 我在 CategoryFramgent 中设置了 todo 你应该在 OnClickListener 中设置CurrentPager。
  • @SantiGil 也用于处理内部片段中的 backPressed:stackoverflow.com/a/29166971/1462770
【解决方案2】:

您可以将ViewPagerTabLayout 一起使用。

<?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>

然后创建一个扩展 FragmentStatePagerAdapter 的类,用您的初始 Fragments 初始化 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);
    }
}

包含实际内容的片段应由HostFragment 包装,如果您想导航,则使用其子FragmentManager 将当前片段替换为新片段,或者从片段堆栈中弹出最后一个片段.调用其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;
    }
}

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

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

这个解决方案当然不是完美的,但可以完成工作。在我的blog post 中阅读有关此问题的更多信息。

【讨论】:

  • 完美!我added thisopenNextFragment更灵活。
【解决方案3】:

您可以更改页面片段内的片段。例如,TAB_A 实现了选取片段 1 或 2 的逻辑,并在内部显示选取的片段。 层次结构如下:

ViewPager -> TAB_A -> 片段 1 或 2(显示在 TAB_A 内)。

您还应该使用 getChildFragmentManager() 来管理 TAB_A 中的片段。

【讨论】:

    猜你喜欢
    • 2015-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多