Android Tab欄封裝實踐——打造微信底部tab欄

CathernBrin 8年前發布 | 17K 次閱讀 tab Android開發 移動開發 Android

目前市面上很多App都采用底部一個Tab欄,管理四到五個Tab,然后選擇切換頁面的方式的設計,這雖然不太符合metro design,但確是一個不容易出錯而又符合國人使用習慣的設計方式。比如微信,支付寶,網易新聞,簡書等都采用這種設計。而所謂封裝一定是基于某種確定的業務需求,所以針對上述的通用設計方式,我們可以做一個比較理想化的封裝。
 

為什么要做封裝

你可能會覺得,這就是一個選擇切換嘛,我只要做些if else判斷就好了。但是Tab欄一般用在首頁,業務邏輯代碼量就不用說了,如果這時候不想被各種if else , swich case 搞得心力交瘁,那么我們少寫些冗余代碼又有何妨。畢竟代碼不止眼前的茍且,還有設計改版和需求變更,某天產品經理更你說要改版,修改完xml,再去修改if else,然后再去修改click。。。想想也是醉了。所以這里要說的封裝當然不會是,一個LinearLayout塞幾個布局,然后做swich case去切換fragment,我希望布局里只需要include一個TabView,代碼里也不需要N多findviewbyId就能實現這個功能。
基于以上分析,開工編碼。我們還是以微信為例吧,假設底部Tab欄共有四個按鈕,上面icon,下面文本。那么我們先把這一樣式的xml寫出來。

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
    <ImageView  android:id="@+id/tab_image" android:layout_width="wrap_content" android:layout_height="wrap_content" />
    <TextView  android:id="@+id/tab_lable" android:layout_width="wrap_content" android:layout_height="wrap_content" />
</merge>

上面是四個Tab按鈕的通用布局,我們還需要寫個TabView來解析這個布局。

public class TabView extends LinearLayout implements View.OnClickListener{

    private ImageView mTabImage;
    private TextView mTabLable;

    public TabView(Context context) {
        super(context);
        initView(context);
    }

    public TabView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    public TabView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView(context);
    }

    private void initView(Context context){
        setOrientation(VERTICAL);
        setGravity(Gravity.CENTER);
        LayoutInflater.from(context).inflate(R.layout.tab_view,this,true);
        mTabImage=(ImageView)findViewById(R.id.tab_image);
        mTabLable=(TextView)findViewById(R.id.tab_lable);

    }

    public void initData(TabItem tabItem){

        mTabImage.setImageResource(tabItem.imageResId);
        mTabLable.setText(tabItem.lableResId);
    }

    @Override
    public void onClick(View v) {

    }
}

到這里我們已經完成了單個TabView按鈕的解析,但是假如我有四個按鈕,要在xml里include四次嘛,要在代碼里findviewById四次嘛,對于這樣的hard code我是拒絕的,我希望在xml里只include一個view,代碼里只findviewById一次,所以我們還需要給TabView再包一層,給四個Tab按鈕一個父容器TabLayout,我們只需要include一個父容器,就能達到現在一片頂過去五片,一口氣上五樓,不費勁的效果。那我要在TabLayout里寫個LinearLayout塞四個TavView嘛,當然不是,我們把一個TabView看做是一個對象,需要幾個就new幾個,然后add到TabLayout里。
所以首先我需要一個TabView的對象TabItem。

public class TabItem {

    /** * icon */
    public int imageResId;
    /** * 文本 */
    public int lableResId;


    public TabItem(int imageResId, int lableResId) {
        this.imageResId = imageResId;
        this.lableResId = lableResId;
    }
}

然后再寫個父容器TabLayout

public class TabLayout extends LinearLayout implements View.OnClickListener{

    private ArrayList<TabItem> tabs;
    private OnTabClickListener listener;
    public TabLayout(Context context) {
        super(context);
        initView();
    }

    public TabLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public TabLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView();
    }

    private void initView(){
        setOrientation(HORIZONTAL);
    }

    public void initData(ArrayList<TabItem>tabs,OnTabClickListener listener){
        this.tabs=tabs;
        this.listener=listener;
        LinearLayout.LayoutParams params=new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT);
        params.weight=1;
        if(tabs!=null&&tabs.size()>0){
            TabView mTabView=null;
            for(int i=0;i< tabs.size();i++){
                mTabView=new TabView(getContext());
                mTabView.setTag(tabs.get(i));
                mTabView.initData(tabs.get(i));
                mTabView.setOnClickListener(this);
                addView(mTabView,params);
            }

        }else{
            throw new IllegalArgumentException("tabs can not be empty");
        }
    }

    @Override
    public void onClick(View v) {
        listener.onTabClick((TabItem)v.getTag());
    }

    public interface OnTabClickListener{

        void onTabClick(TabItem tabItem);
    }
}

以上都是小學五年級水平的代碼,所以我就不寫注釋了,也不需要做過多講解,直接看代碼。到這里我們基本完成了底部TabLayout代碼的編寫,那我們寫個activity測試下效果先。
先把TabLayout include到布局中

<?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:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">

    <FrameLayout  android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@+id/tab_layout" />
    <star.yx.tabview.TabLayout  android:id="@+id/tab_layout" android:layout_alignParentBottom="true" android:layout_width="match_parent" android:layout_height="56dp" />
</RelativeLayout>

底部需要幾個Tab按鈕,就在MainActiviy里new幾個然后add到TabLayout即可,所以有一天產品經理跟你說需要增加一個按鈕,只需要再new一個就好,boss說調整下按鈕順序,就只要調整下new出的TabView順序即可,代碼是不是變得不那么茍且,似乎看見了詩和遠方。
這里寫圖片描述

public class MainActivity extends ActionBarActivity implements TabLayout.OnTabClickListener{

    private TabLayout mTabLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initData();
    }
    private void initView(){
        mTabLayout=(TabLayout)findViewById(R.id.tab_layout);

    }

    private void initData(){

        ArrayList<TabItem>tabs=new ArrayList<TabItem>();
        tabs.add(new TabItem(R.drawable.selector_tab_msg,R.string.wechat));
        tabs.add(new TabItem(R.drawable.selector_tab_contact,R.string.contacts));
        tabs.add(new TabItem(R.drawable.selector_tab_moments,R.string.discover));
        tabs.add(new TabItem(R.drawable.selector_tab_profile,R.string.me));
        mTabLayout.initData(tabs, this);

    }

    @Override
    public void onTabClick(TabItem tabItem) {

    }
}

但是我們還沒有加點擊事件,重點來了,我要在點擊事件里做if else判斷Fragment的顯示隱藏嘛?但if else真的很煩耶,如果不用if else判斷,反射就要出場了,我們可以在TabItem中增加一個變量

public Class<? extends BaseFragment>tagFragmentClz;

然后構造函數里也加一個參數,先偷個懶姑且寫在構造函數里。

public TabItem(int imageResId, int lableResId, Class<? extends BaseFragment> tagFragmentClz) {
    this.imageResId = imageResId;
    this.lableResId = lableResId;
    this.tagFragmentClz = tagFragmentClz;
}

相應的MainActivity里的引用也要修改下,第三個參數就傳入相應的Fragment。然后點擊事件的方法如下:

@Override
public void onTabClick(TabItem tabItem) {
    try {
    BaseFragment fragment= tabItem.tagFragmentClz.newInstance();
        getSupportFragmentManager().beginTransaction().replace(R.id.fragment,fragment).commitAllowingStateLoss();

    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
}

這里寫圖片描述
這樣我們就完成了TabView點擊切換Fragment,然后再處理下按鈕的選中狀態。一個模仿微信的底部導航欄就初見雛形了,但是微信是可以滑動切換的,我們這個還不能滑動切換,那就加上唄,所以我們還要對以上代碼做些調整,毫無疑問這個時候viewpager要出場了。我們把MainActivity中之前的Framelayout替換成Viewpager。

<android.support.v4.view.ViewPager
    android:id="@+id/viewpager"
    android:layout_above="@id/tab_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

還要寫一個viewPager的適配器,這個時候我們選擇把adapter寫為內部類,這樣會更方便一點獲取Fragment。

public class FragAdapter extends FragmentPagerAdapter {


    public FragAdapter(FragmentManager fm) {
        super(fm);
        // TODO Auto-generated constructor stub
    }

    @Override
    public Fragment getItem(int arg0) {
        // TODO Auto-generated method stub
        try {
            return tabs.get(arg0).tagFragmentClz.newInstance();

        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return fragment;
    }

    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return tabs.size();
    }

}

適配好viewpager后,滑動的時候我們還需要對title欄和底部Tab欄做相應的狀態改變。這里viewPager只需要實現OnPageChangeListener接口,在onPageSelected(int position)方法中做相應的處理。我這里的title用了actionbar。

@Override
public void onPageSelected(int position) {
    mTabLayout.setCurrentTab(position);
    actionBar.setTitle(tabs.get(position).lableResId);
}

滑動的時候要改變狀態,那相應的點擊tab欄也要做類似操作。

@Override
public void onTabClick(TabItem tabItem) {

    actionBar.setTitle(tabItem.lableResId);
    mViewPager.setCurrentItem(tabs.indexOf(tabItem));

}

其中tabLayout中的setCurrentTab(int i)方法如下。我們聲明兩個變量,tabCount用來記錄底部tabView的個數,selectView用來標識被選中的View。

public void setCurrentTab(int i) {
    if (i < tabCount && i >= 0) {
        View view = getChildAt(i);
    if (selectView != view) {
        view.setSelected(true);
        if (selectView != null) {
            selectView.setSelected(false);
        }
        selectView = view;
    }
    }
}

自此一個模仿微信的底部Tab欄的封裝基本實現了。伸手黨的福音:GitHub
為方便大家學習和交流Android開發,建了個群,歡迎大家加入。

QQ群:

183899857
這里寫圖片描述

來自: http://blog.csdn.net/lfdfhl/article/details/51122334

 本文由用戶 CathernBrin 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!