一、看效果
二、实现原理
1、继承线性布局LinearLayout;
2、在测量方法onMeasure中,在原高度上基础上增加下划线高度:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight() + underlineHeight);
}
3、在布局方法onLayout中,为下划线预留高度:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b - underlineHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int childCount = getChildCount();
if (childCount <= 0 || currentTab < 0 || currentTab >= childCount) {
return;
}
if (offset < -1) {
offset = -1;
}
if (offset > 1) {
offset = 1;
}
View child = getChildAt(currentTab);
int underlineWidth = child.getWidth();
int left = child.getLeft();
int right = child.getRight();
int bottom = getHeight() - getPaddingBottom();
int top = bottom - underlineHeight;
float offsetWidth = underlineWidth * offset;
paint.setColor(underlineColor);
canvas.drawRect(left + offsetWidth, top, right + offsetWidth, bottom, paint);
}
注意,绘制方法中同时也实现下划线动态偏移的效果,后面可以看完整代码。
三、完整代码
在下述代码中,我们还给出了一个简单设置文本标签的快捷适配器。
import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
public class TabIndicator extends LinearLayout {
protected BaseAdapter adapter;
private int underlineColor;
private int underlineHeight;
private Paint paint;
private int currentTab = 0;
private float offset = 0;
public TabIndicator(Context context) {
this(context, null);
}
public TabIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public void setUnderlineColor(int underlineColor) {
this.underlineColor = underlineColor;
}
public void setUnderlineHeight(int underlineHeight) {
this.underlineHeight = underlineHeight;
}
public void setCurrentTab(int currentTab) {
setCurrentTab(currentTab, 0);
}
public void setCurrentTab(int currentTab, float offset) {
this.currentTab = currentTab;
this.offset = offset;
invalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight() + underlineHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b - underlineHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int childCount = getChildCount();
if (childCount <= 0 || currentTab < 0 || currentTab >= childCount) {
return;
}
if (offset < -1) {
offset = -1;
}
if (offset > 1) {
offset = 1;
}
View child = getChildAt(currentTab);
int underlineWidth = child.getWidth();
int left = child.getLeft();
int right = child.getRight();
int bottom = getHeight() - getPaddingBottom();
int top = bottom - underlineHeight;
float offsetWidth = underlineWidth * offset;
paint.setColor(underlineColor);
canvas.drawRect(left + offsetWidth, top, right + offsetWidth, bottom, paint);
}
private void initView() {
setOrientation(LinearLayout.HORIZONTAL);
paint = new Paint();
paint.setAntiAlias(true);
}
public void setAdapter(BaseAdapter adapter) {
this.adapter = adapter;
if (this.adapter == null) {
removeAllViews();
return;
}
this.adapter.registerDataSetObserver(new DataSetObserver() {
@Override
public void onChanged() {
fillChilds();
}
@Override
public void onInvalidated() {
fillChilds();
}
});
fillChilds();
}
protected void fillChilds() {
removeAllViews();
for (int i = 0; i < adapter.getCount(); i++) {
final View child = adapter.getView(i, null, null);
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER_VERTICAL;
params.weight=1;
addView(child, params);
}
}
public static abstract class TextAdapter extends BaseAdapter {
private final String[] titles;
public TextAdapter(String[] titles) {
this.titles = titles;
}
@Override
public int getCount() {
if (titles == null || titles.length <= 0) {
return 0;
}
return titles.length;
}
@Override
public Object getItem(int position) {
if (titles == null || titles.length <= 0) {
return null;
}
return titles[position];
}
@Override
public long getItemId(int position) {
return position;
}
public abstract TextView getTextView(int position, View convertView, ViewGroup container);
@Override
public View getView(final int position, View convertView, ViewGroup container) {
TextView tv= getTextView(position, convertView, container);
tv.setText(titles[position]);
return tv;
}
}
}
四、与ViewPager结合使用例子
TabIndicator tab = (TabIndicator) findViewById(R.id.tab);
//设置下划线颜色
tab.setUnderlineColor(Color.parseColor("#FFF44D06"));
//设置下划线高度
tab.setUnderlineHeight(6);
//设置标签视图适配器
tab.setAdapter(new TextAdapter(new String[]{"销量","价格","筛选"}) {
@Override
public TextView getTextView(int position, View convertView, ViewGroup container) {
return new TextView(getApplicationContext());
}
});
ViewPager pager = (ViewPager) findViewById(R.id.pager);
pager.setOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageSelected(int arg0) {
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
//注意,此处是让下划线动态滑动的关键
tab.setCurrentTab(arg0, arg1);
}
@Override
public void onPageScrollStateChanged(int arg0) {
}
});
五、特性总结
1、指示器继承线性布局的一切原有特性和语义,无任何负作用;
2、指示器完全在布局层实现,与标签视图无关,适配器中的视图可以是任意View;
3、代码量精简,使用方便,性能稳定。
国际惯例
————————————————————————————————————————————————————————
作者:薄荷记账 (转载请注明原作者)
简洁 稳定 优雅 无限可能!