自定义View指示器让指示器下划线和文字对齐

指示器的标签分为两种情形,一种是固定的一般3~5个标签,另一种的移动的标签;开发中经常会使用MD风格的TabLayout控件,这个控件什么都很好,就是指示器的下滑线和文字不能够对齐,这一点用过的同学都会知道的,我也在网上找了好久,也找到不到现成的,只好自己动手来写一个啦!
先声明一下,我是吧指示器标签分两种情形来写的,固定和不固定,没有合并,因为不是继承同一个控件;另外要多看看TabLayout的源代码,对于自定义这个控件很有好处,可以多多借鉴里面的实现;
最下面可以下载代码,有问题可以留言交流;
说了这么多先看看固定的标签的效果:(Tab是固定的效果)

这里写图片描述

这种效果实现的主要步骤思路:
因为这种标签固定
1.首先要把所有的标签文字全部放到一个容器里面,宽度按个数均分,容器选水平LinearLayout
2.要关联上一个ViewPager,并根据ViewPager的改变动态绘制指示器;
添加的代码如下:

      /**
     * 把数据集合设置成Tab标签
     */
    public void setTabTitle(List<String> list) {
        if (list == null)
            return;
        mTabTitleList = list;//tab数据源
        mTabCount = list.size();//tab的个数
        for (int i = 0; i < list.size(); i++) {
            String text = list.get(i);
            addView(generateText(text));//添加到容器中
        }
        setTabOnClickListener();//设置点击事件
    }

    /**
     * 自动生成TextView
     */
    public TextView generateText(String text) {
        TextView textView = new TextView(getContext());
        textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mTextSize);
        textView.setTextColor(mNormalColor);
        textView.setGravity(Gravity.CENTER);
        textView.setText(text);
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0,
                LinearLayout.LayoutParams.MATCH_PARENT, 1);
        textView.setLayoutParams(lp);
        return textView;
    }

    /**
     * 每一个tab的点击事件
     */
    public void setTabOnClickListener() {
        if (mTabTitleList != null) {
            for (int i = 0; i < mTabCount; i++) {
                TextView textView = (TextView) getChildAt(i);
                textView.setOnClickListener(new OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        int index = indexOfChild(v);
                        if (mViewPager != null)
                            mViewPager.setCurrentItem(index, true);
                    }
                });
            }
        }
    }

上面代码很简单就是把根据文本生成了TextView添加到LinearLayout里面,并且均分;这一步设置好了以后,下面就开始关联ViewPager;

    private RectF rectF = new RectF();//要绘制的矩形

    //我们是根据下面两个参数来动态计算并绘制
    private int position;//ViewPager的里面的position
    private float ration;//滑动的百分比

    /**
     * 关联到ViewPager
     */
    public void setViewPager(ViewPager viewPager) {
        this.mViewPager = viewPager;
        this.mViewPager.addOnPageChangeListener(new OnPageChangeListener() {

            @Override
            public void onPageSelected(int pos) {
            }

            @Override
            public void onPageScrolled(int arg0, float arg1, int arg2) {
                position = arg0;
                ration = arg1;
                //这是为了找出指示器大概会滑动到那个Tab位置
                int pos = Math.round(arg0 + arg1);
                for (int i = 0; i < mTabCount; i++) {
                    TextView textView = (TextView) getChildAt(i);
                    if (i == pos) {
                        textView.setTextColor(mSelectedColor);
                    } else {
                        textView.setTextColor(mNormalColor);
                    }
                }
                //重绘
                postInvalidate();
            }

            @Override
            public void onPageScrollStateChanged(int state) {
            }
        });

    }

这里我们主要在onPageScrolled这个方法里面去做一下更新的操作;其中
int pos = Math.round(arg0 + arg1);这一行代码是为了让ViewPager切换到一半的是时候会显示另一个tab被选中,这一点我也是从源代码里面看到的,这里借鉴了一下;最后重新绘制;

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        if (mTabTitleList != null && mTabCount > 0) {
            String text = mTabTitleList.get(position);//拿到文本
            int textWidth = (int) mPaint.measureText(text);//测量一下宽度
            int perWidth = mViewWidth / mTabCount;//每个Tab的平均宽度

            rectF.set(
                    perWidth * (position + ration) + perWidth / 2 - textWidth / 2,
                    mViewHeight - mPagerIndicatorHeight,
                    perWidth * (position + ration) + perWidth / 2 + textWidth / 2,
                    mViewHeight);
            canvas.drawRect(rectF, mPaint);
        }
    }

这里重写一下dispatchDraw方法,在super.dispatchDraw方法里面是绘制子View,等它们都绘制完毕之后,在进行指示器下面的绘制;这一步不要在onDraw方法里面绘制,因为LinearLayout本身没有什么需要绘制的,只是孩子View需要绘制;即使你绘制了也会被dispatchDraw方法绘制的内容覆盖;

固定标签的指示器到这里就就结束了,还有其他一些初始化的工作这里不说了;下面我们在来看看不固定的情形,也就是标签的个数比较多的情形;

这里写图片描述

上面的情形在写的过程中,大部分代码都是参照TabLayout的,先说一下思路吧:
因为这种标签可以移动
1.首先要把所有的标签文字全部放到一个容器里面,宽度按个数均分,容器选HorizontalScrollView,里面只能有一个View我选择LinearLayout
2.要关联上一个ViewPager,并根据ViewPager的改变动态绘制指示器;
先初始化了代码:

    public ViewPagerIndicator_Scroll(Context context, AttributeSet attrs) {
        super(context, attrs);
        setHorizontalScrollBarEnabled(false);// Disable the Scroll Bar
        initPaint();// 初始化画笔
        /**
         * HorizontalScrollView只能包含一个子View
         */
        mLinearLayout = new LinearLayout(context);
        mLinearLayout.setOrientation(LinearLayout.HORIZONTAL);// 水平方向
        mLinearLayout.setLayoutParams(new HorizontalScrollView.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
        addView(mLinearLayout);//添加
    }

这里要注意是让setHorizontalScrollBarEnabled让滚动条不可用,而且要添加一个孩子容器,
我们把这些标签全部放到这个孩子容器里面;
生成文本标签的代码:

    public TextView generateText(String text) {
        TextView textView = new TextView(getContext());
        textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mTextSize);
        textView.setTextColor(mNormalColor);
        textView.setGravity(Gravity.CENTER);
        textView.setText(text);
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                (int) mPaint.measureText(text), //宽度最直接使用画笔测量出来的宽度
                LinearLayout.LayoutParams.MATCH_PARENT);
        lp.setMargins(dp2px(5), 0, dp2px(5), 0);//设置了左右间距,可以自己调整
        textView.setLayoutParams(lp);
        return textView;
    }

宽度我没有使用Wrap_content那样会有挤压,这里直接使用画笔测量文本的宽度;
我们将生成的表面添加容器里面之后,我们最后就是和ViewPager的联动了

  public void setViewPager(ViewPager viewPager) {
        this.mViewPager = viewPager;
        this.mViewPager.addOnPageChangeListener(new OnPageChangeListener() {

            @Override
            public void onPageSelected(int pos) {

            }

            @Override
            public void onPageScrollStateChanged(int state) {
            }

            @Override
            public void onPageScrolled(int position, float positionOffset,
                                       int positionOffsetPixels) {
                int pos = Math.round(position + positionOffset);
                for (int i = 0; i < mTabCount; i++) {
                    TextView textView = (TextView) mLinearLayout.getChildAt(i);
                    if (i == pos) {
                        textView.setTextColor(mSelectedColor);
                    } else {
                        textView.setTextColor(mNormalColor);
                    }
                }
                setIndicatorPositionFromTabPosition(position, positionOffset);
                scrollTo(calculateScrollXForTab(position, positionOffset), 0);
            }
        });

    }

在onPageScrolled方法中思路内部是这样的:
1,设置一下目前将要滑动到那个标签,我们就并做高亮显示;
2,根据ViewPager位置信息改变绘制指示器的下划线;
3,移动当前View的内容,为了更好的展示
这三个步骤,我全部 都是参照的TabLayout的源代码;我是搞了半天怎么也行;最后才想着看看人家的代码实现;这也算是我的经验吧;直接使用了代码中的算法后,出来效果就是内容滑动的和下划线会对齐;就是这么神奇!!!贴一下代码吧:

   public void setIndicatorPositionFromTabPosition(int position, float positionOffset) {
        mSelectedPosition = position;
        mSelectionOffset = positionOffset;
        updateIndicatorPosition();
    }

    private void updateIndicatorPosition() {
        View selectedTitle = mLinearLayout.getChildAt(mSelectedPosition);
        int left, right;

        if (selectedTitle != null && selectedTitle.getWidth() > 0) {
            left = selectedTitle.getLeft();
            right = selectedTitle.getRight();

            if (mSelectionOffset > 0f && mSelectedPosition < mLinearLayout.getChildCount() - 1) {
                // Draw the selection partway between the tabs
                View nextTitle = mLinearLayout.getChildAt(mSelectedPosition + 1);
                left = (int) (mSelectionOffset * nextTitle.getLeft() +
                        (1.0f - mSelectionOffset) * left);
                right = (int) (mSelectionOffset * nextTitle.getRight() +
                        (1.0f - mSelectionOffset) * right);
            }
        } else {
            left = right = -1;
        }
        setIndicatorPosition(left, right);
    }


    void setIndicatorPosition(int left, int right) {
        if (left != mIndicatorLeft || right != mIndicatorRight) {
            mIndicatorLeft = left;
            mIndicatorRight = right;
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    private int calculateScrollXForTab(int position, float positionOffset) {

        View selectedChild = mLinearLayout.getChildAt(position);
        View nextChild = position + 1 < getChildCount()
                ? getChildAt(position + 1)
                : null;
        int selectedWidth = selectedChild != null ? selectedChild.getWidth() : 0;
        int nextWidth = nextChild != null ? nextChild.getWidth() : 0;

        // base scroll amount: places center of tab in center of parent
        int scrollBase = selectedChild.getLeft() + (selectedWidth / 2) - (getWidth() / 2);
        // offset amount: fraction of the distance between centers of tabs
        int scrollOffset = (int) ((selectedWidth + nextWidth) * 0.5f * positionOffset);

        return (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_LTR)
                ? scrollBase + scrollOffset
                : scrollBase - scrollOffset;

    }

自定义ViewPager的指示器标签—让指示器下划线和文字对齐

猜你喜欢

转载自blog.csdn.net/eyishion/article/details/78170861