- 獨(dú)家發(fā)布于鴻洋公眾號(hào)
大家好,我是徐愛卿。博客地址:flutterall.com

前言
老早就想寫這篇博客了,demo早就完工了,博客到現(xiàn)在才寫,慚愧。忘記什么時(shí)候開始看微博時(shí),無意中注意到微博的導(dǎo)航條,好有趣,就無聊的拖過來拖過去。不多說,上圖。
文章末尾有福利哦~~

可以看下微博,自己滑動(dòng)試一試。
看到上面的黃色的條條,可長可短,邪惡~~
兩個(gè)TAB頁,關(guān)注和熱門。
幾個(gè)特點(diǎn):
- 關(guān)注頁面滑到頁面的一半寬度以上時(shí)會(huì)自動(dòng)切換到熱門頁面,這是ViewPager的特性。
- 關(guān)鍵看黃條的長度。當(dāng)關(guān)注頁面滑動(dòng)一半時(shí),黃條的長度 到達(dá)“熱門”兩個(gè)字的接近右邊,不會(huì)邊長。反之,亦然。
- 選中的頁面的字體大小與顏色均有變化。
- 黃色線的顏色是漸變的(可以自己認(rèn)真看下微博導(dǎo)航條的顏色)

看下我的實(shí)現(xiàn):


開魯

制作導(dǎo)航條的TextView

我們從上到下看看這個(gè)導(dǎo)航條是怎么制作的。對(duì)于這個(gè),我們可以使用現(xiàn)成的HorizontalScrollView。也就是這個(gè)水平滑動(dòng)的ScollView。使用TextView填充HorizontalScrollView時(shí),會(huì)出現(xiàn)兩種情況:

分析:
- 根據(jù)計(jì)算所有TextView的長度+TextView的左右邊距與屏幕寬度比較,判斷TextView的總長度大于小于屏幕寬度。
- 導(dǎo)航條上面的分類字?jǐn)?shù)較少時(shí),沒有盛滿,我們要首先計(jì)算平分的每個(gè)TextView字體的寬度,然后指定TextView的左右邊距。
- 字?jǐn)?shù)長時(shí),我們?cè)O(shè)置TextView的左右邊距為默認(rèn)邊距
根據(jù)TextView的實(shí)際長度計(jì)算其左右邊距代碼:
/**
*
* @param titleAry TextView的String字符串 “關(guān)注” “推薦”
* @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;
}
}
知道了每個(gè)TextView的左右邊距后(每個(gè)邊距均一致,美觀,并且絕大多數(shù)APP都是這樣設(shè)計(jì)的,UED懂的),然后在一個(gè)個(gè)創(chuàng)建TextView添加到textViewLl中即可。
將所有TextView添加到contentLl中
ViewPagerTitle
/**
* Created by lovexujh on 2017/7/3
*/
public class ViewPagerTitle extends HorizontalScrollView {
private String[] titles;//導(dǎo)航條的字符串:關(guān)注、推薦 、視頻。。。
private ArrayList<TextView> textViews = new ArrayList<>(); //導(dǎo)航條的所有TextView
private DynamicLine dynamicLine;
private ViewPager viewPager;
private MyOnPageChangeListener onPageChangeListener;//ViewPager的滑動(dòng)監(jiān)聽
private int margin;//導(dǎo)航條的每兩個(gè)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);
}
/**
* 這個(gè)方法是來修正TextView的左右邊距的,
* 因?yàn)槊總€(gè)TextView而言 : leftMargins + TextViewLength + rightMargins 這三個(gè)的值要一致,
* 被選中的TExtView的TextViewLength要比默認(rèn)沒有選中的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是左右跑動(dòng)的黃色的線
}
/**
*
* @param titleAry TextView的String字符串 “關(guān)注” “推薦”
* @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
可以看到黃色的線并不是一條線,而是一個(gè)圓角矩形。這就可以使用drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) 這個(gè)API。
關(guān)鍵點(diǎn)在于,黃色圓角矩形的移動(dòng),只要更改圓角矩形的起始X坐標(biāo)與終止X坐標(biāo)。這樣就可以讓黃色條條進(jìn)行移動(dòng)了
來自定義一個(gè)DynamicLine繼承View,代碼及說明如下:
public class DynamicLine extends View {
private float startX, stopX;//的起始X,終止X坐標(biāo)。
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));//設(shè)置畫筆漸變色
}
@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);//圓角矩形的圓角的曲率
}
/**
* 根據(jù)起始、終止坐標(biāo)更新黃色圓角,進(jìn)行重新繪制
* @param startX
* @param stopX
*/
public void updateView(float startX, float stopX) {//
this.startX = startX;
this.stopX = stopX;
invalidate();
}
}
我們把DynamicLine放到activity中添加下面代碼,測試一下,效果:
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);
}
}


后面我們需要知道當(dāng)viewpager切換時(shí)動(dòng)作與DynamicLine的startX與stopX的具體對(duì)應(yīng)關(guān)系??梢允褂肰iewPager的addOnPageChangeListener(OnPageChangeListener listener)方法。
OnPageChangeListener 的實(shí)現(xiàn)
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 當(dāng)前頁面的便宜百分小數(shù) [0, 1)
* @param positionOffsetPixels 當(dāng)前頁面的偏移像素 0 ~ 屏幕寬度
*/
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (lastPosition > position) {//頁面向右滾動(dòng)
/**
* 檔頁面向右滾動(dòng)時(shí),dynamicLine的右邊的stopX位置不變,startX在變化。
*/
dynamicLine.updateView((position + positionOffset) * everyLength + dis + fixLeftDis, (lastPosition + 1) * everyLength - dis);
} else { //頁面向左滾動(dòng)
/**
* 檔頁面向左滾動(dòng)時(shí),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 的幾個(gè)狀態(tài):
* SCROLL_STATE_IDLE 掛起,空閑,頁面處于靜止?fàn)顟B(tài)
* SCROLL_STATE_DRAGGING 拖拽,頁面處于拖拽狀態(tài)
* SCROLL_STATE_SETTLING 設(shè)置,手指滑動(dòng)后當(dāng)手指離開頁面時(shí)
* @param state
*/
@Override
public void onPageScrollStateChanged(int state) {
boolean scrollRight;//頁面向右
if (state == SCROLL_STATE_SETTLING) {
scrollRight = lastPosition < pager.getCurrentItem();
lastPosition = pager.getCurrentItem();
/**
* 下面幾行代碼,解決頁面滑到的TAB頁時(shí)對(duì)應(yīng)的TextView對(duì)應(yīng),TextView處于屏幕外面,
* 這個(gè)時(shí)候就需要將HorizontalScrollView滑動(dòng)到屏幕中間。
*/
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 工具類
/**
* 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;
}
}

最后
其實(shí),整個(gè)文章的難點(diǎn)在于如何設(shè)計(jì)DynamicLine,剛開始想著很簡單 ,但是真到你自己去寫寫,很多問題。比如,如何確定滑動(dòng)時(shí)的DynamicLine位置,以及當(dāng)一個(gè)TextView被選中時(shí),它的字體寬度是變大了,這個(gè)時(shí)候DynamicLine的起末位置怎么辦 等等。不信,大神你擼一把試試。
為了方便使用 ,對(duì)上面的代碼優(yōu)化了,自定義了屬性,上到了GitHub,可以查看最新Dev分支。截止發(fā)稿時(shí),為dev1.0.1 。歡迎大家多多fork多多start,O(∩_∩)O多謝!
更多詳細(xì)使用方式見下面??!
地址:https://github.com/kaina404/ViewPagerFlexTitle/tree/dev-1.0.1
看著下面這個(gè)APP火了,閑著沒事,抓包自己搞了一個(gè),也算是高仿了巴。
福利,歡迎大家多多fork多多start,O(∩_∩)O謝謝。

