×

仿微博导航条

96
徐爱卿
2017.07.12 23:50* 字数 855
前进

前言

老早就想写这篇博客了,demo早就完工了,博客到现在才写,惭愧。忘记什么时候开始看微博时,无意中注意到微博的导航条,好有趣,就无聊的拖过来拖过去。不多说,上图。
文章末尾有福利哦~~

微博导航条

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

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

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

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

看下我的实现:

基础版
升级版

开鲁

导航条的整体构造

制作导航条的TextView

导航条的滑动

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

HorizontalScrollView与TextView

分析:

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

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

/**
     *
     * @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;
        }
    }```
>知道了每个TextView的左右边距后(每个边距均一致,美观,并且绝大多数APP都是这样设计的,UED懂的),然后在一个个创建TextView添加到textViewLl中即可。

**将所有TextView添加到contentLl中**

#####ViewPagerTitle 

```java
/**
 * 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,代码及说明如下:

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中添加下面代码,测试一下,效果:

```java
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](http://upload-images.jianshu.io/upload_images/3884536-b7cf8002944cadfb.gif?imageMogr2/auto-orient/strip)

![有渐变色,有效果。可以,没问题。](http://upload-images.jianshu.io/upload_images/3884536-3f197a576d38f0ef.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
> 后面我们需要知道当viewpager切换时动作与DynamicLine的startX与stopX的具体对应关系。可以使用ViewPager的addOnPageChangeListener(OnPageChangeListener listener)方法。

**OnPageChangeListener 的实现**
```java
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 工具类
```java
/**
 * 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的起末位置怎么办 等等。不信,大神你撸一把试试。
为了方便使用 ,对上面的代码优化了,自定义了属性,上到了GitHub,可以查看最新Dev分支。截止发稿时,为dev1.0.1 。欢迎大家多多fork多多start,O(∩_∩)O多谢!
更多详细使用方式见下面👇!
地址:https://github.com/xujianhui404/ViewPagerFlexTitle/tree/dev-1.0.1

看着下面这个APP火了,闲着没事,抓包自己搞了一个,也算是高仿了巴。

福利,欢迎大家多多fork多多start,O(∩_∩)O谢谢。

Android开发
Web note ad 1