天天看點

Android變色TabLayout自定義

前段時間看見了今日頭條的tablayout,感覺相當新鮮,也比較感興趣,效果就是下邊這個

![gif5新檔案.gif](https://upload-images.jianshu.io/upload_images/4770849-71581d5181aabb13.gif?imageMogr2/auto-orient/strip)

像這種效果應該是需要自定義的View來實作的,可以看到在滑動過程中,兩個相鄰的tab是有局部顔色的變換的,前一個tab部分恢複成黑色,後一個tab會部分變為紅色,這取決于滑動的距離.

首先每一個tab應該都是自定義的View,因為這涉及到了局部文字變色,是以應該先定制一個能夠局部文字變色的View,普通的View當然不支援啦~

先來說下思路~主要用的方法是canvas的clipRect方法,先來看下這個方法啥子意思喲..

````

/**

    * Intersect the current clip with the specified rectangle, which is

    * expressed in local coordinates.

    *

    * @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

    * @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);

   }

解釋一下,裡邊的四個參數裁剪範圍的左上右下的位置,比較好了解,需要注意的是,使用完這個方法後需要及時的恢複繪制範圍,是以完整代碼如下

canvas.save();  

canvas.clipRect(left, top, right, bottom);  

//再做繪制操作例如本片要用到的drawText()

canvas.restore();  

知道了這個方法,那麼就想想怎麼繪制出兩種顔色的文本了,先上個圖

![](https://upload-images.jianshu.io/upload_images/4770849-b4622cfa5718e28b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

圖中的1部分為黑色,2部分為紅色,那麼再繪制過程中我們隻需要利用clipRect這個方法,分别裁剪出1部分的範圍以及2部分的範圍,分别使用不同顔色繪制就OK啦~但是總體的繪制起點以及文本都是一樣的,這樣就看起來是一個文本兩種顔色,其實我們是繪制了兩邊,還是比較好了解的

話不多說,直接上代碼

public class ColorClipView extends View {

   private Paint paint;//畫筆

   private String text = "我是不哦車網";//繪制的文本

   private int textSize = sp2px(18);//文本字型大小

   private int textWidth;//文本的寬度

   private int textHeight;//文本的高度

   private int textUnselectColor = R.color.colorPrimary;//文本未選中字型顔色

   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();//文本顯示區域

   private int startX;//X軸開始繪制的坐标

   private int startY;//y軸開始繪制的坐标

   private int baseLineY;//基線的位置

   private float progress;

   public ColorClipView(Context context) {

       this(context, null);

   public ColorClipView(Context context, AttributeSet attrs) {

       super(context, attrs);

       //初始化各個屬性包括畫筆

       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();//用完就得收!

       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();

   public void setText(String text) {

       this.text = text;

   public void setDirection(int direction) {

       this.mDirection = direction;

   public void setTextUnselectColor(int unselectColor) {

       this.textUnselectColor = unselectColor;

   public void setTextSelectedColor(int selectedColor) {

       this.textSelectedColor = selectedColor;

   @Override

   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

       measureText();//測量文本的長寬

       int width = measureWidth(widthMeasureSpec);//通過模式的不同來測量出實際的寬度

       int height = measureHeight(heightMeasureSpec);//通過模式的不同來測量出實際的高度

       setMeasuredDimension(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();

       }

       realSize = mode == MeasureSpec.AT_MOST ? Math.min(realSize, size) : realSize;

       return realSize;

   private int measureWidth(int widthMeasureSpec) {

       int mode = MeasureSpec.getMode(widthMeasureSpec);//通過widthMeasureSpec拿到Mode

       int size = MeasureSpec.getSize(widthMeasureSpec);//同理

       int realSize = 0;//最後傳回的值

           case MeasureSpec.EXACTLY://精确模式下直接用給出的寬度

           case MeasureSpec.AT_MOST://最大模式

           case MeasureSpec.UNSPECIFIED://未指定模式

               //這兩種情況下,用測量出的寬度加上左右padding

               realSize = textWidth;

               realSize = realSize + getPaddingLeft() + getPaddingRight();

       //如果mode為最大模式,不應該大于父類傳入的值,是以取最小

   private void measureText() {

       textWidth = (int) paint.measureText(text);//測量文本寬度

       Log.d("tag", "measureText=" + paint.measureText(text));

       //直接通過獲得文本顯示範圍,再獲得高度

       //參數裡,text 是要測量的文字

       //start 和 end 分别是文字的起始和結束位置,textRect 是存儲文字顯示範圍的對象,方法在測算完成之後會把結果寫進 textRect。

       paint.getTextBounds(text, 0, text.length(), textRect);

       textHeight = textRect.height();

       //通過文本的descent線與top線的距離來測量文本高度,這是其中一種測量方法

       Paint.FontMetrics fm = paint.getFontMetrics();

       textHeight = (int) Math.ceil(fm.descent - fm.top);

       baseLineY = (int) (textHeight / 2 - (fm.bottom - fm.top) / 2 - fm.top);

   protected void onDraw(Canvas canvas) {

       super.onDraw(canvas);

       //OK~開始繪制咯~

       //首先先判斷方向是左還是右呢?  是上還是下呢? 真期待....

       Log.e("tag", "OnDraw");

       if (mDirection == DIRECTION_LEFT) {

           //繪制朝左的選中文字

           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,

                   (int) (startY + progress * textHeight));

           //繪制朝上的未選中文字

           drawVerticalText(canvas, textUnselectColor, (int) (startY + progress

                   * textHeight), startY + textHeight);

       } else {

           //繪制朝下的選中文字

           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) {

       canvas.clipRect(0, startY, getMeasuredWidth(), endY);

       canvas.drawText(text, this.startX,

               this.startY, paint);

}

上邊代碼我自己重寫了onMeasure方法,自己測量了高度與寬度,其實我麼你可以直接繼承TextView,這樣可以不用重寫onMeasure方法,直接交給TextView去測量...

還有關于繪制文字這一點,也就是drawText這個方法需要說一下

    * 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);

上邊的四個參數意思分别是,繪制文本,繪制起點X坐标,繪制起點Y坐标,用來繪制的畫筆

這裡的需要上一張圖

![](https://upload-images.jianshu.io/upload_images/4770849-d63332516f3e3cc6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

如圖所示,在繪制文本的時候繪制起點在左下角而不是左上角,這個需要特殊說明一下...

然後再上一張圖來看下效果,這裡我加了手勢操作來改變文本顔色

![xixi.gif](https://upload-images.jianshu.io/upload_images/4770849-5429aedf91389e37.gif?imageMogr2/auto-orient/strip)

然後每個tab我們制作完了,需要把tab放到tablayout中,并且會随着滑動距離而改變相鄰兩個的tab的不分顔色,OK~我們有需要自定義一個繼承于tablayout的View,重寫addtab方法,将我們剛才的自定義View加入進去,并且加入滑動監聽,進而改變顔色,直接上代碼

public class ColorClipTabLayout extends TabLayout {

   private int tabTextSize;//每個tab字型大小

   private int tabSelectedTextColor;//每個tab選中字型顔色

   private int tabTextColor;//每個tab未選中顔色

   private static final int INVALID_TAB_POS = -1;

   //最後的選中位置

   private int lastSelectedTabPosition = INVALID_TAB_POS;

   private ViewPager viewPager;//所綁定的viewpager

   private ColorClipTabLayoutOnPageChangeListener colorClipTabLayoutOnPageChangeListener;

   public ColorClipTabLayout(Context context) {

   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字型大小

           tabTextSize = ta.getDimensionPixelSize(R.styleable.ColorClipTabLayout_text_size, 72);

           //Tab文字顔色

           tabTextColor = ta.getColor(R.styleable.ColorClipTabLayout_text_unselected_color, Color.parseColor("#000000"));

           tabSelectedTextColor = ta.getColor(R.styleable.ColorClipTabLayout_text_selected_color, Color.parseColor("#cc0000"));

           ta.recycle();

   public void addTab(@NonNull Tab tab, int position, boolean setSelected) {

       //通過addTab的方式将colorClipView作為customView傳入tab

       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);

   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();

   public void removeAllTabs() {

       lastSelectedTabPosition = getSelectedTabPosition();

       super.removeAllTabs();

   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 void setTabWidth(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,

       //手動測量一下

       colorClipView.measure(w, h);

       params.width = colorClipView.getMeasuredWidth() + tabView.getPaddingLeft() + tabView.getPaddingRight();

       //設定tabView的寬度

       tabView.setLayoutParams(params);

   private void setSelectedView(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.0F) {

           return;

       ColorClipView currentTrackView = getColorClipView(position);

       ColorClipView nextTrackView = getColorClipView(position + 1);

       currentTrackView.setDirection(1);

       currentTrackView.setProgress(1.0F - 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;

       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);

       public void onPageSelected(int position) {

           super.onPageSelected(position);

           mPreviousScrollState = SCROLL_STATE_SETTLING;

           tabLayout.setSelectedView(position);

       void reset() {

           mPreviousScrollState = mScrollState = SCROLL_STATE_IDLE;

來,看下效果

![xixi2.gif](https://upload-images.jianshu.io/upload_images/4770849-3bd4644f2c048252.gif?imageMogr2/auto-orient/strip)

OJBK,這就是我想要的效果,話不多說,代碼已經上傳到github,可以下下來看一看,喜歡的可以star一下

恩,你們都是最帥的最美的...

最後推薦一波扔物線的自定義View教程,真的很有幫助[HenCoder](http://hencoder.com/)

參考:

[Android 自定義控件玩轉字型變色 打造炫酷ViewPager訓示器](https://blog.csdn.net/lmj623565791/article/details/44098729)

[自适應Tab寬度可以滑動文字逐漸變色的TabLayout](https://www.jianshu.com/p/4ab5e09a30e8)

github位址:[ColorTabLayout](https://github.com/ConanHolmes/ColorTabLayout)

繼續閱讀