Some time ago, I saw the tablayout of Toutiao, which is quite fresh and interesting. The effect is the following one

This effect should be implemented with a custom View. You can see that during the sliding process, two adjacent tabs have partial color changes, the former TAB partially back to black, the latter TAB partially red, depending on the distance of the sliding.

First of all, each TAB should be a custom View, because it involves local text discoloration, so we should first customize a local text discoloration View, ordinary View of course does not support ~

The main method used is canvas clipRect method. Let’s see what this method means.

/**
     * Intersect the current clip with the specified rectangle, which is
     * expressed in localcoordinates. * * @param left The left side of the rectangle to intersect with the * current clip * @param top The top of  the rectangle to intersect with the current clip * @param right The right side of the rectangle to intersect with the *  current clip * @param bottom The bottom of the rectangle to intersect with the current * clip * @return       true if the resulting clip is non-empty
     */
    public boolean clipRect(int left, int top, int right, int bottom) {
        return nClipRect(mNativeCanvasWrapper, left, top, right, bottom,
                Region.Op.INTERSECT.nativeInt);
    }
Copy the code

It is easy to understand the position of the upper left and lower right of the clipping range of the four parameters. It should be noted that the drawing range needs to be restored in time after using this method, so the complete code is as follows

canvas.save(); canvas.clipRect(left, top, right, bottom); DrawText () canvas.restore(); drawText() canvas.restore();Copy the code

So with that in mind, let’s figure out how to draw text in two colors. Last one, okay

Without further ado, get right to the code

public class ColorClipView extends View { private Paint paint; // Brush private String text ="No, I'm not."; Private int textSize = sp2px(18); Private int textWidth; Private int textHeight; Private int textUnselectColor = r.color.colorPrimary; private int textUnselectColor = r.color.colorPrimary; Private int textSelectedColor = r.color.coloraccent; private int textSelectedColor = r.color.coloraccent; Private static final int DIRECTION_LEFT = 0; private static final int DIRECTION_RIGHT = 1; private static final int DIRECTION_TOP = 2; private static final int DIRECTION_BOTTOM = 3; private int mDirection = DIRECTION_LEFT; private Rect textRect = new Rect(); // Text display area private int startX; Private int startY; private int startY; Private int baseLineY; private int baseLineY; // The location of the baseline is privatefloatprogress; public ColorClipView(Context context) { this(context, null); } public ColorClipView(Context context, AttributeSet attrs) { super(context, attrs); // Initialize various attributes including paint = new paint (paint.anti_alias_flag); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ColorClipView); text = ta.getString(R.styleable.ColorClipView_text); textSize = ta.getDimensionPixelSize(R.styleable.ColorClipView_text_size, textSize); // textUnselectColor = ta.getColor(R.styleable.ColorClipView_text_unselected_color, textUnselectColor); // textSelectedColor = ta.getColor(R.styleable.ColorClipView_text_selected_color, textSelectedColor); mDirection = ta.getInt(R.styleable.ColorClipView_direction, mDirection); progress = ta.getFloat(R.styleable.ColorClipView_progress, 0); ta.recycle(); // Use it up! paint.setTextSize(textSize); } private int sp2px(float dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                dpVal, getResources().getDisplayMetrics());
    }

    public void setProgress(float progress) {
        this.progress = progress;
        invalidate();
    }

    public void setTextSize(int mTextSize) {
        this.textHeight = mTextSize;
        paint.setTextSize(mTextSize);
        requestLayout();
        invalidate();
    }

    public void setText(String text) {
        this.text = text;
        requestLayout();
        invalidate();
    }

    public void setDirection(int direction) {
        this.mDirection = direction;
        invalidate();
    }

    public void setTextUnselectColor(int unselectColor) {
        this.textUnselectColor = unselectColor;
        invalidate();
    }

    public void setTextSelectedColor(int selectedColor) { this.textSelectedColor = selectedColor; invalidate(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { measureText(); Int width = measureWidth(widthMeasureSpec); Int height = measureHeight(heightMeasureSpec); // The actual height can be measured by the different modessetMeasuredDimension(width, height);
        startX = (getMeasuredWidth() - getPaddingRight() - getPaddingLeft()) / 2 - textWidth / 2;
        startY = (textHeight - getPaddingBottom() - getPaddingTop());
    }

    private int measureHeight(int heightMeasureSpec) {
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        int size = MeasureSpec.getSize(heightMeasureSpec);
        int realSize = 0;
        switch (mode) {
            case MeasureSpec.EXACTLY:
                realSize = size;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                realSize = textHeight;
                realSize += getPaddingTop() + getPaddingBottom();
                break;
        }
        realSize = mode == MeasureSpec.AT_MOST ? Math.min(realSize, size) : realSize;
        returnrealSize; } private int measureWidth(int widthMeasureSpec) { int mode = MeasureSpec.getMode(widthMeasureSpec); WidthMeasureSpec Mode int size = MeasureSpec. GetSize (widthMeasureSpec); Int realSize = 0; // Switch (mode) {caseMeasureSpec.EXACTLY:// EXACTLY = size;break;
            caseMeasureSpec.AT_MOST:// Maximum modecaseMeasureSpec. UNSPECIFIED: / / / / not specified mode in both cases, use measure the width of plus padding around realSize = textWidth; realSize = realSize + getPaddingLeft() + getPaddingRight();break; } realSize = mode == MeasureSpec.AT_MOST? Math.min(realSize, size) : realSize;return realSize;
    }

    private void measureText() { textWidth = (int) paint.measureText(text); // Measure text width log.d ("tag"."measureText="+ paint.measureText(text)); // Start and end are the start and end positions of the text to be measured. TextRect is the object storing the text display range. The method will write the result into textRect after the measurement is completed. paint.getTextBounds(text, 0, text.length(), textRect); textHeight = textRect.height(); // The height of text is measured by the distance between the descent line and the top line. Paint.fontmetrics FM = paint.getfontmetrics (); textHeight = (int) Math.ceil(fm.descent - fm.top); baseLineY = (int) (textHeight / 2 - (fm.bottom - fm.top) / 2 - fm.top); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Select * from left to right; // Select * from left to right Is it up or down? Really looking forward to... Log.e("tag"."OnDraw");
        if(mDirection == DIRECTION_LEFT) {// Draw left-sided selected text drawHorizontalText(canvas, textSelectedColor, startX, (int) (startX + progress * textWidth)); DrawHorizontalText (canvas, textUnselectColor, (int) (startX + Progress * textWidth), startX + textWidth); }else if (mDirection == DIRECTION_RIGHT) {
            //绘制朝右的选中文字
            drawHorizontalText(canvas, textSelectedColor,
                    (int) (startX + (1 - progress) * textWidth), startX
                            + textWidth);
            //绘制朝右的未选中文字
            drawHorizontalText(canvas, textUnselectColor, startX,
                    (int) (startX + (1 - progress) * textWidth));
        } else if(mDirection == DIRECTION_TOP) {drawVerticalText(Canvas, textSelectedColor, startY, drawVerticalText(canvas, textSelectedColor, startY, (int) (startY + progress * textHeight)); DrawVerticalText (canvas, textUnselectColor, (int) (startY + progress * textHeight), startY + textHeight); }elseDrawVerticalText (canvas, textSelectedColor, (int) (startY + (1 - progress) * textHeight), drawVerticalText(canvas, textSelectedColor, (int) (startY + (1 - progress) * textHeight) startY + textHeight); DrawVerticalText (canvas, textUnselectColor, startY, (int) (startY + (1-progress) * textHeight)); } } private void drawHorizontalText(Canvas canvas, int color, int startX, int endX) { paint.setColor(color); canvas.save(); Log.e("tag"."getMeasuredHeight"+ getMeasuredHeight()); canvas.clipRect(startX, 0, endX, getMeasuredHeight()); canvas.drawText(text, this.startX, baseLineY, paint); canvas.restore(); } private void drawVerticalText(Canvas canvas, int color, int startY, int endY) { paint.setColor(color); canvas.save(); canvas.clipRect(0, startY, getMeasuredWidth(), endY); canvas.drawText(text, this.startX, this.startY, paint); canvas.restore(); }}Copy the code

I rewrote the onMeasure method and measured the height and width by myself. In fact, you can inherit the TextView directly, so that you don’t need to rewrite the onMeasure method and hand it to the TextView to measure.

One more thing to say about drawing text, the drawText method

/**
     * Draw the text, with origin at (x,y), using the specified paint. The origin is interpreted
     * based on the Align setting in the paint.
     *
     * @param text The text to be drawn
     * @param x The x-coordinate of the origin of the text being drawn
     * @param y The y-coordinate of the baseline of the text being drawn
     * @param paint The paint used for the text (e.g. color, size, style)
     */
    public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
        super.drawText(text, x, y, paint);
    }
Copy the code

The four parameters up here mean draw the text, draw the starting X coordinate, draw the starting Y coordinate, the brush that you use to draw and here you need an image

We need to create a custom View that inherits from tabLayout, rewrite the addTab method, add the custom View we just created, and add the custom View to it And add slide listening, so as to change the color, directly on the code

public class ColorClipTabLayout extends TabLayout { private int tabTextSize; Private int tabSelectedTextColor; Private int tabTextColor; Private static final int INVALID_TAB_POS = -1; Private int lastSelectedTabPosition = INVALID_TAB_POS; // Last selected position private int lastSelectedTabPosition = INVALID_TAB_POS; private ViewPager viewPager; / / viewpager binding by private ColorClipTabLayoutOnPageChangeListener ColorClipTabLayoutOnPageChangeListener; public ColorClipTabLayout(Context context) { this(context, null); } public ColorClipTabLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ColorClipTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr);if(attrs ! = null) { // Text colors/sizes come from the text appearance first final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ColorClipTabLayout); / / Tab font size tabTextSize = ta.621 getDimensionPixelSize (R.s tyleable ColorClipTabLayout_text_size, 72); / / Tab text Color tabTextColor = ta.621 getColor (R.s tyleable ColorClipTabLayout_text_unselected_color, Color. ParseColor ("# 000000"));
            tabSelectedTextColor = ta.getColor(R.styleable.ColorClipTabLayout_text_selected_color, Color.parseColor("#cc0000"));
            ta.recycle();
        }
    }

    @Override
    public void addTab(@NonNull Tab tab, int position, boolean setTAB colorClipView colorClipView = new colorClipView (getContext())); colorClipView.setProgress(setSelected ? 1:0); colorClipView.setText(tab.getText() +"");
        colorClipView.setTextSize(tabTextSize);
        colorClipView.setTag(position);
        colorClipView.setTextSelectedColor(tabSelectedTextColor);
        colorClipView.setTextUnselectColor(tabTextColor);
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        colorClipView.setLayoutParams(layoutParams);
        tab.setCustomView(colorClipView);
        super.addTab(tab, position, setSelected);
        int selectedTabPosition = getSelectedTabPosition();
        if ((selectedTabPosition == INVALID_TAB_POS && position == 0) || (selectedTabPosition == position)) {
            setSelectedView(position);
        }

        setTabWidth(position, colorClipView);
    }

    @Override
    public void setupWithViewPager(@Nullable ViewPager viewPager, boolean autoRefresh) {
        super.setupWithViewPager(viewPager, autoRefresh);
        try {
            if(viewPager ! = null) this.viewPager = viewPager; colorClipTabLayoutOnPageChangeListener = new ColorClipTabLayoutOnPageChangeListener(this); colorClipTabLayoutOnPageChangeListener.reset(); viewPager.addOnPageChangeListener(colorClipTabLayoutOnPageChangeListener); // } } catch (Exception e) { e.printStackTrace(); } } @Override public voidremoveAllTabs() {
        lastSelectedTabPosition = getSelectedTabPosition();
        super.removeAllTabs();
    }

    @Override
    public int getSelectedTabPosition() {
        final int selectedTabPositionAtParent = super.getSelectedTabPosition();
        return selectedTabPositionAtParent == INVALID_TAB_POS ?
                lastSelectedTabPosition : selectedTabPositionAtParent;
    }

    public void setLastSelectedTabPosition(int lastSelectedTabPosition) {
        lastSelectedTabPosition = lastSelectedTabPosition;
    }

    public void setCurrentItem(int position) {
        if(viewPager ! = null) viewPager.setCurrentItem(position); } private voidsetTabWidth(int position, ColorClipView colorClipView) { ViewGroup slidingTabStrip = (ViewGroup) getChildAt(0); ViewGroup tabView = (ViewGroup) slidingTabStrip.getChildAt(position); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT); int w = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); int h = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); // ColorClipview.measure (w, h); params.width = colorClipView.getMeasuredWidth() + tabView.getPaddingLeft() + tabView.getPaddingRight(); // Set the width of the tabView tabView.setLayoutParams(params); } private voidsetSelectedView(int position) {
        final int tabCount = getTabCount();
        if (position < tabCount) {
            for(int i = 0; i < tabCount; i++) { getColorClipView(i).setProgress(i == position ? 1:0); } } } public void tabScrolled(int position,float positionOffset) {

        if(positionOffset = = 0.0 F) {return; } ColorClipView currentTrackView = getColorClipView(position); ColorClipView nextTrackView = getColorClipView(position + 1); currentTrackView.setDirection(1); CurrentTrackView. SetProgress (F - 1.0 positionOffset); nextTrackView.setDirection(0); nextTrackView.setProgress(positionOffset); } private ColorClipView getColorClipView(int position) {return (ColorClipView) getTabAt(position).getCustomView();
    }

    public static class ColorClipTabLayoutOnPageChangeListener extends TabLayoutOnPageChangeListener {

        private final WeakReference<ColorClipTabLayout> mTabLayoutRef;
        private int mPreviousScrollState;
        private int mScrollState;

        public ColorClipTabLayoutOnPageChangeListener(TabLayout tabLayout) {
            super(tabLayout);
            mTabLayoutRef = new WeakReference<>((ColorClipTabLayout) tabLayout);
        }

        @Override
        public void onPageScrollStateChanged(final int state) {
            mPreviousScrollState = mScrollState;
            mScrollState = state;
        }

        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            super.onPageScrolled(position, positionOffset, positionOffsetPixels);
            ColorClipTabLayout tabLayout = mTabLayoutRef.get();
            if (tabLayout == null) return; final boolean updateText = mScrollState ! = SCROLL_STATE_SETTLING || mPreviousScrollState == SCROLL_STATE_DRAGGING;if (updateText) {
                Log.e("tag"."positionOffset" + positionOffset);
                tabLayout.tabScrolled(position, positionOffset);
            }
        }

        @Override
        public void onPageSelected(int position) {
            super.onPageSelected(position);
            ColorClipTabLayout tabLayout = mTabLayoutRef.get();
            mPreviousScrollState = SCROLL_STATE_SETTLING;
            tabLayout.setSelectedView(position);
        }

        void reset() { mPreviousScrollState = mScrollState = SCROLL_STATE_IDLE; }}}Copy the code

Here, take a look at the effect

OJBK, this is the effect I want, without further words, the code has been uploaded to Github, you can have a look, like you can star, well, you are the most handsome and beautiful…

Finally, I recommend a custom View tutorial for the throw line, which really helps HenCoder

ViewPager pointer ADAPTS to Tab width and slides text gradually to color TabLayout

Making address: ColorTabLayout