FlycoTabLayout 从头到脚

时间:2021-04-16 04:10:25

简介

FlycoTabLayout,是一个比Google原生TabLayout 功能更强大的TabLayout库。目前有3种TabLayout:

  • SlidingTabLayout
  • CommonTabLayout
  • SegmentTabLayout

具体介绍和使用方法参考开源库的Wiki

官方示例:

FlycoTabLayout 从头到脚


源码分析

共有属性名称 格式 描述
tl_indicator_color color 设置显示器颜色
tl_indicator_height dimension 设置显示器高度
tl_indicator_margin_left dimension 设置显示器margin,当indicator_width大于0,无效
tl_indicator_margin_top dimension 设置显示器margin,当indicator_width大于0,无效
tl_indicator_margin_right dimension 设置显示器margin,当indicator_width大于0,无效
tl_indicator_margin_bottom dimension 设置显示器margin,当indicator_width大于0,无效
tl_indicator_corner_radius dimension 设置显示器圆角弧度
tl_divider_color color 设置分割线颜色
tl_divider_width dimension 设置分割线宽度
tl_divider_padding dimension 设置分割线的paddingTop和paddingBottom
tl_tab_padding dimension 设置tab的paddingLeft和paddingRight
tl_tab_space_equal boolean 设置tab大小等分
tl_tab_width dimension 设置tab固定大小
tl_textsize dimension 设置字体大小
tl_textSelectColor color 设置字体选中颜色
tl_textUnselectColor color 设置字体未选中颜色
tl_textBold boolean 设置字体加粗
tl_textAllCaps boolean 设置字体全大写

1. SlidingTabLayout

1.1 特有属性

特有属性 格式 描述
tl_indicator_width dimension 设置显示器固定宽度
tl_indicator_gravity enum 设置显示器上方(TOP)还是下方(BOTTOM),只对常规显示器有用
tl_indicator_style enum 设置显示器为常规(NORMAL)或三角形(TRIANGLE)或背景色块(BLOCK)
tl_indicator_width_equal_title boolean 设置显示器与标题一样长(only for SlidingTabLayout)
tl_underline_color color 设置下划线颜色
tl_underline_height dimension 设置下划线高度
tl_underline_gravity enum 设置下划线上方(TOP)还是下方(BOTTOM)

1.2 类结构

1.2.1 构造方法

FlycoTabLayout 从头到脚

第一个调用第二个,第二个调用第三个,第三个获取自定义属性值。

public SlidingTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    setFillViewport(true);//设置滚动视图是否可以伸缩其内容以填充视口
    setWillNotDraw(false);//重写onDraw方法,需要调用这个方法来清除flag
    setClipChildren(false);//不限制child在其范围内绘制
    setClipToPadding(false);//滚动时child可以绘制到padding区域

    this.mContext = context;
    mTabsContainer = new LinearLayout(context);//tab容器
    addView(mTabsContainer);//添加到HorizontalScrollView中

    obtainAttributes(context, attrs);//获取自定义属性,常用的方法,TypedArray记得回收

    //获取layout_height属性的值,这个方法比较溜,之前没见过
    String height = attrs.getAttributeValue("http://schemas.android.com/apk/res/android", "layout_height");

   //针对height做处理
   if (height.equals(ViewGroup.LayoutParams.MATCH_PARENT + "")) {
    } else if (height.equals(ViewGroup.LayoutParams.WRAP_CONTENT + "")) {
    } else {
        int[] systemAttrs = {android.R.attr.layout_height};
        TypedArray a = context.obtainStyledAttributes(attrs, systemAttrs);
        //获取高度
        mHeight = a.getDimensionPixelSize(0, ViewGroup.LayoutParams.WRAP_CONTENT);
        a.recycle();
    }
}
1.2.2 ViewPager

FlycoTabLayout 从头到脚

FlycoTabLayout 从头到脚

方法 描述
setViewPager(ViewPager vp) 设置ViewPager内容
public void setViewPager(ViewPager vp, String[] titles) 设置ViewPager内容和标签页的标题
    /** 关联ViewPager */
    public void setViewPager(ViewPager vp) {
        if (vp == null || vp.getAdapter() == null) {
            throw new IllegalStateException("ViewPager or ViewPager adapter can not be NULL !");
        }
        /*本地赋值*/
        this.mViewPager = vp;
        /*重新绑定OnPageChangeListener*/
        this.mViewPager.removeOnPageChangeListener(this);
        this.mViewPager.addOnPageChangeListener(this);
        /*viewpager变化,tab页响应处理处理*/
        notifyDataSetChanged();
    }
    /** 更新数据 */
    public void notifyDataSetChanged() {
        mTabsContainer.removeAllViews();//清空tab
        this.mTabCount = mTitles == null ? mViewPager.getAdapter().getCount() : mTitles.size();//获取tab数量,优先级mTitles > ViewPager的默认标题

        /*添加tab*/
        View tabView;
        for (int i = 0; i < mTabCount; i++) {
            tabView = View.inflate(mContext, R.layout.layout_tab, null);
            CharSequence pageTitle = mTitles == null ? mViewPager.getAdapter().getPageTitle(i) : mTitles.get(i);
            addTab(i, pageTitle.toString(), tabView);
        }
        //更新选中未选中状态更新tab
        updateTabStyles();
    }
  /** 创建并添加tab */
    private void addTab(final int position, String title, View tabView) {
        //设置标题
        TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title);
        if (tv_tab_title != null) {
            if (title != null) tv_tab_title.setText(title);
        }
        //绑定点击事件,与ViewPager联动
        tabView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = mTabsContainer.indexOfChild(v);
                if (position != -1) {
                    if (mViewPager.getCurrentItem() != position) {
                        if (mSnapOnTabClick) {
                           // transition immediately
                           mViewPager.setCurrentItem(position, false);
                        } else {
                            //smoothly scroll to
                            mViewPager.setCurrentItem(position);
                        }

                        if (mListener != null) {
                            //自定义tab点击事件处理
                            mListener.onTabSelect(position);
                        }
                    } else {
                        if (mListener != null) {
                            //自定义Reselect事件处理
                            mListener.onTabReselect(position);
                        }
                    }
                }
            }
        });

        /** 每一个Tab的布局参数,mTabSpaceEqual 属性控制是否均分 */
        LinearLayout.LayoutParams lp_tab = mTabSpaceEqual ?
                new LinearLayout.LayoutParams(0,
                LayoutParams.MATCH_PARENT, 1.0f) :
                new
                LinearLayout.LayoutParams(
                LayoutParams.WRAP_CONTENT,
                LayoutParams.MATCH_PARENT);
        if (mTabWidth > 0) {
            lp_tab = new LinearLayout.LayoutParams((int) mTabWidth, LayoutParams.MATCH_PARENT);
        }
        //添加到Tab容器
        mTabsContainer.addView(tabView, position, lp_tab);
    }
    private void updateTabStyles() {
        //遍历设置标题选中颜色,未选中颜色,字体大小,大小写,粗体字
        for (int i = 0; i < mTabCount; i++) {
            View v = mTabsContainer.getChildAt(i);
            TextView tv_tab_title = (TextView) v.findViewById(R.id.tv_tab_title);
            if (tv_tab_title != null) {
                tv_tab_title.setTextColor(i == mCurrentTab ? mTextSelectColor : mTextUnselectColor);
                tv_tab_title.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextsize);
                tv_tab_title.setPadding((int) mTabPadding, 0, (int) mTabPadding, 0);
                if (mTextAllCaps) {
                    tv_tab_title.setText(tv_tab_title.getText().toString().toUpperCase());
                }

                if (mTextBold == TEXT_BOLD_BOTH) {
                    tv_tab_title.getPaint().setFakeBoldText(true);
                } else if (mTextBold == TEXT_BOLD_NONE) {
                    tv_tab_title.getPaint().setFakeBoldText(false);
                }
            }
        }
    }
方法 描述
setViewPager(ViewPager vp, String[] titles, FragmentActivity fa, ArrayList< Fragment > fragments) 设置ViewPager,标题内容,FragmentActivity和用于显示的Fragment,用来设置ViewPager的Adapter
    /** 关联ViewPager,用于连适配器都不想自己实例化的情况 */
    public void setViewPager(ViewPager vp, String[] titles, FragmentActivity fa, ArrayList<Fragment> fragments) {
        if (vp == null) {
            throw new IllegalStateException("ViewPager can not be NULL !");
        }

        if (titles == null || titles.length == 0) {
            throw new IllegalStateException("Titles can not be EMPTY !");
        }

        this.mViewPager = vp;
        /*通过传入的参数构建FragmentPagerAdapter,设置到ViewPager*/
        this.mViewPager.setAdapter(new InnerPagerAdapter(fa.getSupportFragmentManager(), fragments, titles));

        this.mViewPager.removeOnPageChangeListener(this);
        this.mViewPager.addOnPageChangeListener(this);
        notifyDataSetChanged();
    }

看下这个内部的InnerPagerAdapter,静态Fragment,不销毁重建,只更新数据内容。

    class InnerPagerAdapter extends FragmentPagerAdapter {
        private ArrayList<Fragment> fragments = new ArrayList<>();
        private String[] titles;

        public InnerPagerAdapter(FragmentManager fm, ArrayList<Fragment> fragments, String[] titles) {
            super(fm);
            this.fragments = fragments;
            this.titles = titles;
        }

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

        @Override
        public CharSequence getPageTitle(int position) {
            return titles[position];
        }

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

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            // 覆写destroyItem并且空实现,这样每个Fragment中的视图就不会被销毁
            // super.destroyItem(container, position, object);
        }

        @Override
        public int getItemPosition(Object object) {
            return PagerAdapter.POSITION_NONE;
        }
    }
方法 描述
onPageScrolled(int position, float positionOffset, int positionOffsetPixels) 页面滚动,position为当前位置,positionOffset范围[0,1),从当前到下一页,positionOffsetPixels从当前位置滚动的offset,单位px
onPageSelected(int position) 选中位置
onPageScrollStateChanged(int state) 滚动状态改变

SCROLL_STATE_IDLE(pager处于空闲状态)

SCROLL_STATE_DRAGGING( pager处于正在拖拽中)

SCROLL_STATE_SETTLING(pager正在自动沉降,相当于松手后,pager恢复到一个完整pager的过程)

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        /**
         * position:当前View的位置
         * mCurrentPositionOffset:当前View的偏移量比例.[0,1)
         */
        this.mCurrentTab = position;
        this.mCurrentPositionOffset = positionOffset;
        /*标签栏根据ViewPager的滚动状态联动,滚动到对应位置*/
        scrollToCurrentTab();
        /*触发重绘*/
        invalidate();
    }

    @Override
    public void onPageSelected(int position) {
        /*根据ViewPager选中状态调整标签页的选中状态*/
        updateTabSelection(position);
    }

    @Override
    public void onPageScrollStateChanged(int state) {
    }
  /** HorizontalScrollView滚到当前tab,并且居中显示 */
    private void scrollToCurrentTab() {
        if (mTabCount <= 0) {
            return;
        }

        int offset = (int) (mCurrentPositionOffset * mTabsContainer.getChildAt(mCurrentTab).getWidth());
        /**当前Tab的left+当前Tab的Width乘以positionOffset*/
        int newScrollX = mTabsContainer.getChildAt(mCurrentTab).getLeft() + offset;

        if (mCurrentTab > 0 || offset > 0) {
            /**HorizontalScrollView移动到当前tab,并居中*/
            newScrollX -= getWidth() / 2 - getPaddingLeft();
            calcIndicatorRect();
            newScrollX += ((mTabRect.right - mTabRect.left) / 2);
        }

        if (newScrollX != mLastScrollX) {
            mLastScrollX = newScrollX;
            /** scrollTo(int x,int y):x,y代表的不是坐标点,而是偏移量
             *  x:表示离起始位置的x水平方向的偏移量
             *  y:表示离起始位置的y垂直方向的偏移量
             */
            scrollTo(newScrollX, 0);
        }
    }
    /*根据是否选中设置字体颜色和粗体*/
    private void updateTabSelection(int position) {
        for (int i = 0; i < mTabCount; ++i) {
            View tabView = mTabsContainer.getChildAt(i);
            final boolean isSelect = i == position;
            TextView tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title);

            if (tab_title != null) {
                tab_title.setTextColor(isSelect ? mTextSelectColor : mTextUnselectColor);
                if (mTextBold == TEXT_BOLD_WHEN_SELECT) {
                    tab_title.getPaint().setFakeBoldText(isSelect);
                }
            }
        }
    }
1.2.3 Tab相关

FlycoTabLayout 从头到脚

FlycoTabLayout 从头到脚

FlycoTabLayout 从头到脚

方法 描述
addNewTab(String title) 提供给外部使用的新增tab的方法
    public void addNewTab(String title) {
        View tabView = View.inflate(mContext, R.layout.layout_tab, null);
        if (mTitles != null) {
            mTitles.add(title);
        }

        CharSequence pageTitle = mTitles == null ? mViewPager.getAdapter().getPageTitle(mTabCount) : mTitles.get(mTabCount);
        addTab(mTabCount, pageTitle.toString(), tabView);
        this.mTabCount = mTitles == null ? mViewPager.getAdapter().getCount() : mTitles.size();

        updateTabStyles();
    }
方法 描述
setCurrentTab(int currentTab) 跳转到指定tab,是否平滑的滚动过去由系统控制
setCurrentTab(int currentTab, boolean smoothScroll) 跳转到制指定tab, smoothScroll控制是否平滑的滚动过去
    public void setCurrentTab(int currentTab) {
        this.mCurrentTab = currentTab;
        mViewPager.setCurrentItem(currentTab);

    }

    public void setCurrentTab(int currentTab, boolean smoothScroll) {
        this.mCurrentTab = currentTab;
        mViewPager.setCurrentItem(currentTab, smoothScroll);
    }
1.2.4 Getter和Setter

不做赘述

1.2.5 MsgView相关(未读信息)

FlycoTabLayout 从头到脚

方法 描述
showMsg(int position, int num) 显示未读消息,position为tab位置,num小于等于0显示红点,num大于0显示数字
showDot(int position) 显示未读红点, position为tab位置
hideMsg(int position) 隐藏未读消息, position为tab位置
setMsgMargin(int position, float leftPadding, float bottomPadding) 设置未读消息偏移,原点为文字的右上角.当控件高度固定,消息提示位置易控制,显示效果佳
getMsgView(int position) 当前类只提供了少许设置未读消息属性的方法,可以通过该方法获取MsgView对象从而各种设置

2. CommonTabLayout

2.1 特有属性

特有属性 格式 描述
tl_indicator_width dimension 设置显示器固定宽度
tl_indicator_gravity enum 设置显示器上方(TOP)还是下方(BOTTOM),只对常规显示器有用
tl_indicator_style enum 设置显示器为常规(NORMAL)或三角形(TRIANGLE)或背景色块(BLOCK)
tl_indicator_anim_enable boolean 设置显示器支持动画(only for CommonTabLayout)
tl_indicator_anim_duration integer 设置显示器动画时间(only for CommonTabLayout)
tl_indicator_bounce_enable boolean 设置显示器支持动画回弹效果(only for CommonTabLayout)
tl_underline_color color 设置下划线颜色
tl_underline_height dimension 设置下划线高度
tl_underline_gravity enum 设置下划线上方(TOP)还是下方(BOTTOM)
tl_iconWidth dimension 设置icon宽度(仅支持CommonTabLayout)
tl_iconHeight dimension 设置icon高度(仅支持CommonTabLayout)
tl_iconVisible boolean 设置icon是否可见(仅支持CommonTabLayout)
tl_iconGravity enum 设置icon显示位置,对应Gravity中常量值,左上右下(仅支持CommonTabLayout),LEFT,RIGHT,TOP,BOTTOM
tl_iconMargin dimension 设置icon与文字间距(仅支持CommonTabLayout)

2.2 区别于SlidingTabLayout

  • 不依赖于ViewPager,可以与其他组件搭配
  • 支持自定义Tab样式,主要体现在常用的图标+文字的形式。
  • SlidingTabLayout继承HorizontalScrollView而CommonTabLayout继承FrameLayout

2.3 类结构

2.3.1 构造方法

FlycoTabLayout 从头到脚

与SlidingTabLayout实现类似,获取的属性值不太一样而已。多出一个动画的内容,点击某一个Tab后,indicator的移动动画效果

mValueAnimator = ValueAnimator.ofObject(new PointEvaluator(), mLastP, mCurrentP);
mValueAnimator.addUpdateListener(this);
2.3.2 动画相关
class IndicatorPoint {
    public float left;
    public float right;
}

private IndicatorPoint mCurrentP = new IndicatorPoint();
private IndicatorPoint mLastP = new IndicatorPoint();

class PointEvaluator implements TypeEvaluator<IndicatorPoint> {
    @Override
    public IndicatorPoint evaluate(float fraction, IndicatorPoint startValue, IndicatorPoint endValue) {
        float left = startValue.left + fraction * (endValue.left - startValue.left);
        float right = startValue.right + fraction * (endValue.right - startValue.right);
        IndicatorPoint point = new IndicatorPoint();
        point.left = left;
        point.right = right;
        return point;
    }
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
    View currentTabView = mTabsContainer.getChildAt(this.mCurrentTab);
    IndicatorPoint p = (IndicatorPoint) animation.getAnimatedValue();
    mIndicatorRect.left = (int) p.left;
    mIndicatorRect.right = (int) p.right;

    if (mIndicatorWidth < 0) {   //indicatorWidth小于0时,原jpardogo's PagerSlidingTabStrip

    } else {//indicatorWidth大于0时,圆角矩形以及三角形
        float indicatorLeft = p.left + (currentTabView.getWidth() - mIndicatorWidth) / 2;

        mIndicatorRect.left = (int) indicatorLeft;
        mIndicatorRect.right = (int) (mIndicatorRect.left + mIndicatorWidth);
    }
    invalidate();
}
2.3.3 Tab相关

FlycoTabLayout 从头到脚

方法 描述
setTabData(ArrayList< CustomTabEntity> tabEntitys) 设置tab entity
setTabData(ArrayList< CustomTabEntity> tabEntitys, FragmentActivity fa, int containerViewId, ArrayList< Fragment> fragments) 关联数据支持同时切换fragments
notifyDataSetChanged() 更新数据
setCurrentTab(int currentTab) 设置当前tab
public void setTabData(ArrayList<CustomTabEntity> tabEntitys) {
    if (tabEntitys == null || tabEntitys.size() == 0) {
        throw new IllegalStateException("TabEntitys can not be NULL or EMPTY !");
    }

    this.mTabEntitys.clear();
    /*设置tab标签*/
    this.mTabEntitys.addAll(tabEntitys);
    /*更新数据*/
    notifyDataSetChanged();
}
/** 更新数据 */
public void notifyDataSetChanged() {
    /*清空容器中的tab*/
    mTabsContainer.removeAllViews();
    this.mTabCount = mTabEntitys.size();
    View tabView;
    /*根据图标的gravity构建不同的tab样式,图标支持上下左右*/
    for (int i = 0; i < mTabCount; i++) {
        if (mIconGravity == Gravity.LEFT) {
            tabView = View.inflate(mContext, R.layout.layout_tab_left, null);
        } else if (mIconGravity == Gravity.RIGHT) {
            tabView = View.inflate(mContext, R.layout.layout_tab_right, null);
        } else if (mIconGravity == Gravity.BOTTOM) {
            tabView = View.inflate(mContext, R.layout.layout_tab_bottom, null);
        } else {
            tabView = View.inflate(mContext, R.layout.layout_tab_top, null);
        }
        /*i添加到tag,但从这个类的方法上看,这一步没有什么必要,因为addTab会传入i,addTab中直接使用i就好,但是如果我们在外部拿到tabView,就可以直接指导它的position,不用循环遍历,还是挺方便的*/
        tabView.setTag(i);
        /*添加tab*/
        addTab(i, tabView);
    }
    /*根据选中未选中状态更新tab显示效果*/
    updateTabStyles();
}
/** 创建并添加tab */
private void addTab(final int position, View tabView) {
    /*设置文本内容,title*/
    TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title);
    tv_tab_title.setText(mTabEntitys.get(position).getTabTitle());
    /*设置图标内容,添加未选中内容,后面根据选中未选中重新刷一次图片*/
    ImageView iv_tab_icon = (ImageView) tabView.findViewById(R.id.iv_tab_icon);
    iv_tab_icon.setImageResource(mTabEntitys.get(position).getTabUnselectedIcon());

    /*设置tabView的点击事件*/
    tabView.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            /*前面设置的tab,也就是position*/
            int position = (Integer) v.getTag();
            if (mCurrentTab != position) {
                /*设置当前tab*/
                setCurrentTab(position);
                //有OnTabSelectListener则执行对应的处理
                if (mListener != null) {
                    mListener.onTabSelect(position);
                }
            } else {
                if (mListener != null) {
                    mListener.onTabReselect(position);
                }
            }
        }
    });

    /** 每一个Tab的布局参数 ,根据是否均分设置不同的布局,若宽度不为0,则设置宽度*/
    LinearLayout.LayoutParams lp_tab = mTabSpaceEqual ?
            new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f) :
            new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
    if (mTabWidth > 0) {
        lp_tab = new LinearLayout.LayoutParams((int) mTabWidth, LayoutParams.MATCH_PARENT);
    }
    mTabsContainer.addView(tabView, position, lp_tab);
}
/*和SlidingTabLayout相似,多了一个图标的处理*/
private void updateTabStyles() {
    for (int i = 0; i < mTabCount; i++) {
        View tabView = mTabsContainer.getChildAt(i);
        tabView.setPadding((int) mTabPadding, 0, (int) mTabPadding, 0);
        TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title);
        tv_tab_title.setTextColor(i == mCurrentTab ? mTextSelectColor : mTextUnselectColor);
        tv_tab_title.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextsize);
//            tv_tab_title.setPadding((int) mTabPadding, 0, (int) mTabPadding, 0);
        if (mTextAllCaps) {
            tv_tab_title.setText(tv_tab_title.getText().toString().toUpperCase());
        }

        if (mTextBold == TEXT_BOLD_BOTH) {
            tv_tab_title.getPaint().setFakeBoldText(true);
        } else if (mTextBold == TEXT_BOLD_NONE) {
            tv_tab_title.getPaint().setFakeBoldText(false);
        }

        ImageView iv_tab_icon = (ImageView) tabView.findViewById(R.id.iv_tab_icon);
        if (mIconVisible) {
            iv_tab_icon.setVisibility(View.VISIBLE);
            CustomTabEntity tabEntity = mTabEntitys.get(i);
            iv_tab_icon.setImageResource(i == mCurrentTab ? tabEntity.getTabSelectedIcon() : tabEntity.getTabUnselectedIcon());
            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                    mIconWidth <= 0 ? LinearLayout.LayoutParams.WRAP_CONTENT : (int) mIconWidth,
                    mIconHeight <= 0 ? LinearLayout.LayoutParams.WRAP_CONTENT : (int) mIconHeight);
            if (mIconGravity == Gravity.LEFT) {
                lp.rightMargin = (int) mIconMargin;
            } else if (mIconGravity == Gravity.RIGHT) {
                lp.leftMargin = (int) mIconMargin;
            } else if (mIconGravity == Gravity.BOTTOM) {
                lp.topMargin = (int) mIconMargin;
            } else {
                lp.bottomMargin = (int) mIconMargin;
            }

            iv_tab_icon.setLayoutParams(lp);
        } else {
            iv_tab_icon.setVisibility(View.GONE);
        }
    }
}

另外一个setTabData

/** 关联数据支持同时切换fragments */
public void setTabData(ArrayList<CustomTabEntity> tabEntitys, FragmentActivity fa, int containerViewId, ArrayList<Fragment> fragments) {
    /*拿到一个mFragmentChangeManager ,后面setCurrentTab的时候 ,如果这个值不为空,根据这个来切换fragment,实现一种类似FragmentPagerAdapter的效果*/
    mFragmentChangeManager = new FragmentChangeManager(fa.getSupportFragmentManager(), containerViewId, fragments);
    setTabData(tabEntitys);
}
public void setCurrentTab(int currentTab) {
    mLastTab = this.mCurrentTab;
    this.mCurrentTab = currentTab;
    /*遍历更新tab选中未选中状态*/
    updateTabSelection(currentTab);
    /*如果mFragmentChangeManager 不为空,就根据当前选中的tab显示对应的Fragment*/
    if (mFragmentChangeManager != null) {
        mFragmentChangeManager.setFragments(currentTab);
    }
    /*indicator动画效果,计算后重绘*/
    if (mIndicatorAnimEnable) {
        calcOffset();
    } else {
        invalidate();
    }
}
2.3.4 FragmentChangeManager

FlycoTabLayout 从头到脚

public class FragmentChangeManager {
    private FragmentManager mFragmentManager;
    private int mContainerViewId;
    /** Fragment切换数组 */
    private ArrayList<Fragment> mFragments;
    /** 当前选中的Tab */
    private int mCurrentTab;

    /*构造方法,setTabData的时候看到过*/
    public FragmentChangeManager(FragmentManager fm, int containerViewId, ArrayList<Fragment> fragments) {
        this.mFragmentManager = fm;
        this.mContainerViewId = containerViewId;
        this.mFragments = fragments;
        initFragments();
    }

    /** 初始化fragments */
    private void initFragments() {
        for (Fragment fragment : mFragments) {
            mFragmentManager.beginTransaction().add(mContainerViewId, fragment).hide(fragment).commit();
        }

        setFragments(0);
    }

    /** 界面切换控制,CommonTabLayout中的setCurrentTab方法可以控制*/
    public void setFragments(int index) {
        for (int i = 0; i < mFragments.size(); i++) {
            FragmentTransaction ft = mFragmentManager.beginTransaction();
            Fragment fragment = mFragments.get(i);
            if (i == index) {
                ft.show(fragment);
            } else {
                ft.hide(fragment);
            }
            ft.commit();
        }
        mCurrentTab = index;
    }

    public int getCurrentTab() {
        return mCurrentTab;
    }

    public Fragment getCurrentFragment() {
        return mFragments.get(mCurrentTab);
    }
}
2.3.5 Getter,Setter以及MsgView先关

Getter和Setter方法是属性值的获取和设置,MsgView相关方法和SlidingTabLayout比较相似。


3. SegmentTabLayout

3.1 特有属性

特有属性 格式 描述
tl_indicator_anim_enable boolean 设置显示器支持动画
tl_indicator_anim_duration integer 设置显示器动画时间
tl_indicator_bounce_enable boolean 设置显示器支持动画回弹效果
tl_bar_color color 设置整体颜色
tl_bar_stroke_color color 设置边框颜色
tl_bar_stroke_width dimension 设置边框粗细

3.2 区别于CommonTabLayout

  • 不支持图标,但是可以看做是一个特殊的CommonTabLayout.

3.3 类结构

整体来说,内容基本上和CommonTabLayout,只是少了Icon的对应处理,多出的是Segment样式的处理。


4. MsgView

4.1 自定义属性

属性值 格式 描述
mv_backgroundColor color 圆角矩形背景色
mv_cornerRadius dimension 圆角弧度,单位dp
mv_strokeWidth dimension 边框粗细,单位dp
mv_strokeColor color 圆角边框颜色
mv_isRadiusHalfHeight boolean 圆角弧度是高度一半
mv_isWidthHeightEqual boolean 圆角矩形宽高相等,取较宽高中大值

4.2 类结构

FlycoTabLayout 从头到脚


项目使用

个人项目Gank.io Android 客户端中使用效果,底部使用的CommonTabLayout,顶部使用的是SlidingTabLayout。整体而言,日常开发过程中,FlycoTabLayout还是很实用的。

FlycoTabLayout 从头到脚


FlycoTabLayout 从头到脚


最后

个人微信公众号:Learning_Of_ALL,欢迎大家扫码关注,Android技术交流。

FlycoTabLayout 从头到脚