前段時間看見了今日頭條的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)