App主界面Tab实现方法

Android 程序中实现Tab类型界面很常见,所以想在这里总结一下,实现tab类型界面的几种方式,供大家参考。
一、TabActivity + TabWidget + TabHost.
实现TAB类型界面,首先想到的就是这种方式。但是在API level 13之后官方就不建议使用它了。

二、ViewPager + PageAdapter

目前最常见的tab界面就是使用viewpager来实现了。
先来说一下viewpager的一般使用步骤:

  1. 在布局文件中添加viewpager控件

  2. 在代码中设置viewpager适配器,该类继承与pagerAdapter或它的子类。必须实现以下四个方法:
    (1)getCount()
    (2)instantiateItem()
    (3)destroyItem()
    (4)isViewFromObject()

  3. 初始化viewpager控件,设置监听器

  4. 设置监听事件(setOnPageChangeListener)
    下面看一下这种方式的效果图:



    主要的功能代码如下:

    private void init() {  
      viewPager = (ViewPager) findViewById(R.id.first_vp);  
      LayoutInflater inflater = LayoutInflater.from(this);  
      View view1 = inflater.inflate(R.layout.first_layout1, null);  
      View view2 = inflater.inflate(R.layout.first_layout2, null);  
      View view3 = inflater.inflate(R.layout.first_layout3, null);  
      list.add(view1);  
      list.add(view2);  
      list.add(view3);  
    
    viewPager.setAdapter(pagerAdapter);  
    viewPager.setOnPageChangeListener(new OnPageChangeListener() {  
       @Override  
       public void onPageSelected(int arg0) {  
           setDots(arg0);  
       }  
    
       @Override  
       public void onPageScrolled(int arg0, float arg1, int arg2) {  
       }  
    
       @Override  
       public void onPageScrollStateChanged(int arg0) {  
       }  
    });  
    }  
    
     private PagerAdapter pagerAdapter = new PagerAdapter() {  
      //官方建议这么写  
      @Override  
      public boolean isViewFromObject(View arg0, Object arg1) {  
       return arg0 == arg1;  
    }  
     //返回一共有多少个界面  
     @Override  
    public int getCount() {  
       return list.size();  
    }  
    
      @Override  
      public Object instantiateItem(ViewGroup container, int position) {  
       container.addView(list.get(position));  
       return list.get(position);  
     }  
       //销毁一个item  
     @Override  
    public void destroyItem(ViewGroup container, int position, Object object) {  
       container.removeView(list.get(position));  
    }  
    };  
    
适配器中必须要实现以上的四个方法。

如果只有这几个页面,交互性肯定是不好的,所以需要添加“指示器”,用来标识当前的页面是哪一个!我在这里用点来实现。就像效果图显示的那样。


/** 
 * 初始化底部的点 
 */  
private void initDots() {  
    pointLayout = (LinearLayout) findViewById(R.id.point_layout);  
    dots = new ImageView[list.size()];  
    for (int i = 0; i < list.size(); i++) {  
        dots[i] = (ImageView) pointLayout.getChildAt(i);  
    }  
    currentIndex = 0;  
    dots[currentIndex].setBackgroundResource(R.drawable.dian_down);  
}  

/** 
 * 当滚动的时候更换点的背景图 
 */  
private void setDots(int position) {  
    if (position < 0 || position > list.size() - 1  
            || currentIndex == position) {  
        return;  
    }  
    dots[position].setBackgroundResource(R.drawable.dian_down);  
    dots[currentIndex].setBackgroundResource(R.drawable.dian);  
    currentIndex = position;  
}  

重点就是页面切换之后,点也要切换。这时候就用到了OnPageChangeListener中的onPageSelected(int arg0)这个方法了。

三、Fragment + FragmentManager
fragment相信大家在项目中肯定都用过。这个方法主要就是利用fragmentManager对fragment的事务管理功能。

// 三个选项卡  
private LinearLayout tab1Layout, tab2Layout, tab3Layout;  
// 默认选中第一个tab  
private int index = 1;  
// fragment管理类  
private FragmentManager fragmentManager;  
// 三个fragment  
private Fragment tab1Fragment, tab2Fragment, tab3Fragment;  

@Override  
protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(R.layout.activity_second);  
    fragmentManager = getSupportFragmentManager();  
    init();  
}  

/** 
 * 初始化控件 
 */  
private void init() {  
    tab1Layout = (LinearLayout) findViewById(R.id.tab1_layout);  
    tab2Layout = (LinearLayout) findViewById(R.id.tab2_layout);  
    tab3Layout = (LinearLayout) findViewById(R.id.tab3_layout);  

    tab1Layout.setOnClickListener(this);  
    tab2Layout.setOnClickListener(this);  
    tab3Layout.setOnClickListener(this);  
    //  
    setDefaultFragment();  
}  

/** 
 * 设置默认显示的fragment 
 */  
private void setDefaultFragment() {  
    FragmentTransaction transaction = fragmentManager.beginTransaction();  
    tab1Fragment = new Tab1Fragment();  
    transaction.replace(R.id.content_layout, tab1Fragment);  
    transaction.commit();  
}  

/** 
 *切换fragment 
 * @param newFragment 
 */  
private void replaceFragment(Fragment newFragment) {  
    FragmentTransaction transaction = fragmentManager.beginTransaction();  
    if (!newFragment.isAdded()) {  
        transaction.replace(R.id.content_layout, newFragment);  
        transaction.commit();  
    } else {  
        transaction.show(newFragment);  
    }  
}  

/** 
 * 改变现象卡的选中状态 
 */  
private void clearStatus() {  
    if (index == 1) {  
        tab1Layout.setBackgroundColor(getResources().getColor(R.color.tab));  
    } else if (index == 2) {  
        tab2Layout.setBackgroundColor(getResources().getColor(R.color.tab));  
    } else if (index == 3) {  
        tab3Layout.setBackgroundColor(getResources().getColor(R.color.tab));  
    }  
}  

@Override  
public void onClick(View v) {  
    clearStatus();  
    switch (v.getId()) {  
    case R.id.tab1_layout:  
        if (tab1Fragment == null) {  
            tab1Fragment = new Tab1Fragment();  
        }  
        replaceFragment(tab1Fragment);  
        tab1Layout.setBackgroundColor(getResources().getColor(  
                R.color.tab_down));  
        index = 1;  
        break;  
    case R.id.tab2_layout:  
        if (tab2Fragment == null) {  
            tab2Fragment = new Tab2Fragment();  
        }  
        replaceFragment(tab2Fragment);  
        tab2Layout.setBackgroundColor(getResources().getColor(  
                R.color.tab_down));  
        index = 2;  
        break;  
    case R.id.tab3_layout:  
        if (tab3Fragment == null) {  
            tab3Fragment = new Tab3Fragment();  
        }  
        replaceFragment(tab3Fragment);  
        tab3Layout.setBackgroundColor(getResources().getColor(  
                R.color.tab_down));  
        index = 3;  
        break;  
    }  
}  

每一个fragment对应一个布局,点击不同的按钮来切换页面。效果如下图:


四、ViewPager + Fragment + FragmentPagerAdapter
如果想使用fragment的时候又想可以左右滑动,就可以使用这种方式。主要的部分就在viewpager的适配器。它的适配器继承FragmentPagerAdapter.

public class FragmentAdapter extends FragmentPagerAdapter {  
private ArrayList<Fragment> list;  
public FragmentAdapter(FragmentManager fm, ArrayList<Fragment> list) {  
    super(fm);  
    this.list = list;  
}  
@Override  
public Fragment getItem(int arg0) {  
    return list.get(arg0);  
}  
@Override  
public int getCount() {  
    return list.size();  
}  
}  

需要传入FragmentManager对象和一个存放fragment的list对象。

/** 
 * 初始化viewpager 
 */  
private void initViewPager() {  
    viewPager = (ViewPager) findViewById(R.id.third_vp);  
    fragmentsList = new ArrayList<>();  
    Fragment fragment = new Tab1Fragment();  
    fragmentsList.add(fragment);  
    fragment = new Tab2Fragment();  
    fragmentsList.add(fragment);  
    fragment = new Tab3Fragment();  
    fragmentsList.add(fragment);  

    viewPager.setAdapter(new FragmentAdapter(getSupportFragmentManager(),  
            fragmentsList));  
    viewPager.setCurrentItem(0);  
    viewPager.setOnPageChangeListener(this);  
}  

对button添加点击事件。

  @Override  
 public void onClick(View v) {  
    switch (v.getId()) {  
    case R.id.tab1_tv:  
        viewPager.setCurrentItem(0);  
        break;  
    case R.id.tab2_tv:  
        viewPager.setCurrentItem(1);  
        break;  
    case R.id.tab3_tv:  
        viewPager.setCurrentItem(2);  
        break;  
    }  
}  

我在布局文件中添加了一个imageview作为指示器。如果想第一种tab类型界面的实现方式那样在onPageSelected()方法中进行设置,效果是只能当页面完全切换过来之后才能把指示器移动过去。要想实现滑动页面的时候同时移动指示器,就需要在onPageScrolled()方法中进行设置。

 @Override  
public void onPageScrolled(int position, float positionOffset,  
        int positionOffsetPixels) {  
    offset = (screen1_3 - cursorImg.getLayoutParams().width) / 2;  
    Log.d("111", position + "--" + positionOffset + "--"  
            + positionOffsetPixels);  
    final float scale = getResources().getDisplayMetrics().density;  
    if (position == 0) {// 0<->1  
        lp.leftMargin = (int) (positionOffsetPixels / 3) + offset;  
    } else if (position == 1) {// 1<->2  
        lp.leftMargin = (int) (positionOffsetPixels / 3) + screen1_3 +offset;  
    }  
    cursorImg.setLayoutParams(lp);  
    currentIndex = position;  
}  

onPageScrolled中的三个参数比较重要。第一个参数是position。它的含义是表示当前显示的界面中的第一个界面。意思就是的当滑动的时候,有可能出现两个界面,position指的是左边的界面。第二个参数是positionOffset指的是偏移量的比例,取值范围是[0, 1)。第三个参数是positionOffsetPixels是指偏移的像素值。后两个参数都相对页面(一个page)来说的。
我之前有看到过设置指示器的时候用的前两个参数的,我也试了一下,OK的。不过感觉比较复杂,看了一下官方api,用第三个参数更简单。关键就是理解第一个参数position。用这种方法我只在代码里有两个判断就可以完成了。
效果图如下:


五、Viewpager + PagerTitleStrip / PagerTabStrip
这种方式没有上一种效果好看,而且标题变动。不在详细介绍。

六、Viewpager + TabLayout
TabLayout 是官方的,最好的是它可以兼容到2.2以上版本,包括2.2。很简单。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
android:layout_width="match_parent"  
android:layout_height="match_parent"  
xmlns:app="http://schemas.android.com/apk/res-auto"  
android:orientation="vertical">  

<android.support.design.widget.TabLayout  
    android:id="@+id/tab_FindFragment_title"  
    android:layout_width="match_parent"  
    android:layout_height="wrap_content"  
    android:background="@color/titleBlue"  
    app:tabIndicatorColor="@color/white"  
    app:tabSelectedTextColor="@color/gray"  
    app:tabTextColor="@color/white"  
    />  


<android.support.v4.view.ViewPager  
    android:id="@+id/vp_FindFragment_pager"  
    android:layout_width="fill_parent"  
    android:layout_height="0dp"  
    android:layout_weight="1"  
    />  
</LinearLayout>  

这里面没有什么特别的,就是添加了一个TabLayout和Viewpager作为上下的布局。其中

app:tabIndicatorColor="@color/white"                 // 下方滚动的下划线颜色  
app:tabSelectedTextColor="@color/gray"               // tab被选中后,文字的颜色  
app:tabTextColor="@color/white"                      // tab默认的文字颜色  

因为这里面我每个栏目下,都会有一些列表,所以采用list<View>的方式,在里面切换layout不太适合,所以我采用了List<Fragment>来直接加载多个fragment

public class TabAdapter extends FragmentPagerAdapter {  

private List<Fragment> list_fragment;                         //fragment列表  
private List<String> list_Title;                              //tab名的列表  



public TabAdapter(FragmentManager fm,List<Fragment> list_fragment,List<String> list_Title) {  
    super(fm);  
    this.list_fragment = list_fragment;  
    this.list_Title = list_Title;  
}  

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

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

//此方法用来显示tab上的名字  
@Override  
public CharSequence getPageTitle(int position) {  

    return list_Title.get(position % list_Title.size());  
}  
}  

创建Fragment

public class PageFragment extends Fragment {

public static final String ARG_PAGE = "ARG_PAGE";
private int mPage;

public static PageFragment newInstance(int page) {
    Bundle args = new Bundle();
    args.putInt(ARG_PAGE, page);
    PageFragment pageFragment = new PageFragment();
    pageFragment.setArguments(args);
    return pageFragment;
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mPage = getArguments().getInt(ARG_PAGE);
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_page, container, false);
    TextView textView = (TextView) view;
    textView.setText("Fragment #" + mPage);
    return view;
}
}

使用

public class TabFragment extends Fragment {  

private TabLayout tab_FindFragment_title;                            //定义TabLayout  
private ViewPager vp_FindFragment_pager;                             //定义viewPager  
private FragmentPagerAdapter fAdapter;                               //定义adapter  

private List<Fragment> list_fragment;                                //定义要装fragment的列表  
private List<String> list_title;                                     //tab名称列表  


@Override  
public View onCreateView(LayoutInflater inflater, ViewGroup container,  
                         Bundle savedInstanceState) {  
    View view = inflater.inflate(R.layout.fragment_find, container, false);  

    initControls(view);  

    return view;  
}  

/** 
 * 初始化各控件 
 * @param view 
 */  
private void initControls(View view) {  

    tab_FindFragment_title = (TabLayout)view.findViewById(R.id.tab_FindFragment_title);  
    vp_FindFragment_pager = (ViewPager)view.findViewById(R.id.vp_FindFragment_pager);  


    //将fragment装进列表中  
    list_fragment = new ArrayList<>();  
    list_fragment.add(new PageFragment.newInstance(1));  
    list_fragment.add(new PageFragment.newInstance(2));  
    list_fragment.add(new PageFragment.newInstance(3));  

    //将名称加载tab名字列表,正常情况下,我们应该在values/arrays.xml中进行定义然后调用  
    list_title = new ArrayList<>();  
    list_title.add("tab1");  
    list_title.add("tab2");  
    list_title.add("tab3");  
  

    //设置TabLayout的模式  
    tab_FindFragment_title.setTabMode(TabLayout.MODE_FIXED);  
    //为TabLayout添加tab名称  
    tab_FindFragment_title.addTab(tab_FindFragment_title.newTab().setText(list_title.get(0)));  
    tab_FindFragment_title.addTab(tab_FindFragment_title.newTab().setText(list_title.get(1)));  
    tab_FindFragment_title.addTab(tab_FindFragment_title.newTab().setText(list_title.get(2)));  


    fAdapter = new TabAdapter(getActivity().getSupportFragmentManager(),list_fragment,list_title);  

    //viewpager加载adapter  
    vp_FindFragment_pager.setAdapter(fAdapter);  
    //tab_FindFragment_title.setViewPager(vp_FindFragment_pager);  
    //TabLayout加载viewpager  
    tab_FindFragment_title.setupWithViewPager(vp_FindFragment_pager);  
    //tab_FindFragment_title.set  
}  
}  

七、TabPageIndicator/PagerSlidingTabStrip+ViewPager+FragmentPagerAdapter
TabPageIndicator 和 PagerSlidingTabStrip很像,不过有点过时了,不在详细介绍。
八、FlycoTabLayout
这个是我见过的最好的,大家可以点击看一下, 不在详细介绍。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容