Fragment+BottomTab实现页面导航


前文我们说过,Fragment的作用之一就是拓展页面,使得在一块有限的屏幕上展示更多的内容,也就是多个Fragment页面。那么如何在多个页面之间快速切换呢?
本文将介绍最简单、常见的底Tab+Fragment多页面结构的实现案例,也是对前文Fragment知识的实践应用。

1. 目标效果

在这里插入图片描述

就是这样一个简单的,常见的页面结构。底部是三个导航按钮,点击不同的按钮,中间主要内容区域切换不同的Fragment页面。

2. 案例教学

2.1 主界面布局

首先,新建工程FragmentBottomTab1,实现基本的页面布局。
主界面MainActivity的布局,activity_main.xml :

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

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
        
    <!--分割线View-->
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="5dp"
        android:background="@color/gray" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <RelativeLayout
            android:id="@+id/rl_home"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1">

            <ImageView
                android:id="@+id/iv_home"
                android:layout_width="48dp"
                android:layout_height="48dp"
                android:layout_centerHorizontal="true"
                android:src="@drawable/selector_home" />

            <TextView
                android:id="@+id/tv_home"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/iv_home"
                android:layout_centerHorizontal="true"
                android:text="首页" />

        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/rl_find"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1">

            <ImageView
                android:id="@+id/iv_find"
                android:layout_width="48dp"
                android:layout_height="48dp"
                android:layout_centerHorizontal="true"
                android:src="@drawable/selector_find" />

            <TextView
                android:id="@+id/tv_find"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/iv_find"
                android:layout_centerHorizontal="true"
                android:text="发现" />

        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/rl_mine"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1">

            <ImageView
                android:id="@+id/iv_mine"
                android:layout_width="48dp"
                android:layout_height="48dp"
                android:layout_centerHorizontal="true"
                android:src="@drawable/selector_mine" />

            <TextView
                android:id="@+id/tv_mine"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/iv_mine"
                android:layout_centerHorizontal="true"
                android:text="" />

        </RelativeLayout>

    </LinearLayout>
</LinearLayout>

整体分为上下两部分(中间有个View是分割线,可写可不写),因此采用线性布局,上面FragmentContainerView承载Fragment,下面的三个按钮用LinearLayout等包装而成。
看起来很简单的按钮却写了很长,导致整个文件内容很长,看起来不好看。我们做一点优化,把这部分抽出来作为一个单独的文件,再用include标签引入。如下:
底部菜单抽出到文件bottom_tab_layout.xml :

<?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="wrap_content"
    android:orientation="horizontal">

    <RelativeLayout
        android:id="@+id/rl_home"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1">

        <ImageView
            android:id="@+id/iv_home"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_centerHorizontal="true"
            android:src="@drawable/selector_home" />

        <TextView
            android:id="@+id/tv_home"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/iv_home"
            android:layout_centerHorizontal="true"
            android:text="首页" />

    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/rl_find"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1">

        <ImageView
            android:id="@+id/iv_find"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_centerHorizontal="true"
            android:src="@drawable/selector_find" />

        <TextView
            android:id="@+id/tv_find"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/iv_find"
            android:layout_centerHorizontal="true"
            android:text="发现" />

    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/rl_mine"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1">

        <ImageView
            android:id="@+id/iv_mine"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_centerHorizontal="true"
            android:src="@drawable/selector_mine" />

        <TextView
            android:id="@+id/tv_mine"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/iv_mine"
            android:layout_centerHorizontal="true"
            android:text="" />

    </RelativeLayout>

</LinearLayout>

主界面布局activity_main.xml变成了这样:

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

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
     
    <!--分割线View-->
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="5dp"
        android:background="@color/gray" />

    <include layout="@layout/bottom_tab_layout" />
</LinearLayout>

是不是很简洁,很好看了呢?
如果你原样复制粘贴上面代码,会发现很多报错的地方。那是因为用到了一些你没有的资源,比如:图片、颜色。
这里提供下。首先是颜色:

color.xml里补充:

    <color name="gray">#716F6F</color>
    <color name="green_200">#C5E1A5</color>
    <color name="green_500">#8BC34A</color>
    <color name="green_700">#689F38</color>

三个按钮图片drawable:
selector_home.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="true" android:drawable="@drawable/ic_green_home_24" />
    <item android:drawable="@drawable/ic_gray_home_24" />
</selector>

selector_find.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="true" android:drawable="@drawable/ic_green_find_24" />
    <item android:drawable="@drawable/ic_gray_find_24" />
</selector>

selector_mine.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="true" android:drawable="@drawable/ic_green_mine_24" />
    <item android:drawable="@drawable/ic_gray_mine_24" />
</selector>

这里使用的是可以根据不同状态切换不同图片的selector,以实现那种选中的时候绿色,非选中的时候灰色的效果。
到这里,你会发现还缺那些ic_xxx的文件。没错,这些才是真正的图片,你需要为每个图标准备灰色、绿色两种。你可以去网上找,如阿里图库,也可以利用Android studio的New Vector Asset功能新建矢量图(选中res目录,右键就能找到)。

这里就不贴这些图片的代码了,太多了。。。

2.2 准备Fragment

这里的Fragment就是在承载三个按钮对应内容的页面。简单起见,这里我们就用一个Fragment类,通过传入不同的参数,展现不同的页面来演示。真正应用中,你的三个Fragment可能长得特别不一样,那么你就新建三个不同的Fragment类,切到哪个用哪个就好。
新建ExampleFragment.java:

public class ExampleFragment extends Fragment {
    // 改为public,以便于外界能引用到
    public static final String ARG_PARAM1 = "param1";
    public static final String ARG_PARAM2 = "param2";

    private String mParam1;
    private String mParam2;
    
    private TextView mTvContent;

    public ExampleFragment() {
    }
   
    public static ExampleFragment newInstance(String param1, String param2) {
        ExampleFragment fragment = new ExampleFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_example, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mTvContent = view.findViewById(R.id.tv_content);
        // 如果传入的参数有值,则设置内容
        if (!TextUtils.isEmpty(mParam1)) {
            mTvContent.setText(mParam1);
        }
    }
}

ExampleFragment对应的布局fragment_example.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ExampleFragment">

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textSize="25sp"
        android:text="你好" />

</FrameLayout>

上述ExampleFragment比较简单,就是接收参数,在布局中把接收的数据显示出来。我们后面会传入“这是HomeFragment”、“这是FineFragment”、“这是MineFragment”,从而展示不同的页面。

2.3 主界面MainActivity

首先做一些控件的基础工作。将布局中的控件声明并初始化(findViewById)。为底部导航按钮按钮设置点击事件监听。做完之后,MainActivity.java如下:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private FragmentContainerView mFragmentContainerView;
    private RelativeLayout mRlHome,mRlFind,mRlMine;
    private TextView mTvHome,mTvFind,mTvMine;
    private ImageView mIvHome,mIvFind,mIvMine;

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

    }

    private void initView() {
        mFragmentContainerView = findViewById(R.id.fragment_container);
        mRlHome = findViewById(R.id.rl_home);
        mRlFind = findViewById(R.id.rl_find);
        mRlMine = findViewById(R.id.rl_mine);

        mTvMine = findViewById(R.id.tv_mine);
        mTvFind = findViewById(R.id.tv_find);
        mTvHome = findViewById(R.id.tv_home);

        mIvMine = findViewById(R.id.iv_mine);
        mIvFind = findViewById(R.id.iv_find);
        mIvHome = findViewById(R.id.iv_home);
    }

    private void initEvent() {
        // 为底部导航按钮设置点击监听
        mRlHome.setOnClickListener(this);
        mRlFind.setOnClickListener(this);
        mRlMine.setOnClickListener(this);
    }

    /**
     * 点击了底部导航按钮
     * @param v
     */
    @Override
    public void onClick(View v) {
        
    }
}

接下来,我们就要处理点击 底部导航按钮 后的响应行为了。
这里的逻辑也很简单:根据点击按钮的ID,添加不同的Fragment页面,设置对应按钮位选中状态(按钮背景和文字颜色变绿)。

在onClick方法中加入代码:

/**
 * 点击了底部导航按钮
 * @param v
 */
@Override
public void onClick(View v) {
    // 根据点击按钮的ID,添加不同的Fragment页面
    switch (v.getId()) {
        case R.id.rl_home:
            mFragmentManager = getSupportFragmentManager();
            mFragmentTransaction = mFragmentManager.beginTransaction();
            Bundle bundle = new Bundle();
            bundle.putString(ExampleFragment.ARG_PARAM1, "这是HomeFragment");
            mFragmentTransaction.replace(R.id.fragment_container, ExampleFragment.class, bundle, "home")
                    .commit();
            break;
        case R.id.rl_find:
            mFragmentManager = getSupportFragmentManager();
            mFragmentTransaction = mFragmentManager.beginTransaction();

            Bundle bundleFind = new Bundle();
            bundleFind.putString(ExampleFragment.ARG_PARAM1,"这是FindFragment");
            mFragmentTransaction.replace(R.id.fragment_container, ExampleFragment.class, bundleSetting, "find")
                    .commit();
            break;
        case R.id.rl_mine:
            mFragmentManager = getSupportFragmentManager();
            mFragmentTransaction = mFragmentManager.beginTransaction();

            Bundle bundleMine = new Bundle();
            bundleMine.putString(ExampleFragment.ARG_PARAM1,"这是MineFragment");
            mFragmentTransaction.replace(R.id.fragment_container, ExampleFragment.class, bundleMine, "mine")
                    .commit();
            break;
        default:
            break;
    }
}

注意到 mFragmentManager 和 mFragmentTransaction会重复使用到,将其添加到全局变量,即:

  private FragmentManager mFragmentManager;
  private FragmentTransaction mFragmentTransaction;

接下来是按钮状态的切换,我们将这个过程封装到一个单独的方法 setBottomTabSelected 来做。

/**
 * 点击了底部导航按钮
 * @param v
 */
@Override
public void onClick(View v) {
    setBottomTabSelected(v.getId());
	// 省略下面的switch case...
}

/**
 * 设置底部导航按钮选中
 * @param tabId
 */
 private void setBottomTabSelected(int tabId) {
     // 先重置所有按钮状态为 全不选
     resetBottomTab();
     // 再设置某一个按钮为 选中
     switch (tabId) {
         case R.id.rl_home:
             mIvHome.setSelected(true);
             mTvHome.setTextColor(getResources().getColor(R.color.green_500));
             break;
         case R.id.rl_find:
             mIvFind.setSelected(true);
             mTvFind.setTextColor(getResources().getColor(R.color.green_500));
             break;
         case R.id.rl_mine:
             mIvMine.setSelected(true);
             mTvMine.setTextColor(getResources().getColor(R.color.green_500));
             break;
         default:
             break;
     }
 }

/**
 * 重置所有按钮状态为 全不选
 */
 private void resetBottomTab() {
     mTvHome.setTextColor(getResources().getColor(R.color.black));
     mTvFind.setTextColor(getResources().getColor(R.color.black));
     mTvMine.setTextColor(getResources().getColor(R.color.black));

     mIvHome.setSelected(false);
     mIvFind.setSelected(false);
     mIvMine.setSelected(false);
 }

到此,我们已经完成了99%的内容。已经可以点击底部导航按钮实现Fragment页面切换了。还差最后一点,那就是最开始进入Activity时,用户并没有点击任何一个底部菜单,我们需要设置一个默认进入的页面。也很简单,只需要初始化的最后,触发一下某个底部导航按钮的点击行为就行了。
比如,我们想让用户默认进入HomeFragment,在initEvent方法最后添加:

// 默认点击下home按钮,进入HomeFragment
mRlHome.performClick();

2.4 完整MainActivity.java代码

最后,附上完整的MainActivity.java代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private FragmentContainerView mFragmentContainerView;
    private RelativeLayout mRlHome,mRlFind,mRlMine;
    private TextView mTvHome,mTvFind,mTvMine;
    private ImageView mIvHome,mIvFind,mIvMine;

    private FragmentManager mFragmentManager;
    private FragmentTransaction mFragmentTransaction;

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

    private void initView() {
        mFragmentContainerView = findViewById(R.id.fragment_container);
        mRlHome = findViewById(R.id.rl_home);
        mRlFind = findViewById(R.id.rl_find);
        mRlMine = findViewById(R.id.rl_mine);

        mTvMine = findViewById(R.id.tv_mine);
        mTvFind = findViewById(R.id.tv_find);
        mTvHome = findViewById(R.id.tv_home);

        mIvMine = findViewById(R.id.iv_mine);
        mIvFind = findViewById(R.id.iv_find);
        mIvHome = findViewById(R.id.iv_home);
    }

    private void initEvent() {
        // 为底部导航按钮设置点击监听
        mRlHome.setOnClickListener(this);
        mRlFind.setOnClickListener(this);
        mRlMine.setOnClickListener(this);
        // 默认点击下home按钮,进入HomeFragment
        mRlHome.performClick();
    }

    /**
     * 点击了底部导航按钮
     * @param v
     */
    @Override
    public void onClick(View v) {
        setBottomTabSelected(v.getId());
        switch (v.getId()) {
            case R.id.rl_home:

                mFragmentManager = getSupportFragmentManager();
                mFragmentTransaction = mFragmentManager.beginTransaction();
                Bundle bundle = new Bundle();
                bundle.putString(ExampleFragment.ARG_PARAM1, "这是HomeFragment");
                mFragmentTransaction.replace(R.id.fragment_container, ExampleFragment.class, bundle, "home")
                        .commit();
                break;
            case R.id.rl_find:
                mFragmentManager = getSupportFragmentManager();
                mFragmentTransaction = mFragmentManager.beginTransaction();

                Bundle bundleSetting = new Bundle();
                bundleSetting.putString(ExampleFragment.ARG_PARAM1,"这是FindFragment");
                mFragmentTransaction.replace(R.id.fragment_container, ExampleFragment.class, bundleSetting, "setting")
                        .commit();
                break;
            case R.id.rl_mine:
                mFragmentManager = getSupportFragmentManager();
                mFragmentTransaction = mFragmentManager.beginTransaction();

                Bundle bundleMine = new Bundle();
                bundleMine.putString(ExampleFragment.ARG_PARAM1,"这是MineFragment");
                mFragmentTransaction.replace(R.id.fragment_container, ExampleFragment.class, bundleMine, "mine")
                        .commit();
                break;
            default:
                break;
        }
    }

    /**
     * 重置所有按钮状态为 全不选
     */
    private void resetBottomTab() {
        mTvHome.setTextColor(getResources().getColor(R.color.black));
        mTvFind.setTextColor(getResources().getColor(R.color.black));
        mTvMine.setTextColor(getResources().getColor(R.color.black));

        mIvHome.setSelected(false);
        mIvFind.setSelected(false);
        mIvMine.setSelected(false);

    }

    /**
     * 设置底部导航按钮选中
     * @param tabId
     */
    private void setBottomTabSelected(int tabId) {
        // 先重置所有按钮状态为 全不选
        resetBottomTab();
        // 再设置某一个按钮为 选中
        switch (tabId) {
            case R.id.rl_home:
                mIvHome.setSelected(true);
                mTvHome.setTextColor(getResources().getColor(R.color.green_500));
                break;
            case R.id.rl_find:
                mIvFind.setSelected(true);
                mTvFind.setTextColor(getResources().getColor(R.color.green_500));
                break;
            case R.id.rl_mine:
                mIvMine.setSelected(true);
                mTvMine.setTextColor(getResources().getColor(R.color.green_500));
                break;
            default:
                break;
        }
    }

}
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
使用Fragment和ViewPager实现页面切换的步骤如下: 1. 创建Fragment:创建需要显示的Fragment,并实现Fragment的布局和逻辑。 2. 创建ViewPager:在主界面布局中添加ViewPager,并创建一个PagerAdapter(适配器)用于管理Fragment。 3. 实现PagerAdapter:创建一个继承FragmentPagerAdapter或FragmentStatePagerAdapter的适配器,重写getItem()方法,返回对应位置的Fragment。 4. 设置ViewPager:将创建好的适配器设置给ViewPager,并添加TabLayout(选项卡)用于切换页面。 具体实现步骤如下: 1. 创建需要显示的Fragment: ``` public class Fragment1 extends Fragment { @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment1_layout, container, false); // TODO: 添加需要显示的布局和逻辑 return view; } } ``` 2. 创建ViewPager: ``` <androidx.viewpager.widget.ViewPager android:id="@+id/view_pager" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 3. 实现PagerAdapter: ``` public class MyPagerAdapter extends FragmentPagerAdapter { private List<Fragment> mFragments; private List<String> mTitles; public MyPagerAdapter(FragmentManager fm, List<Fragment> fragments, List<String> titles) { super(fm); mFragments = fragments; mTitles = titles; } @Override public Fragment getItem(int position) { return mFragments.get(position); } @Override public int getCount() { return mFragments.size(); } @Nullable @Override public CharSequence getPageTitle(int position) { return mTitles.get(position); } } ``` 4. 设置ViewPager: ``` ViewPager viewPager = findViewById(R.id.view_pager); List<Fragment> fragments = new ArrayList<>(); fragments.add(new Fragment1()); fragments.add(new Fragment2()); List<String> titles = new ArrayList<>(); titles.add("页面1"); titles.add("页面2"); MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager(), fragments, titles); viewPager.setAdapter(adapter); TabLayout tabLayout = findViewById(R.id.tab_layout); tabLayout.setupWithViewPager(viewPager); ``` 这样就可以使用ViewPager和Fragment实现页面切换了。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子林Android

感谢老板,老板大气!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值