1. 程式人生 > >Android--RadioGroup和Tablayout兩種實現底部導航的方式詳解

Android--RadioGroup和Tablayout兩種實現底部導航的方式詳解

        自從Android3.0引入Fargment之後,在Activity中使用底部導航進行Fragment的切換已經越來越普遍,或者可以說已經成為了移動應用的標配,而本篇文章我總結了專案中常用的幾種實現導航的方式,分別是RadioGroup、Tablayout、RadioGroup+反射和FragmentTabHost四種實現方式,包含底部和頂部的雙導航介面的實現,實現的結果類似下圖所示:

   

一、使用RadioGroup+Fragment實現底部導航,使用TabLayout+Fragment實現頂部導航

        1,RadioGroup+Fragment的形式是之前開發中比較受歡迎,使用比較多的一種實現形式,所以把它排到第一位。好了不扯了,直接程式碼走起:

       首先,在radiogroup.xml中的佈局檔案是:

<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"
    tools:context="com.example.linktestproject.RadioGroupActivity">

    <FrameLayout
        android:id="@+id/fl_radio_show"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        ></FrameLayout>
    <RadioGroup
        android:id="@+id/rg_radio_navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_gravity="bottom">
        <RadioButton
            android:id="@+id/rb_radio_homepage"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:drawableTop="@drawable/homepage"
            android:text="@string/homepage"
            android:button="@null"
            android:drawablePadding="5dp"
            android:textColor="@drawable/radio_button_selector"
            />
        <RadioButton
            android:id="@+id/rb_radio_subscription"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:drawableTop="@drawable/subscription"
            android:text="@string/subscription"
            android:button="@null"
            android:drawablePadding="5dp"
            android:textColor="@drawable/radio_button_selector"
            />
        <RadioButton
            android:id="@+id/rb_radio_find"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:drawableTop="@drawable/find"
            android:text="@string/find"
            android:button="@null"
            android:drawablePadding="5dp"
            android:textColor="@drawable/radio_button_selector"
            />
        <RadioButton
            android:id="@+id/rb_radio_mine"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:drawableTop="@drawable/mine"
            android:text="@string/mine"
            android:button="@null"
            android:drawablePadding="5dp"
            android:textColor="@drawable/radio_button_selector"
            />
    </RadioGroup>
</LinearLayout>

        我們需要對每個RadioButton的圖片資源做一個選擇器,在drawable資料夾下新增四個選擇器,內容是(這是首頁的圖片選擇器,其他與此類似就不貼程式碼了):

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true" android:drawable="@mipmap/tab4_down"></item>
    <item android:state_checked="false" android:drawable="@mipmap/tab4"></item>
</selector>
之後我們還要對選中的RadioButton新增字型顏色的選擇器:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true" android:color="@color/textChecked"></item>
    <item android:state_checked="false" android:color="@color/textUnChecked"></item>
</selector>
之後進入Actiivity中,實現程式碼:
public class RadioGroupActivity extends AppCompatActivity implements RadioGroup.OnCheckedChangeListener {

    private RadioGroup mRadioGroup;
    private Fragment[] mFragments;
    private FrameLayout mLayout;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_radio_group);
        getSupportActionBar().hide();
        initView();
        initFragment();
        setListener();
    }

    private void setListener() {
        //對RadioGroup設定監聽事件(監聽點選選擇)
        mRadioGroup.setOnCheckedChangeListener(this);

    }

    private void initFragment() {
        //初始化要顯示的Fragment陣列
        mFragments=new Fragment[4];
        mFragments[0]=new HomepageFragment();
        mFragments[1]=new SubscriptionFragment();
        mFragments[2]=new FindFragment();
        mFragments[3]=new MineFragment();
        //獲取Fragment管理器
        FragmentManager manager=getSupportFragmentManager();
        //獲取事物(使用v4包下)
        FragmentTransaction transaction=manager.beginTransaction();
        //預設選中HomepageFragment替換Framelayout
        transaction.replace(R.id.fl_radio_show,mFragments[0]);
        //提交事物
        transaction.commit();
        //預設點選首頁
        mRadioGroup.check(R.id.rb_radio_homepage);
    }

    private void initView() {
        mRadioGroup= (RadioGroup) findViewById(R.id.rg_radio_navigation);
        mLayout= (FrameLayout) findViewById(R.id.fl_radio_show);
    }

    @Override
    public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
        //寫法與預設點選頁面的相同
        FragmentManager manager=getSupportFragmentManager();
        FragmentTransaction  transaction=manager.beginTransaction();
        switch(checkedId){
            case R.id.rb_radio_homepage:
                transaction.replace(R.id.fl_radio_show,mFragments[0]);
            break;
            case R.id.rb_radio_subscription:
                transaction.replace(R.id.fl_radio_show,mFragments[1]);
                break;
            case R.id.rb_radio_find:
                transaction.replace(R.id.fl_radio_show,mFragments[2]);
                break;
            case R.id.rb_radio_mine:
                transaction.replace(R.id.fl_radio_show,mFragments[3]);
                break;
        }
        transaction.commit();
    }
}
到此底部導航就已經完成,實現效果如下圖所示:

      

        2,在HomepageFragment中使用Tablayout+Fragment實現頂部導航

            首先Tablayout是Android5.0釋出的Design包中的元件,所以我們在使用之前必須加入Design包的依賴(這裡不會就自己百度吧)。然後在Homepage的資原始檔中的佈局如下:

<LinearLayout 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"
    android:orientation="vertical"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context="com.example.linktestproject.fragments.HomepageFragment">
    <android.support.design.widget.TabLayout
        android:id="@+id/tl_homepage_navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabIndicatorColor="@color/colorPrimary"
        app:tabSelectedTextColor="@color/textChecked"
        app:tabTextColor="@color/textUnChecked"
        >
    </android.support.design.widget.TabLayout>
    <android.support.v4.view.ViewPager
        android:id="@+id/vp_homepage_show"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
    </android.support.v4.view.ViewPager>
</LinearLayout>
 Tablayout的屬性(此處只是給出用到的三個屬性,其他常用的屬性會在Tablayout+Fragment中給出):
  app:tabIndicatorColor="@color/colorPrimary"    //下邊指示橫線的顏色
  app:tabSelectedTextColor="@color/textChecked"  //Text選中的文字顏色
 app:tabTextColor="@color/textUnChecked"         //沒有選中的文字顏色
在HomepageFragment中的程式碼是:
public class HomepageFragment extends Fragment {
    private ViewPager mViewPager;
    private TabLayout mTabLayout;
    private List<Fragment>mFragments;
    private List<String>mTitles;
    private HomepageAdapter mAdapter;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view=inflater.inflate(R.layout.fragment_homepage, container, false);
        initView(view);
        initData(view);
        setData();
        return view;
    }

    private void setData() {
        mViewPager.setAdapter(mAdapter);
        //設定Viewpager和Tablayout進行聯動
        mTabLayout.setupWithViewPager(mViewPager);
//        //將標題設定可以左右搖動而不是移動
//        mTabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
//        //設定預載入頁數
//        mViewPager.setOffscreenPageLimit(3);

    }

    private void initData(View view) {
        //初始化導航標題,如果是title在json資料中,在初始化的時候可以使用非同步任務載入的形式新增
        mTitles=new ArrayList<>();
        mTitles.add("熱門");
        mTitles.add("分類");
        mTitles.add("榜單");
        //初始化Fragment
        mFragments=new ArrayList<>();
        for (int i = 0; i <mTitles.size() ; i++) {
            if(i==0){
                mFragments.add(new HotFragment());
            }else if(i==1){
                mFragments.add(new ClassifyFragment());
            }else if(i==2){
                mFragments.add(new ListingFragment());
            }
        }
        //getSupportFragmentManager()是Activity巢狀fragment時使用
        //getChildFragmentManager()是Fragment巢狀Fragment時使用
        mAdapter=new HomepageAdapter(getChildFragmentManager(),mFragments,mTitles);
        mAdapter.notifyDataSetChanged();
    }

    private void initView(View view) {
         mViewPager= (ViewPager) view.findViewById(R.id.vp_homepage_show);
        mTabLayout= (TabLayout) view.findViewById(R.id.tl_homepage_navigation);
    }

}
用到的Viewpager的Adapter程式碼是:
public class HomepageAdapter extends FragmentPagerAdapter {
    private List<Fragment> mFragments;
    private List<String> mTitles;

    public HomepageAdapter(FragmentManager fm, List<Fragment> framents, List<String> titles) {
        super(fm);
        mFragments = framents;
        mTitles = titles;
    }

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

    @Override
    public int getCount() {
        return mFragments == null ? 0 : mFragments.size();
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return mTitles.get(position);
    }
}
最後所實現的效果如下所示:

     

二、使用Tablayout+Fragment實現底部導航

 注意:之後三種實現方式不再實現頂部導航,只實現底部導航,而在第二種方式中將詳細介紹Tablyout的用法及屬性。

        1,首先我們先總結一下Tablayout的基本使用(和頂部導航不同的方式實現)

第一種使用先看佈局檔案:

<android.support.constraint.ConstraintLayout 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"
    tools:context="com.example.linktestproject.TablayoutActivity">
    <android.support.design.widget.TabLayout
        android:id="@+id/tl_tablayout_navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
    </android.support.design.widget.TabLayout>
</android.support.constraint.ConstraintLayout>
在程式碼中:
        mTabLayout.addTab(mTabLayout.newTab().setText("Title1"));
        mTabLayout.addTab(mTabLayout.newTab().setText("Title2"));
        mTabLayout.addTab(mTabLayout.newTab().setText("Title3"));
        mTabLayout.addTab(mTabLayout.newTab().setText("Title4"));
第二種使用方式(佈局):
<android.support.constraint.ConstraintLayout 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"
    tools:context="com.example.linktestproject.TablayoutActivity">
    <android.support.design.widget.TabLayout
        android:id="@+id/tl_tablayout_navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
        <android.support.design.widget.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Title1"/>
        <android.support.design.widget.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Title2"/>
        <android.support.design.widget.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Title3"/>
        <android.support.design.widget.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Title4"/>
    </android.support.design.widget.TabLayout>
</android.support.constraint.ConstraintLayout>
二種方式實現的結果相同,如下圖所示:

     

下邊我們總結一下Tablayout的屬性用法:

app:tabSelectedTextColor="  "  //改變選中字型的顏色

app:tabTextColor="   "  //改變未選中字型的顏色

app:tabIndicatorColor="   "   //改變指示器下標的顏色

           app:tabBackground="  "    //改變整個TabLayout的顏色

           app:tabTextAppearance="  "  //改變Tablayout的內部字型大小

   app:tabPadding="xxdp"  //內部子控制元件的Padding值

   app:paddingEnd="xxdp"  //設定整個Tablayout的Padding值

   app:paddingStart="xxdp"

   app:tabMaxWidth="xxdp"  //設定最小和最大的tab寬度

   app:tabMinWidth="xxdp"

   app:tabContentStart="xxdp" //設定Tablayout開始位置的偏移量

   tabLayout.addTab(tabLayout.newTab().setText("Tab 1").setIcon(R.mipmap.ic_launcher));//為Tablayout新增圖片

       

       app:tabMode=“scrollable”  //這個屬性用於tab比較多的情況下,實現的結果如下圖:

     

  app:tabIndicatorHeight="  "//改變指示器下標的高度

 app:tabIndicatorHeight="0dp"//當設定為0時就會去掉指示器下標

     

    2,通過Tablayout+Fragment實現底部導航

首先佈局檔案很簡單:

<?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"
    tools:context="com.example.linktestproject.TablayoutActivity">
    <android.support.v4.view.ViewPager
        android:id="@+id/vp_tablayout_show"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        ></android.support.v4.view.ViewPager>
    <android.support.design.widget.TabLayout
        android:id="@+id/tl_tablayout_navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        style="@style/CustomTablayout"
        >
    </android.support.design.widget.TabLayout>
</LinearLayout>
  給Tablayout設定style樣式:
<style name="CustomTablayout" parent="Widget.Design.TabLayout">
        <item name="tabIndicatorHeight">0dp</item>
        <item name="tabSelectedTextColor">@color/textChecked</item>
        <item name="tabTextColor">@color/textUnChecked</item>
    </style>
切換頁面使用了ViewPager和Fragment,所以我們的介面卡Adapter的內容:
public class TablayoutAdapter extends FragmentPagerAdapter {
    private List<Fragment>mFragments;
    private List<String>mTitles;
    public TablayoutAdapter(FragmentManager fm, List<Fragment>mFragments, List<String>mTitles) {
        super(fm);
        this.mFragments=mFragments;
        this.mTitles=mTitles;
    }

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

    @Override
    public int getCount() {
        return mFragments==null?0:mFragments.size();
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return mTitles.get(position);
    }
}
最後是TablayoutActivity中的程式碼:
public class TablayoutActivity extends AppCompatActivity {

    private TabLayout mTabLayout;
    private ViewPager mViewPager;
    private List<Fragment>mFragments;
    private TablayoutAdapter mAdapter;
    private List<String>mTitles;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tablayout);
        getSupportActionBar().hide();
        initView();
        initData();
        setData();
        setListener();
    }

    private void setListener() {

    }

    private void setData() {
        mViewPager.setAdapter(mAdapter);
        mTabLayout.setupWithViewPager(mViewPager);

        for (int i = 0; i <mTabLayout.getTabCount() ; i++) {
            TabLayout.Tab tab=mTabLayout.getTabAt(i);
            Drawable drawable=null;
            switch(i){
                case 0:
                    //圖片資源我們同樣要使用選擇器,選擇器我們不能使用state_checked屬性,而應該使用state_selected屬性
                    drawable=getResources().getDrawable(R.drawable.hometablayout);
                break;
                case 1:
                    drawable=getResources().getDrawable(R.drawable.subtablayout);
                    break;
                case 2:
                    drawable=getResources().getDrawable(R.drawable.findtablayout);
                    break;
                case 3:
                    drawable=getResources().getDrawable(R.drawable.minetablayout);
                    break;
            }
                tab.setIcon(drawable);
        }

    }

    private void initData() {
        mTitles=new ArrayList<>();
        mTitles.add("首頁");
        mTitles.add("訂閱");
        mTitles.add("發現");
        mTitles.add("我的");
        mFragments=new ArrayList<>();
        for (int i = 0; i <mTitles.size(); i++) {
            if(i==0){
                mFragments.add(new HomepageFragment());
            }else if(i==1){
                mFragments.add(new SubscriptionFragment());
            }else if(i==2){
                mFragments.add(new FindFragment());
            }else  if(i==3){
                mFragments.add(new MineFragment());
            }
        }
         mAdapter=new TablayoutAdapter(getSupportFragmentManager(),mFragments,mTitles);
         mAdapter.notifyDataSetChanged();

    }

    private void initView() {
        mTabLayout= (TabLayout) findViewById(R.id.tl_tablayout_navigation);
        mViewPager= (ViewPager) findViewById(R.id.vp_tablayout_show);
    }
}
drawable=getResources().getDrawable(R.drawable.hometablayout);//這是一個已經過時的方法,
//現在是最新的方法是傳入兩個引數,但需要的最低版本有限制(Call requires API level 21 (current min is 15)):
drawable=getResources().getDrawable(R.drawable.hometablayout,null);
所以自己根據需要自己選擇

上邊註釋的圖片選擇器要根據控制元件選擇對應的屬性,選擇器內容如下:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="true" android:drawable="@mipmap/tab4_down"></item>
    <item android:state_selected="false" android:drawable="@mipmap/tab4"></item>
</selector>
以上就是Tablayout+ViewPager+Fragment實現底部導航的所有內容,實現的結果如下圖所示:
   

       本來準備四種方式實現底部導航,但篇幅較大所以把下邊兩種方式寫到下一遍部落格中,我在原始碼中新增各個Fragment的生命週期方法Log,可以自己執行比較這幾種實現方式。從我自己的開發經歷以及Fragment的生命週期的比較,我推薦使用反射機制實現底部導航,並且反射也是你進階需要掌握的一個知識點。