前段时间看见了今日头条的tablayout,感觉相当新鲜,也比较感兴趣,效果就是下边这个
像这种效果应该是需要自定义的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
* 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);
}
解释一下,里边的四个参数裁剪范围的左上右下的位置,比较好理解,需要注意的是,使用完这个方法后需要及时的恢复绘制范围,所以完整代码如下
canvas.save();
canvas.clipRect(left, top, right, bottom);
//再做绘制操作例如本片要用到的drawText()
canvas.restore();
知道了这个方法,那么就想想怎么绘制出两种颜色的文本了,先上个图
图中的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();
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);//通过模式的不同来测量出实际的高度
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为最大模式,不应该大于父类传入的值,所以取最小
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));
//直接通过获得文本显示范围,再获得高度
//参数里,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);
}
@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方法,自己测量了高度与宽度,其实我么你可以直接继承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坐标,用来绘制的画笔
这里的需要上一张图
如图所示,在绘制文本的时候绘制起点在左下角而不是左上角,这个需要特殊说明一下...
然后再上一张图来看下效果,这里我加了手势操作来改变文本颜色
然后每个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) {
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);
//手动测量一下
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;
}
@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,这就是我想要的效果,话不多说,代码已经上传到github,可以下下来看一看,喜欢的可以star一下
恩,你们都是最帅的最美的...
最后推荐一波扔物线的自定义View教程,真的很有帮助HenCoder
参考:
Android 自定义控件玩转字体变色 打造炫酷ViewPager指示器
自适应Tab宽度可以滑动文字逐渐变色的TabLayout
github地址:ColorTabLayout
下一篇算是自我学习的文章:Android的线程池