仿微博導(dǎo)航條

  • 獨(dú)家發(fā)布于鴻洋公眾號(hào)

大家好,我是徐愛卿。博客地址:flutterall.com

前進(jìn)

前言

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

微博導(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):

基礎(chǔ)版
升級(jí)版

開魯

導(dǎo)航條的整體構(gòu)造

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

導(dǎo)航條的滑動(dòng)

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

HorizontalScrollView與TextView

分析:

  • 根據(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);
    }
}
DynamicLine
有漸變色,有效果??梢?,沒問題。

后面我們需要知道當(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í)現(xiàn)

最后

其實(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謝謝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,426評(píng)論 4 61
  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標(biāo)簽?zāi)J(rèn)的外補(bǔ)...
    _Yfling閱讀 14,168評(píng)論 1 92
  • WebSocket-Swift Starscream的使用 WebSocket 是 HTML5 一種新的協(xié)議。它實(shí)...
    香橙柚子閱讀 24,781評(píng)論 8 183
  • 你 自己 我 無限可能
    小龍家育嬰閱讀 226評(píng)論 0 0
  • 看到《神奇女俠》的海報(bào)時(shí), 以為就是超人那類相當(dāng)“主旋律”的拯救世人, 體現(xiàn)愛與和平的電影,但是海報(bào)里女主在酷炫中...
    綠洲玫瑰閱讀 410評(píng)論 0 2

友情鏈接更多精彩內(nèi)容