仿微博导航条

前言

忘记什么时候开始看微博时,无意中注意到微博的导航条,好有趣,就无聊的拖过来拖过去。不多说,上图。

微博导航条

可以看下微博,自己滑动试一试。

看到上面的黄色的条条,可长可短,邪恶~~

两个TAB页,关注和热门。
几个特点:

  • 关注页面滑到页面的一半宽度以上时会自动切换到热门页面,这是ViewPager的特性。
  • 关键看黄条的长度。当关注页面滑动一半时,黄条的长度 到达“热门”两个字的接近右边,不会边长。反之,亦然。
  • 选中的页面的字体大小与颜色均有变化。
  • 黄色线的颜色是渐变的(可以自己认真看下微博导航条的颜色)

看下我的实现:

开鲁

导航条的整体构造

制作导航条的TextView

导航条的滑动

我们从上到下看看这个导航条是怎么制作的。对于这个,我们可以使用现成的HorizontalScrollView。也就是这个水平滑动的ScollView。使用TextView填充HorizontalScrollView时,会出现两种情况:

HorizontalScrollView与TextView

分析:

  • 根据计算所有TextView的长度+TextView的左右边距与屏幕宽度比较,判断TextView的总长度大于小于屏幕宽度。
  • 导航条上面的分类字数较少时,没有盛满,我们要首先计算平分的每个TextView字体的宽度,然后指定TextView的左右边距。
  • 字数长时,我们设置TextView的左右边距为默认边距

根据TextView的实际长度计算其左右边距代码

1
2
3
4
5
6
7
8
9
10
11
/**
*
* @param titleAry TextView的String字符串 “关注” “推荐”
* @return
*/
private int getTextViewMargins(String[] titleAry) {
int defaultMargins = 30;
float countLength = 0;
TextView textView = new TextView(getContext());
textView.setTextSize(defaultTextSize);
TextPaint paint = textView.getPaint();
1
2
3
4
5
6
7
8
9
10
11
12
13
        for (int i = 0; i < titleAry.length; i++) {
countLength = countLength + defaultMargins + paint.measureText(titleAry[i]) + defaultMargins;
}
int screenWidth = getScreenWidth(getContext());

if (countLength <= screenWidth) { //TextView总长度小于屏幕宽度
allTextViewLength = screenWidth;
return (screenWidth / titleAry.length - (int) paint.measureText(titleAry[0])) / 2;
} else { //TextView总长度大于屏幕宽度
allTextViewLength = (int) countLength;
return defaultMargins;
}
}

知道了每个TextView的左右边距后(每个边距均一致,美观,并且绝大多数APP都是这样设计的,UED懂的),然后在一个个创建TextView添加到textViewLl中即可。

将所有TextView添加到contentLl中

ViewPagerTitle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/**
* Created by lovexujh on 2017/7/3
*/

public class ViewPagerTitle extends HorizontalScrollView {

private String[] titles;//导航条的字符串:关注、推荐 、视频。。。
private ArrayList<TextView> textViews = new ArrayList<>(); //导航条的所有TextView
private DynamicLine dynamicLine;
private ViewPager viewPager;
private MyOnPageChangeListener onPageChangeListener;//ViewPager的滑动监听
private int margin;//导航条的每两个TextView之间的间距
private LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
private LinearLayout.LayoutParams textViewParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
private float defaultTextSize = 18;
private float selectedTextSize = 22;
private int defaultTextColor = Color.GRAY;
private int selectedTextColor = Color.BLACK;
private int allTextViewLength;
public ViewPagerTitle(Context context) {
this(context, null);
}

public ViewPagerTitle(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public ViewPagerTitle(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

private void init() {

}
public void initData(String[] titles, ViewPager viewPager, int defaultIndex) {
this.titles = titles;
this.viewPager = viewPager;
createDynamicLine();
createTextViews(titles);

int fixLeftDis = getFixLeftDis();
onPageChangeListener = new MyOnPageChangeListener(getContext(), viewPager, dynamicLine, this, allTextViewLength, margin, fixLeftDis);
setDefaultIndex(defaultIndex);

viewPager.addOnPageChangeListener(onPageChangeListener);

}

/**
* 这个方法是来修正TextView的左右边距的,
* 因为每个TextView而言 : leftMargins + TextViewLength + rightMargins 这三个的值要一致,
* 被选中的TExtView的TextViewLength要比默认没有选中的TextView的TextViewLength大,
* 所以选中的字体的左右边距要偏小。
* @return
*/
private int getFixLeftDis() {
TextView textView = new TextView(getContext());
textView.setTextSize(defaultTextSize);
textView.setText(titles[0]);
float defaultTextSize = getTextViewLength(textView);
textView.setTextSize(selectedTextSize);
float selectTextSize = getTextViewLength(textView);
return (int)(selectTextSize - defaultTextSize) / 2;
}

public ArrayList<TextView> getTextView() {
return textViews;
}
private void createDynamicLine() {
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
dynamicLine = new DynamicLine(getContext());
dynamicLine.setLayoutParams(params);
}
private void createTextViews(String[] titles) {
LinearLayout contentLl = new LinearLayout(getContext());
contentLl.setBackgroundColor(Color.parseColor("#fffacd"));
contentLl.setLayoutParams(contentParams);
contentLl.setOrientation(LinearLayout.VERTICAL);
addView(contentLl);
LinearLayout textViewLl = new LinearLayout(getContext());
textViewLl.setLayoutParams(contentParams);
textViewLl.setOrientation(LinearLayout.HORIZONTAL);

margin = getTextViewMargins(titles);

textViewParams.setMargins(margin, 0, margin, 0);

for (int i = 0; i < titles.length; i++) {
TextView textView = new TextView(getContext());
textView.setText(titles[i]);
textView.setTextColor(Color.GRAY);
textView.setTextSize(defaultTextSize);
textView.setLayoutParams(textViewParams);
textView.setGravity(Gravity.CENTER_HORIZONTAL);
textView.setOnClickListener(onClickListener);
textView.setTag(i);
textViews.add(textView);
textViewLl.addView(textView);
}
contentLl.addView(textViewLl); //将所有的TextView所在的LinerLayout添加到HorizontalScrollView的contentLl中
contentLl.addView(dynamicLine);//dynamicLine是左右跑动的黄色的线
}

/**
*
* @param titleAry TextView的String字符串 “关注” “推荐”
* @return
*/
private int getTextViewMargins(String[] titleAry) {
int defaultMargins = 30;
float countLength = 0;
TextView textView = new TextView(getContext());
textView.setTextSize(defaultTextSize);
TextPaint paint = textView.getPaint();
for (int i = 0; i < titleAry.length; i++) {
countLength = countLength + defaultMargins + paint.measureText(titleAry[i]) + defaultMargins;
}
int screenWidth = getScreenWidth(getContext());

if (countLength <= screenWidth) { //TextView总长度小于屏幕宽度
allTextViewLength = screenWidth;
return (screenWidth / titleAry.length - (int) paint.measureText(titleAry[0])) / 2;
} else { //TextView总长度大于屏幕宽度
allTextViewLength = (int) countLength;
return defaultMargins;
}
}
private OnClickListener onClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
setCurrentItem((int) v.getTag());
viewPager.setCurrentItem((int) v.getTag());

}
};

public void setDefaultIndex(int index) {
setCurrentItem(index);
}

public void setCurrentItem(int index) {
for (int i = 0; i < textViews.size(); i++) {
if (i == index) {
textViews.get(i).setTextColor(selectedTextColor);
textViews.get(i).setTextSize(selectedTextSize);
} else {
textViews.get(i).setTextColor(defaultTextColor);
textViews.get(i).setTextSize(defaultTextSize);
}
}
}

@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
viewPager.removeOnPageChangeListener(onPageChangeListener);
}
}

黄色的线-DynamicLine

可以看到黄色的线并不是一条线,而是一个圆角矩形。这就可以使用drawRoundRect(@NonNull
RectF rect, float rx, float ry, @NonNull Paint paint) 这个API。

关键点在于,黄色圆角矩形的移动,只要更改圆角矩形的起始X坐标与终止X坐标。这样就可以让黄色条条进行移动了
来自定义一个DynamicLine继承View,代码及说明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class DynamicLine extends View {
private float startX, stopX;//的起始X,终止X坐标。
private Paint paint;
private RectF rectF = new RectF(startX, 0, stopX, 0);//RectF指的是float精度的矩形

public DynamicLine(Context context) {
this(context, null);
}

public DynamicLine(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public DynamicLine(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

private void init() {
paint = new Paint();
paint.setAntiAlias(true);//抗锯齿
paint.setStyle(Paint.Style.FILL);//填充
paint.setStrokeWidth(5);//画笔宽度
paint.setShader(new LinearGradient(0, 100, getScreenWidth(getContext()), 100, Color.parseColor("#ffc125"), Color.parseColor("#ff4500"), Shader.TileMode.MIRROR));//设置画笔渐变色
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//自定义DynamicLine的高度
heightMeasureSpec = MeasureSpec.makeMeasureSpec(20, MeasureSpec.getMode(heightMeasureSpec));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

@Override
protected void onDraw(Canvas canvas) {
rectF.set(startX, 0, stopX, 10);
canvas.drawRoundRect(rectF, 5, 5, paint);//圆角矩形的圆角的曲率
}

/**
* 根据起始、终止坐标更新黄色圆角,进行重新绘制
* @param startX
* @param stopX
*/
public void updateView(float startX, float stopX) {//
this.startX = startX;
this.stopX = stopX;
invalidate();
}
}

我们把DynamicLine放到activity中添加下面代码,测试一下,效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MainActivity extends AppCompatActivity {
private DynamicLine dynamicLine;
private float startX, stopX;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dynamicLine = (DynamicLine)findViewById(R.id.dynamicLine);
// init();
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startX = ev.getRawX();
case MotionEvent.ACTION_MOVE:
stopX = ev.getRawX();
dynamicLine.updateView(startX, stopX);
}
return super.dispatchTouchEvent(ev);
}
}

DynamicLine

有渐变色,有效果。可以,没问题。

后面我们需要知道当viewpager切换时动作与DynamicLine的startX与stopX的具体对应关系。可以使用ViewPager的addOnPageChangeListener(OnPageChangeListener
listener)方法。

OnPageChangeListener 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
public class MyOnPageChangeListener implements ViewPager.OnPageChangeListener {
private int fixLeftDis;
private ArrayList<TextView> textViews;
private ViewPagerTitle viewPagerTitle;
private DynamicLine dynamicLine;

private ViewPager pager;
private int pagerCount;
private int screenWidth;
private int lineWidth;
private int everyLength;
private int lastPosition;
private int dis;
private int[] location = new int[2];

/**
*
* @param context
* @param viewPager
* @param dynamicLine
* @param viewPagerTitle
* @param allLength 所有的TextView的总长度。
* @param margin TextView的左右边距。
* @param fixLeftDis TextView的修正的距离
*/
public MyOnPageChangeListener(Context context, ViewPager viewPager, DynamicLine dynamicLine, ViewPagerTitle viewPagerTitle, int allLength, int margin, int fixLeftDis) {
this.viewPagerTitle = viewPagerTitle;
this.pager = viewPager;
this.dynamicLine = dynamicLine;
textViews = viewPagerTitle.getTextView();
pagerCount = textViews.size();
screenWidth = getScreenWidth(context);

lineWidth = (int) getTextViewLength(textViews.get(0));

everyLength = allLength / pagerCount;
dis = margin;
this.fixLeftDis = fixLeftDis;
}

/**
*
* @param position
* @param positionOffset 当前页面的便宜百分小数 [0, 1)
* @param positionOffsetPixels 当前页面的偏移像素 0 ~ 屏幕宽度
*/
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

if (lastPosition > position) {//页面向右滚动
/**
* 档页面向右滚动时,dynamicLine的右边的stopX位置不变,startX在变化。
*/
dynamicLine.updateView((position + positionOffset) * everyLength + dis + fixLeftDis, (lastPosition + 1) * everyLength - dis);

} else { //页面向左滚动
/**
* 档页面向左滚动时,dynamicLine的左边的startX位置不变,stopX在变化。
*/
if (positionOffset > 0.5f) {
positionOffset = 0.5f;
}
dynamicLine.updateView(lastPosition * everyLength + dis + fixLeftDis, (position + positionOffset * 2) * everyLength + dis + lineWidth);

}

}

@Override
public void onPageSelected(int position) {
viewPagerTitle.setCurrentItem(position);
}

/**
* state 的几个状态:
* SCROLL_STATE_IDLE 挂起,空闲,页面处于静止状态
* SCROLL_STATE_DRAGGING 拖拽,页面处于拖拽状态
* SCROLL_STATE_SETTLING 设置,手指滑动后当手指离开页面时
* @param state
*/
@Override
public void onPageScrollStateChanged(int state) {
boolean scrollRight;//页面向右
if (state == SCROLL_STATE_SETTLING) {
scrollRight = lastPosition < pager.getCurrentItem();
lastPosition = pager.getCurrentItem();
/**
* 下面几行代码,解决页面滑到的TAB页时对应的TextView对应,TextView处于屏幕外面,
* 这个时候就需要将HorizontalScrollView滑动到屏幕中间。
*/
if (lastPosition + 1 < textViews.size() && lastPosition - 1 >= 0) {
textViews.get(scrollRight ? lastPosition + 1 : lastPosition - 1).getLocationOnScreen(location);
if (location[0] > screenWidth) {
viewPagerTitle.smoothScrollBy(screenWidth / 2, 0);
} else if (location[0] < 0) {
viewPagerTitle.smoothScrollBy(-screenWidth / 2, 0);
}
}

}

}

}
Tool 工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Created by lovexujh on 2017/7/4
*/

public class Tool {

public static float getTextViewLength(TextView textView) {
TextPaint paint = textView.getPaint();
return paint.measureText(textView.getText().toString());
}

public static float getTextViewLength(TextView textView, float textSize) {
TextPaint paint = textView.getPaint();
paint.setTextSize(textSize);
return paint.measureText(textView.getText().toString());
}

public static int getScreenWidth(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
return dm.widthPixels;
}
}

最终实现

最后

能看到这的都是神人了

其实,整个文章的难点在于如何设计DynamicLine,刚开始想着很简单
,但是真到你自己去写写,很多问题。比如,如何确定滑动时的DynamicLine位置,以及当一个TextView被选中时,它的字体宽度是变大了,这个时候DynamicLine的起末位置怎么办
等等。不信,大神你撸一把试试。

-------------本文结束感谢您的阅读-------------
如果你喜欢这篇文章,可以请我喝一杯 Coffee ~