Android TabContainerView 實(shí)現(xiàn)底部導(dǎo)航欄效果 V1.0

本篇文章已授權(quán)微信公眾號(hào) guolin_blog (郭霖)獨(dú)家發(fā)布


xiaoguo.gif

上圖效果大家應(yīng)該都很熟悉了,基本市面上的App都會(huì)用到這種布局效果,實(shí)現(xiàn)起來(lái)也很簡(jiǎn)單,就是上面一個(gè)ViewPager,下面一個(gè)線性布局

TabContainerView就是把實(shí)現(xiàn)邏輯封裝起來(lái),讓開(kāi)發(fā)者可以通過(guò)更簡(jiǎn)單的代碼實(shí)現(xiàn)這種布局效果,提高工作效率。

使用
TabContainerView tabContainerView = (TabContainerView) findViewById(R.id.container_tab);
//設(shè)置適配器配置數(shù)據(jù)
tabContainerView.setAdapter(new MainTabContainerAdapter(getSupportFragmentManager(),
new Fragment[] {new MainFragment(), new WorkFragment(), new AppFragment(), new MineFragment()}));

就是這么簡(jiǎn)單,調(diào)用兩行代碼就可以實(shí)現(xiàn)了,不過(guò)需要我們自己創(chuàng)建一個(gè)適配器,MainTabContainerAdapter就是自己創(chuàng)建的,它需要繼承BaseAdapter來(lái)實(shí)現(xiàn)里面的抽象方法,BaseAdapter是此項(xiàng)目當(dāng)中自定義的抽象類。

思路

TabContainerView是一個(gè)RelativeLayout布局,整個(gè)布局由兩部分組成:底部布局,內(nèi)容布局

底部布局為一個(gè)LinearLayout布局,里面的單個(gè)Tab也是一個(gè)LinarLayout布局;中間的內(nèi)部區(qū)域是一個(gè)ViewPager。

實(shí)現(xiàn)

項(xiàng)目由6個(gè)類組成

//暴露給開(kāi)發(fā)者的View,主要負(fù)責(zé)添加底部和ViewPager的布局
TabContainerView 
//底部布局的單個(gè)布局,包含單個(gè)布局的文本,圖片屬性信息
Tab
//底部布局的整體布局,負(fù)責(zé)Tab布局的添加和狀態(tài)切換
TabHost
//Tab選中的監(jiān)聽(tīng)
OnTabSelectedListener
//適配器提供了底部文本內(nèi)容,圖片內(nèi)容,fragment內(nèi)容
BaseAdapter
//內(nèi)容ViewPager的適配器
TabViewPagerAdapter

我們先來(lái)分析調(diào)用的第一行代碼
TabContainerView tabContainerView = (TabContainerView) findViewById(R.id.container_tab);

看看它的構(gòu)造方法

 public TabContainerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
 }

構(gòu)造方法調(diào)用了init方法

private void init(Context context, AttributeSet attrs) {
       initStyle(context, attrs);
       initTabHost(context);
       initDivideLine(context);
       initViewPager(context);

       tabHost.setContentViewPager(contentViewPager);
}

分別初始化了自定義屬性,底部的TabHost,分割線,ViewPager等;
initStyle就是初始化一些自定義的屬性,沒(méi)啥好說(shuō)的;
我們來(lái)看下initTabHost方法

 private void initTabHost(Context context) {
        tabHost = new TabHost(context);
        addView(tabHost.getRootView());
 }

 public TabHost(Context context) {
        this.context = context;
        initView();
 }

 private void initView() {
        rootView = new LinearLayout(context);
        rootView.setOrientation(LinearLayout.HORIZONTAL);
        rootView.setId(R.id.linearlayout_tab);

        RelativeLayout.LayoutParams rootViewLp = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        rootViewLp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
        rootView.setLayoutParams(rootViewLp);
 }

initTabHost方法里創(chuàng)建了一個(gè)TabHost,TabHost的構(gòu)造方法里調(diào)用initView方法,方法創(chuàng)建RootView布局,設(shè)置長(zhǎng)寬, 位置等屬性,然后把TabHost的根布局添加到TabContainerView布局當(dāng)中。

initDivideLine方法創(chuàng)建一個(gè)分割線View添加到布局當(dāng)中
initViewPager方法創(chuàng)建一個(gè)ViewPager添加到布局當(dāng)中

下面再來(lái)看看第二行代碼里面做了些什么事情
tabContainerView.setAdapter(new MainTabContainerAdapter(getSupportFragmentManager(),
new Fragment[] {new MainFragment(), new WorkFragment(), new AppFragment(), new MineFragment()}));

 public void setAdapter(BaseAdapter baseAdapter) {
        setAdapter(baseAdapter, 0);
 }

 public void setAdapter(BaseAdapter baseAdapter, int index) {
        if (baseAdapter == null) return;
        tabHost.addTabs(baseAdapter, textSize, textColor, selectedTextColor);
        contentViewPager.setAdapter(new TabViewPagerAdapter(baseAdapter.getFragmentManager(), baseAdapter.getFragmentArray()));

        setCurrentItem(index);
 }

BaseAdapter是個(gè)抽象類

public abstract class BaseAdapter {

    /**
     *  tab數(shù)量
     */
    public abstract int getCount();

    /**
     * tab text 數(shù)組
     */
    public abstract String[] getTextArray();

    /**
     * tab icon 數(shù)組
     */
    public abstract int[] getIconImageArray();

    /**
     * tab icon 選中 數(shù)組
     */
    public abstract int[] getSelectedIconImageArray();

    /**
     * fragment 數(shù)組
     */
    public abstract Fragment[] getFragmentArray();

    public abstract FragmentManager getFragmentManager();

}

它提供了容器當(dāng)中需要的文本,圖片,還有內(nèi)容區(qū)的fragment信息

方法里調(diào)用TabHost的addTabs方法給TabHost添加Tab

 tabHost.addTabs(baseAdapter, textSize, textColor, selectedTextColor);

 public void addTabs(BaseAdapter baseAdapter, int textSize, int textColor, int selectedTextColor) {
        int count = baseAdapter.getCount();
        String[] textArray = baseAdapter.getTextArray();
        int[] iconImageArray = baseAdapter.getIconImageArray();
        int[] selectedIconImageArray = baseAdapter.getSelectedIconImageArray();

        if (count == 0 || textArray == null || iconImageArray == null || selectedIconImageArray == null) return;
        if (textArray.length != count || iconImageArray.length != count || selectedIconImageArray.length != count) return;

        for (int i = 0; i < count; i++) {
            Tab tab = new Tab(context, textArray[i], textSize, textColor, selectedTextColor, iconImageArray[i], selectedIconImageArray[i], i);
            addTab(tab);
        }
    }

通過(guò)方法得到文本,圖片等數(shù)組,然后通過(guò)循環(huán)創(chuàng)建Tab對(duì)象,添加到TabHost布局當(dāng)中,我們來(lái)看看Tab的構(gòu)造方法

 public Tab(Context context, String text, int textSize, int textColor, int selectedTextColor, int iconImage, int selectedIconImage, int index) {
        this.context = context;
        this.text = text;
        this.textSize = textSize;
        this.textColor = textColor;
        this.selectedTextColor = selectedTextColor;

        this.iconImage = iconImage;
        this.selectedIconImage = selectedIconImage;
        this.index = index;

        init();
    }

    private void init() {
        initView();

        rootView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                tabSelected();
            }
        });
    }

    private void initView() {
        rootView = new LinearLayout(context);
        rootView.setOrientation(LinearLayout.VERTICAL);
        rootView.setGravity(Gravity.CENTER_HORIZONTAL);
        rootView.setPadding(0, 25, 0, 0);
        LinearLayout.LayoutParams rootViewLp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        rootViewLp.weight = 1;

        rootView.setLayoutParams(rootViewLp);

        /**
         *  icon view
         */
        iconImageView = new ImageView(context);
        iconImageView.setImageResource(iconImage);
        iconImageView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        rootView.addView(iconImageView);

        /**
         *  text view
         */
        textTextView = new TextView(context);
        textTextView.setText(text);
        textTextView.setTextColor(textColor);
        textTextView.setTextSize(textSize);
        textTextView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        rootView.addView(textTextView);
    }

Tab的構(gòu)造方法就是給文本和圖片的屬性設(shè)值,添加監(jiān)聽(tīng),創(chuàng)建Tab需要的文本,圖片布局
接下來(lái)給ViewPager設(shè)置適配器,添加fragment

 contentViewPager.setAdapter(new TabViewPagerAdapter(baseAdapter.getFragmentManager(), baseAdapter.getFragmentArray()));

這樣底部的Tab和內(nèi)容區(qū)域的ViewPager數(shù)據(jù)都填充完成了

接下來(lái)需要設(shè)置點(diǎn)擊狀態(tài)的切換,從Tab類的init方法里可以看出給RootView添加了點(diǎn)擊事件,onClick方法會(huì)調(diào)用TabHost設(shè)置給Tab的監(jiān)聽(tīng)回調(diào)類,下面的代碼就是TabHost給Tab添加的監(jiān)聽(tīng)

//TabHost里給Tab添加Tab選中的監(jiān)聽(tīng)
private void addTabChangeListener(Tab tab) {
      tab.setOnTabSelectedListener(new OnTabSelectedListener() {
           @Override
            public void onTabSelected(Tab tab) {
                contentViewPager.setCurrentItem(tab.getIndex());
            }
        });
}

onTabSelected方法里設(shè)置contentViewPager當(dāng)前選中的Item,然后會(huì)回調(diào)到ViewPager監(jiān)聽(tīng)類OnPageChangeListener的onPageSelected方法

 public void onPageSelected(int position) {
       tabHost.onChangeTabHostStatus(position);
       Tab selectedTab = tabHost.getTabForIndex(position);
       if (onTabSelectedListener != null && selectedTab != null) 
           onTabSelectedListener.onTabSelected(selectedTab);
 }

首先調(diào)用onChangeTabHostStatus方法

 public void onChangeTabHostStatus(int index) {
        for (int i = 0, size = tabList.size(); i < size; i++) {
            Tab tab = tabList.get(i);
            tab.setTabIsSelected(index == i ? true : false);
        }
  }
.```
循環(huán)TabList,根據(jù)index判斷Tab狀態(tài)的選中與否
然后調(diào)用回調(diào)監(jiān)聽(tīng)onTabSelectedListener的onTabSelected方法

到此為止整個(gè)項(xiàng)目的實(shí)現(xiàn)過(guò)程就分析完了。

####結(jié)束語(yǔ)
整個(gè)項(xiàng)目的實(shí)現(xiàn)并沒(méi)有任何難度,把它封裝成一個(gè)View是為了以后在項(xiàng)目中更好更快的實(shí)現(xiàn)這種效果,提升開(kāi)發(fā)效率

想看完整代碼的可以移步至:https://github.com/chenpengfei88/TabContainerView
歡迎大家Star,F(xiàn)ollow,謝謝。

TabContainerView V2.0版本(http://www.itdecent.cn/p/9aaff43bbf9f  )

我還有一篇封裝布局狀態(tài)切換的文章,大家有興趣也可以看看
http://www.itdecent.cn/p/9d53893b3eda
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容