前段時(shí)間看見了今日頭條的tablayout,感覺相當(dāng)新鮮,也比較感興趣,效果就是下邊這個(gè)
像這種效果應(yīng)該是需要自定義的View來實(shí)現(xiàn)的鸵贬,可以看到在滑動(dòng)過程中,兩個(gè)相鄰的tab是有局部顏色的變換的,前一個(gè)tab部分恢復(fù)成黑色,后一個(gè)tab會(huì)部分變?yōu)榧t色,這取決于滑動(dòng)的距離.
首先每一個(gè)tab應(yīng)該都是自定義的View,因?yàn)檫@涉及到了局部文字變色,所以應(yīng)該先定制一個(gè)能夠局部文字變色的View,普通的View當(dāng)然不支持啦~
先來說下思路~主要用的方法是canvas的clipRect方法,先來看下這個(gè)方法啥子意思喲..
/**
* 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
* 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);
}
解釋一下俗他,里邊的四個(gè)參數(shù)裁剪范圍的左上右下的位置,比較好理解,需要注意的是,使用完這個(gè)方法后需要及時(shí)的恢復(fù)繪制范圍,所以完整代碼如下
canvas.save();
canvas.clipRect(left, top, right, bottom);
//再做繪制操作例如本片要用到的drawText()
canvas.restore();
知道了這個(gè)方法,那么就想想怎么繪制出兩種顏色的文本了,先上個(gè)圖
圖中的1部分為黑色,2部分為紅色,那么再繪制過程中我們只需要利用clipRect這個(gè)方法,分別裁剪出1部分的范圍以及2部分的范圍,分別使用不同顏色繪制就OK啦~但是總體的繪制起點(diǎn)以及文本都是一樣的,這樣就看起來是一個(gè)文本兩種顏色,其實(shí)我們是繪制了兩邊,還是比較好理解的
話不多說,直接上代碼
public class ColorClipView extends View {
private Paint paint;//畫筆
private String text = "我是不哦車網(wǎng)";//繪制的文本
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();//文本顯示區(qū)域
private int startX;//X軸開始繪制的坐標(biāo)
private int startY;//y軸開始繪制的坐標(biāo)
private int baseLineY;//基線的位置
private float progress;
public ColorClipView(Context context) {
this(context, null);
}
public ColorClipView(Context context, AttributeSet attrs) {
super(context, attrs);
//初始化各個(gè)屬性包括畫筆
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();
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);//通過模式的不同來測量出實(shí)際的寬度
int height = measureHeight(heightMeasureSpec);//通過模式的不同來測量出實(shí)際的高度
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();
break;
}
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;//最后返回的值
switch (mode) {
case MeasureSpec.EXACTLY://精確模式下直接用給出的寬度
realSize = size;
break;
case MeasureSpec.AT_MOST://最大模式
case MeasureSpec.UNSPECIFIED://未指定模式
//這兩種情況下,用測量出的寬度加上左右padding
realSize = textWidth;
realSize = realSize + getPaddingLeft() + getPaddingRight();
break;
}
//如果mode為最大模式,不應(yīng)該大于父類傳入的值,所以取最小
realSize = mode == MeasureSpec.AT_MOST ? Math.min(realSize, size) : realSize;
return realSize;
}
private void measureText() {
textWidth = (int) paint.measureText(text);//測量文本寬度
Log.d("tag", "measureText=" + paint.measureText(text));
//直接通過獲得文本顯示范圍,再獲得高度
//參數(shù)里兆衅,text 是要測量的文字
//start 和 end 分別是文字的起始和結(jié)束位置,textRect 是存儲(chǔ)文字顯示范圍的對(duì)象嗜浮,方法在測算完成之后會(huì)把結(jié)果寫進(jìn) 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);
}
@Override
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) {
paint.setColor(color);
canvas.save();
canvas.clipRect(0, startY, getMeasuredWidth(), endY);
canvas.drawText(text, this.startX,
this.startY, paint);
canvas.restore();
}
}
上邊代碼我自己重寫了onMeasure方法,自己測量了高度與寬度,其實(shí)我么你可以直接繼承TextView,這樣可以不用重寫onMeasure方法,直接交給TextView去測量...
還有關(guān)于繪制文字這一點(diǎn),也就是drawText這個(gè)方法需要說一下
/**
* 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);
}
上邊的四個(gè)參數(shù)意思分別是,繪制文本,繪制起點(diǎn)X坐標(biāo),繪制起點(diǎn)Y坐標(biāo),用來繪制的畫筆
這里的需要上一張圖
如圖所示,在繪制文本的時(shí)候繪制起點(diǎn)在左下角而不是左上角,這個(gè)需要特殊說明一下...
然后再上一張圖來看下效果,這里我加了手勢操作來改變文本顏色
然后每個(gè)tab我們制作完了,需要把tab放到tablayout中,并且會(huì)隨著滑動(dòng)距離而改變相鄰兩個(gè)的tab的不分顏色,OK~我們有需要自定義一個(gè)繼承于tablayout的View,重寫addtab方法,將我們剛才的自定義View加入進(jìn)去,并且加入滑動(dòng)監(jiān)聽,從而改變顏色,直接上代碼
public class ColorClipTabLayout extends TabLayout {
private int tabTextSize;//每個(gè)tab字體大小
private int tabSelectedTextColor;//每個(gè)tab選中字體顏色
private int tabTextColor;//每個(gè)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) {
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字體大小
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();
}
}
@Override
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);
}
@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 void removeAllTabs() {
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 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,
MeasureSpec.UNSPECIFIED);
//手動(dòng)測量一下
colorClipView.measure(w, h);
params.width = colorClipView.getMeasuredWidth() + tabView.getPaddingLeft() + tabView.getPaddingRight();
//設(shè)置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;
}
@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;
}
}
}
來,看下效果
OJBK,這就是我想要的效果,話不多說,代碼已經(jīng)上傳到github,可以下下來看一看,喜歡的可以star一下
恩,你們都是最帥的最美的...
最后推薦一波扔物線的自定義View教程,真的很有幫助HenCoder
參考:
Android 自定義控件玩轉(zhuǎn)字體變色 打造炫酷ViewPager指示器
自適應(yīng)Tab寬度可以滑動(dòng)文字逐漸變色的TabLayout
github地址:ColorTabLayout
下一篇算是自我學(xué)習(xí)的文章:Android的線程池