ViewPager导航控件——SimpleViewpagerIndicator

写这个小控件是因为最近负责维护的一款app大改版,设计师给了一个新的ViewPager导航样式,但找了几个常用的导航控件发现都无法100%实现设计师给的效果,于是就干脆自己动手丰衣足食了。

控件只有一个单独的java类,代码也很简单,放出来希望能帮到需要的人。

效果

控件提供了比较丰富的可配置选项,下面是两个例子:

1.所有配置项均使用默认值(tab宽度包裹内容、indicator与文字等宽…):

ViewPager导航控件——SimpleViewpagerIndicator_第1张图片

2.tab宽度平分父控件剩余空间、indicator与tab等宽…:

ViewPager导航控件——SimpleViewpagerIndicator_第2张图片

配置项

在调用setViewPager前,使用一系列setXXX方法进行设置即可,支持链式调用:

indicator.setExpand(true)//设置tab宽度为包裹内容还是平分父控件剩余空间,默认值:false,包裹内容
    .setIndicatorWrapText(false)//设置indicator是与文字等宽还是与整个tab等宽,默认值:true,与文字等宽
    .setIndicatorColor(Color.parseColor("#ff3300"))//indicator颜色
    .setIndicatorHeight(2)//indicator高度
    .setShowUnderline(true, Color.parseColor("#dddddd"), 2)//设置是否展示underline,默认不展示
    .setShowDivider(true, Color.parseColor("#dddddd"), 10, 1)//设置是否展示分隔线,默认不展示
    .setTabTextSize(16)//文字大小
    .setTabTextColor(Color.parseColor("#ff999999"))//文字颜色
    .setTabTypeface(null)//字体
    .setTabTypefaceStyle(Typeface.NORMAL)//字体样式:粗体、斜体等
    .setTabBackgroundResId(0)//设置tab的背景
    .setTabPadding(0)//设置tab的左右padding
    .setSelectedTabTextSize(20)//被选中的文字大小
    .setSelectedTabTextColor(Color.parseColor("#ff3300"))//被选中的文字颜色
    .setSelectedTabTypeface(null)
    .setSelectedTabTypefaceStyle(Typeface.BOLD)
    .setScrollOffset(120);//滚动偏移量

indicator.setViewPager(viewPager);

所有的配置项均有默认值,也就是说不进行任何设置也是可以的,效果参考上面的第一张图。

代码

源码不多,注释也比较详细,所以就不多废话了:

/**
 * 使用方式:
 * --在调用setViewPager之前使用setxxx方法进行样式设置,支持链式调用
 * --调用setViewPager方法将viewPager绑定到simpleViewpagerIndicator(viewPager的adapter必须实现getPageTitle方法)
 * --调用setOnPageChangeListener来设置viewPager的页面切换监听
 */
public class SimpleViewpagerIndicator extends HorizontalScrollView {

//配置属性 START-----------------------------------------------------------------------------

    /*
     * true:每个tab宽度为平分父控件剩余空间
     * false:每个tab宽度为包裹内容
     */
    private boolean expand = false;

    /*
     * 指示器(被选中的tab下的短横线)
     */
    private boolean indicatorWrapText = true;//true:indicator与文字等长;false:indicator与整个tab等长
    private int indicatorColor = Color.parseColor("#ff666666");
    private int indicatorHeight = 2;//dp

    /*
     * 底线(指示器的背景滑轨)
     */
    private boolean showUnderline = false;//是否展示底线
    private int underlineColor;
    private int underlineHeight;//dp

    /*
     * tab之间的分割线
     */
    private boolean showDivider = false;//是否展示分隔线
    private int dividerColor;
    private int dividerPadding;//分隔线上下的padding,dp
    private int dividerWidth;//分隔线宽度,dp

    /*
     * tab
     */
    private int tabTextSize = 16;//tab字号,dp
    private int tabTextColor = Color.parseColor("#ff999999");//tab字色
    private Typeface tabTypeface = null;//tab字体
    private int tabTypefaceStyle = Typeface.NORMAL;//tab字体样式
    private int tabBackgroundResId = 0;//每个tab的背景资源id
    private int tabPadding = 24;//每个tab的左右内边距,dp

    /*
     * 被选中的tab
     */
    private int selectedTabTextSize = 16;//dp
    private int selectedTabTextColor = Color.parseColor("#ff666666");
    private Typeface selectedTabTypeface = null;
    private int selectedTabTypefaceStyle = Typeface.BOLD;

    /*
     * scrollView整体滚动的偏移量,dp
     */
    private int scrollOffset = 100;

//配置属性 End-------------------------------------------------------------------------------

    private LinearLayout.LayoutParams wrapTabLayoutParams;
    private LinearLayout.LayoutParams expandTabLayoutParams;

    private Paint rectPaint;
    private Paint dividerPaint;
    private Paint measureTextPaint;//测量文字宽度用的画笔

    private final PageListener pageListener = new PageListener();
    private OnPageChangeListener userPageListener;

    private LinearLayout tabsContainer;//tab的容器
    private ViewPager viewPager;

    private int currentPosition = 0;//viewPager当前页面
    private float currentPositionOffset = 0f;//viewPager当前页面的偏移百分比(取值:0~1)
    private int selectedPosition = 0;//viewPager当前被选中的页面

    private int tabCount;
    private int lastScrollX = 0;

    public SimpleViewpagerIndicator(Context context) {
        this(context, null);
    }

    public SimpleViewpagerIndicator(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SimpleViewpagerIndicator(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setFillViewport(true);
        setWillNotDraw(false);
    }

    public SimpleViewpagerIndicator setViewPager(ViewPager viewPager) {
        this.viewPager = viewPager;
        if (viewPager.getAdapter() == null) {
            throw new IllegalStateException("ViewPager does not have adapter instance.");
        }

        viewPager.setOnPageChangeListener(pageListener);

        init();
        initView();

        return this;
    }

    public SimpleViewpagerIndicator setOnPageChangeListener(OnPageChangeListener listener) {
        this.userPageListener = listener;
        return this;
    }

    private void init() {
        /*
         * 将dp换算为px
         */
        float density = getContext().getResources().getDisplayMetrics().density;
        indicatorHeight = (int) (indicatorHeight * density);
        underlineHeight = (int) (underlineHeight * density);
        dividerPadding = (int) (dividerPadding * density);
        dividerWidth = (int) (dividerWidth * density);
        tabTextSize = (int) (tabTextSize * density);
        tabPadding = (int) (tabPadding * density);
        selectedTabTextSize = (int) (selectedTabTextSize * density);
        scrollOffset = (int) (scrollOffset * density);

        /*
         * 创建tab的容器(LinearLayout)
         */
        tabsContainer = new LinearLayout(getContext());
        tabsContainer.setOrientation(LinearLayout.HORIZONTAL);
        tabsContainer.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        addView(tabsContainer);

        /*
         * 创建画笔
         */
        rectPaint = new Paint();
        rectPaint.setAntiAlias(true);
        rectPaint.setStyle(Style.FILL);

        dividerPaint = new Paint();
        dividerPaint.setAntiAlias(true);
        dividerPaint.setStrokeWidth(dividerWidth);

        measureTextPaint = new Paint();
        measureTextPaint.setTextSize(selectedTabTextSize);

        /*
         * 创建两个Tab的LayoutParams,一个为宽度包裹内容,一个为宽度等分父控件剩余空间
         */
        wrapTabLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);//宽度包裹内容
        expandTabLayoutParams = new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f);//宽度等分
    }

    private void initView() {
        //注意:currentPosition和selectedPosition的含义并不相同,它们分别在onPageScroll和onPageSelected中被赋值
        //在从tab1往tab2滑动的过程中,selectedPosition会比currentPosition先由1变成2
        currentPosition = viewPager.getCurrentItem();
        selectedPosition = viewPager.getCurrentItem();

        tabsContainer.removeAllViews();
        tabCount = viewPager.getAdapter().getCount();

        //创建tab并添加到tabsContainer中
        for (int i = 0; i < tabCount; i++) {
            addTab(i, viewPager.getAdapter().getPageTitle(i).toString());
        }

        //遍历tab,设置tab文字大小和样式
        updateTextStyle();

        //滚动scrollView
        getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                    getViewTreeObserver().removeGlobalOnLayoutListener(this);
                } else {
                    getViewTreeObserver().removeOnGlobalLayoutListener(this);
                }

                scrollToChild(currentPosition, 0);//滚动scrollView
            }
        });
    }

    /**
     * 添加tab
     */
    private void addTab(final int position, String title) {
        TextView tab = new TextView(getContext());

        tab.setGravity(Gravity.CENTER);
        tab.setSingleLine();
        tab.setText(title);
        if (tabBackgroundResId != 0) {
            tab.setBackgroundResource(tabBackgroundResId);
        }
        tab.setPadding(tabPadding, 0, tabPadding, 0);
        tab.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                viewPager.setCurrentItem(position);
            }
        });

        tabsContainer.addView(tab, position, expand ? expandTabLayoutParams : wrapTabLayoutParams);
    }

    /**
     * 遍历tab,设置tab文字大小和样式
     */
    private void updateTextStyle() {
        for (int i = 0; i < tabCount; i++) {
            TextView tvTab = (TextView) tabsContainer.getChildAt(i);

            if (i == selectedPosition) {//被选中的tab
                tvTab.setTextSize(TypedValue.COMPLEX_UNIT_PX, selectedTabTextSize);
                tvTab.setTypeface(selectedTabTypeface, selectedTabTypefaceStyle);
                tvTab.setTextColor(selectedTabTextColor);
            } else {//未被选中的tab
                tvTab.setTextSize(TypedValue.COMPLEX_UNIT_PX, tabTextSize);
                tvTab.setTypeface(tabTypeface, tabTypefaceStyle);
                tvTab.setTextColor(tabTextColor);
            }
        }
    }

    /**
     * 滚动scrollView
     * 

* 注意:当普通文字字号(tabTextSize)与被选中的文字字号(selectedTabTextSize)相差过大,且tab的宽度模式为包裹内容(expand = false)时, * 由于文字选中状态切换时文字宽度突变,造成tab宽度突变,可能导致scrollView在滚动时出现轻微抖动。 * 因此,当普通文字字号(tabTextSize)与被选中的文字字号(selectedTabTextSize)相差过大时,应避免使tab宽度包裹内容(expand = false)。 */ private void scrollToChild(int position, int offset) { if (tabCount == 0) return; //getLeft():tab相对于父控件,即tabsContainer的left int newScrollX = tabsContainer.getChildAt(position).getLeft() + offset; //附加一个偏移量,防止当前选中的tab太偏左 //可以去掉看看是什么效果 if (position > 0 || offset > 0) { newScrollX -= scrollOffset; } if (newScrollX != lastScrollX) { lastScrollX = newScrollX; scrollTo(newScrollX, 0); } } /** * 绘制indicator、underline和divider */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (isInEditMode() || tabCount == 0) return; final int height = getHeight(); /* * 绘制divider */ if (showDivider) { dividerPaint.setColor(dividerColor); for (int i = 0; i < tabCount - 1; i++) { View tab = tabsContainer.getChildAt(i); canvas.drawLine(tab.getRight(), dividerPadding, tab.getRight(), height - dividerPadding, dividerPaint); } } /* * 绘制underline(indicator的背景线) */ if (showUnderline) { rectPaint.setColor(underlineColor); canvas.drawRect(0, height - underlineHeight, tabsContainer.getWidth(), height, rectPaint); } /* * 绘制indicator */ if (indicatorWrapText) {//indicator与文字等长 rectPaint.setColor(indicatorColor); getTextLocation(currentPosition); float lineLeft = textLocation.left; float lineRight = textLocation.right; if (currentPositionOffset > 0f && currentPosition < tabCount - 1) { getTextLocation(currentPosition + 1); final float nextLeft = textLocation.left; final float nextRight = textLocation.right; lineLeft = lineLeft + (nextLeft - lineLeft) * currentPositionOffset; lineRight = lineRight + (nextRight - lineRight) * currentPositionOffset; } canvas.drawRect(lineLeft, height - indicatorHeight, lineRight, height, rectPaint); } else {//indicator与tab等长 rectPaint.setColor(indicatorColor); View currentTab = tabsContainer.getChildAt(currentPosition); float lineLeft = currentTab.getLeft(); float lineRight = currentTab.getRight(); if (currentPositionOffset > 0f && currentPosition < tabCount - 1) { View nextTab = tabsContainer.getChildAt(currentPosition + 1); final float nextLeft = nextTab.getLeft(); final float nextRight = nextTab.getRight(); lineLeft = lineLeft + (nextLeft - lineLeft) * currentPositionOffset; lineRight = lineRight + (nextRight - lineRight) * currentPositionOffset; } canvas.drawRect(lineLeft, height - indicatorHeight, lineRight, height, rectPaint); } } /** * 获得指定tab中,文字的left和right */ private void getTextLocation(int position) { View tab = tabsContainer.getChildAt(position); String tabText = viewPager.getAdapter().getPageTitle(position).toString(); float textWidth = measureTextPaint.measureText(tabText); int tabWidth = tab.getWidth(); textLocation.left = tab.getLeft() + (int) ((tabWidth - textWidth) / 2); textLocation.right = tab.getRight() - (int) ((tabWidth - textWidth) / 2); } private LeftRight textLocation = new LeftRight(); class LeftRight { int left, right; } private class PageListener implements OnPageChangeListener { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { currentPosition = position; currentPositionOffset = positionOffset; //scrollView滚动 scrollToChild(position, (int) (positionOffset * tabsContainer.getChildAt(position).getWidth())); invalidate();//invalidate后onDraw会被调用,绘制indicator、divider等 if (userPageListener != null) { userPageListener.onPageScrolled(position, positionOffset, positionOffsetPixels); } } @Override public void onPageScrollStateChanged(int state) { if (state == ViewPager.SCROLL_STATE_IDLE) { scrollToChild(viewPager.getCurrentItem(), 0);//scrollView滚动 } if (userPageListener != null) { userPageListener.onPageScrollStateChanged(state); } } @Override public void onPageSelected(int position) { selectedPosition = position; updateTextStyle();//更新tab文字大小和样式 if (userPageListener != null) { userPageListener.onPageSelected(position); } } } //setter------------------------------------------------------------------------------------ ...... }

完整代码和demo见:https://github.com/al4fun/SimpleViewpagerIndicator

你可能感兴趣的:(Android)