自定義view實現(xiàn)TabLayout效果(其中滑塊根據(jù)Tab的字長度改變而改變)

最近為了滿足公司ui的要求饱苟,TabLayout已經(jīng)滿足不了孩擂,自定義View實現(xiàn)!話不多說直接上代碼

1> TabLayoutView這個是最核心的view

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RelativeLayout;

import com.zcedu.zhuchengjiaoyu.R;

/**
 * 自定義控件實現(xiàn) TabLayout效果
 */
public class TabLayoutView extends RelativeLayout {

    private static final String TAG = "TabLayoutView";

    private HorizontalScrollView hs_indicator;
    private RadioGroup rg_indicator;
    private View iv_indicator, relativeLayout_indicator;//滑塊,整個容器
    private Context mContext;
    /**
     * radioButton 狀態(tài)顏色選擇集合
     **/
    private ColorStateList colorStateList;


    //新的屬性
    private int tabWidth, tabWidthInit;
    private int tabTextSize;
    private int tabIndicatorColor;
    private int tabIndicatorHeight;
    private int tabIndicatorWidth;
    private ViewPager viewPager;
    private int tabPadding, tabIndicatorMarginBottom, stopTablNumber = -1;
    private View view;

    public TabLayoutView(Context context) {
        super(context);
        init(context, null, 0, 0);
    }

    public TabLayoutView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs, 0, 0);
    }

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

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public TabLayoutView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context, attrs, defStyleAttr, 0);
    }


    private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        mContext = context;

        TypedArray array = mContext.getTheme().obtainStyledAttributes(new int[]{
                android.R.attr.colorPrimary,
                android.R.attr.colorPrimaryDark,
                android.R.attr.colorAccent,
        });
        int colorPrimary = array.getColor(0, 0);

        final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TabLayoutView, defStyleAttr, defStyleRes);

        //tabItem的屬性初始化
        tabWidthInit = typedArray.getDimensionPixelSize(R.styleable.TabLayoutView_tabWidth, 0);//tab寬度箱熬,不設(shè)置就是wrap_content
        tabTextSize = typedArray.getDimensionPixelSize(R.styleable.TabLayoutView_tabTextSize, sp2px(mContext, 15f));//tab字體大小,默認(rèn)15sp
        tabTextSize = px2sp(mContext, tabTextSize);
        int tabTextColor = typedArray.getColor(R.styleable.TabLayoutView_tabTextColor, Color.BLACK);//tab顏色
        int tabSelectedTextColor = typedArray.getColor(R.styleable.TabLayoutView_tabSelectedTextColor, colorPrimary);//tab選中的顏色
        colorStateList = createColorStateList(tabSelectedTextColor, tabTextColor);

        //tabIndicator的屬性初始化
        tabIndicatorColor = typedArray.getColor(R.styleable.TabLayoutView_tabIndicatorColor, colorPrimary);//滑塊的顏色
        tabIndicatorHeight = typedArray.getDimensionPixelSize(R.styleable.TabLayoutView_tabIndicatorHeight, 3);//滑塊的高度
        tabIndicatorWidth = typedArray.getDimensionPixelSize(R.styleable.TabLayoutView_tabIndicatorWidth, 0);//滑塊的寬度肋殴,不傳就和tab一樣寬
        tabPadding = typedArray.getDimensionPixelSize(R.styleable.TabLayoutView_tabPadding, dip2px(mContext, 26f));//如果tab沒有設(shè)置Padding,默認(rèn)26dp
        tabIndicatorMarginBottom = typedArray.getDimensionPixelSize(R.styleable.TabLayoutView_tabIndicatorMarginBottom, dip2px(mContext, 4f));//如果滑塊沒有設(shè)置MarginBottom坦弟,默認(rèn)4dp
        typedArray.recycle();

        //把我們的布局添加到當(dāng)前控件中
        view = View.inflate(context, R.layout.item_tab_radiogroup, null);
        hs_indicator = (HorizontalScrollView) view.findViewById(R.id.hs_indicator);
        rg_indicator = (RadioGroup) view.findViewById(R.id.rg_indicator);
        iv_indicator = view.findViewById(R.id.iv_indicator);
        relativeLayout_indicator = view.findViewById(R.id.relativeLayout_indicator);
        addView(view);


        rg_indicator.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {

                if (rg_indicator.getChildAt(checkedId) != null) {

                    RadioButton radioButton1 = (RadioButton) rg_indicator
                            .getChildAt(checkedId);

                    Resources resources = mContext.getResources();
                    DisplayMetrics dm = resources.getDisplayMetrics();
                    int width = dm.widthPixels;//獲取屏幕寬度

                    stopTablNumber = (stopTablNumber == -1) ? ((width / tabWidth / 2)) : stopTablNumber;//0表示停留在第一個 1就是第二個.... (默認(rèn)停留中間)
                    RadioButton radioButton2 = (RadioButton) rg_indicator
                            .getChildAt(stopTablNumber);
                    if (radioButton1 != null && radioButton2 != null) {
                        hs_indicator.smoothScrollTo(
                                (checkedId > stopTablNumber ? radioButton1.getLeft() : 0)
                                        - radioButton2.getLeft(), 0);
                        if (viewPager != null) {
                            viewPager.setCurrentItem(checkedId);
                        }
                    }
                }

            }
        });

    }

    /**
     * 重新計算繪制后的高度
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         view.setLayoutParams(new RelativeLayout.LayoutParams(widthMeasureSpec, heightMeasureSpec));
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        postInvalidate();
    }

    /**
     * 設(shè)置tab停留的位置(默認(rèn)停留在中間)
     *
     * @param stopTablNumber
     */
    public void setStopTablNumber(int stopTablNumber) {
        this.stopTablNumber = stopTablNumber;
    }

    /**
     * convert px to its equivalent sp
     * <p>
     * 將px轉(zhuǎn)換為sp
     */
    public static int px2sp(Context context, float pxValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (pxValue / fontScale + 0.5f);
    }

    /**
     * convert sp to its equivalent px
     * <p>
     * 將sp轉(zhuǎn)換為px
     */
    public static int sp2px(Context context, float spValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }

    /**
     * 對TextView設(shè)置不同狀態(tài)時其文字顯示顏色
     */
    private ColorStateList createColorStateList(int check, int normal) {
        int[] colors = new int[]{check, normal};
        int[][] states = new int[2][];
        states[0] = new int[]{android.R.attr.state_checked};
        states[1] = new int[]{};
        ColorStateList colorList = new ColorStateList(states, colors);
        return colorList;
    }

    /**
     * 讓我們的自定義控件和viewpager相關(guān)聯(lián)
     */
    public void setupWithViewPager(@Nullable ViewPager viewPager) {

        this.viewPager = viewPager;
        PagerAdapter pagerAdapter = viewPager.getAdapter();
        rg_indicator.removeAllViews();
        for (int position = 0; position < pagerAdapter.getCount(); position++) {
            RadioButton rb = new RadioButton(mContext);
            rb.setText(pagerAdapter.getPageTitle(position));
            rb.setId(position);

            int content = tabWidthInit == 0 ? RadioGroup.LayoutParams.WRAP_CONTENT : tabWidthInit;//判斷是否有設(shè)置护锤,沒有就是wrap_content
            RadioGroup.LayoutParams layoutParam = new RadioGroup.LayoutParams(
                    new RadioGroup.LayoutParams(content, RadioGroup.LayoutParams.MATCH_PARENT));
            rb.setLayoutParams(layoutParam);
            rb.setTextSize(tabTextSize);
            rb.setTextColor(colorStateList);
            rb.setGravity(Gravity.CENTER_VERTICAL);
            rb.setBackgroundColor(Color.TRANSPARENT);
            rb.setButtonDrawable(android.R.color.transparent);
            //設(shè)置padding
            rb.setPadding(tabPadding, 0, tabPadding, 0);
            rb.setTag(tabPadding);
            if (position == 0) {//默認(rèn)選中第一個
                rb.setChecked(true);
            }
            rg_indicator.addView(rb);
        }

        Resources resources = mContext.getResources();
        DisplayMetrics dm = resources.getDisplayMetrics();
        int width = dm.widthPixels;//獲取屏幕寬度

        if (relativeLayout_indicator.getWidth() < width) {//讓寬度沒有占滿屏幕居中
            FrameLayout.LayoutParams layoutParam2 = new FrameLayout.LayoutParams(width, LayoutParams.MATCH_PARENT);
            relativeLayout_indicator.setLayoutParams(layoutParam2);
        }

        getAlbum(0);//滑塊默認(rèn)是第一個RadioButton的寬度
//        setCurrentSelectItem(0);

        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {

            @Override
            public void onPageSelected(int position) {
                setCurrentSelectItem(position);
            }

            @Override
            public void onPageScrolled(int arg0, float arg1, int arg2) {
                int s = 0;
                LayoutParams layoutParams = (LayoutParams) iv_indicator.getLayoutParams();
                for (int i = 0; i < arg0; i++) {
                    s += rg_indicator.getChildAt(i).getWidth();
                }
                int width = rg_indicator.getChildAt(arg0).getWidth();

                if (arg1 == 0f) { // 停止?jié)L動
                    layoutParams.setMargins(s + tabPadding, 0, 0, tabIndicatorMarginBottom);
                } else {
                    layoutParams.setMargins((int) (s + (width * arg1)) + tabPadding, 0, 0, tabIndicatorMarginBottom);
                }
                iv_indicator.setLayoutParams(layoutParams);
            }

            @Override
            public void onPageScrollStateChanged(int position) {
            }
        });

    }


    /**
     *  根據(jù)手機的分辨率從 dp 的單位 轉(zhuǎn)成為 px(像素)
     */

    public static int dip2px(Context context, float dpValue) {

        final float scale = context.getResources().getDisplayMetrics().density;

        return (int) (dpValue * scale + 0.5f);
    }


    /**
     * 根據(jù)RadioButton  動態(tài)計算滑塊的寬度
     *
     * @param position
     */
    public void getAlbum(int position) {
        RadioButton rb = (RadioButton) rg_indicator.getChildAt(position);
        if (rb == null) {
            return;
        }
        //獲取字的寬度
        CharSequence text = rb.getText();
        TextPaint paint = rb.getPaint();
        int tabWidth2 = (int) (paint.measureText((String) text) + 0.5);
        tabWidth = tabWidth2 + 2 * tabPadding;

        LayoutParams indicator_LayoutParams = (LayoutParams) iv_indicator.getLayoutParams();
        indicator_LayoutParams.height = tabIndicatorHeight;//設(shè)置滑塊的高度
        indicator_LayoutParams.width = tabIndicatorWidth == 0 ? tabWidth2 : tabIndicatorWidth;//設(shè)置滑塊的寬度(不傳就和上邊tab一樣長)
        indicator_LayoutParams.setMargins(tabPadding, 0, 0, tabIndicatorMarginBottom);//設(shè)置滑塊的Margins 為了和RadioButton對齊
        iv_indicator.setLayoutParams(indicator_LayoutParams);
        iv_indicator.setBackgroundColor(tabIndicatorColor);//繪制滑塊的顏色
        postInvalidate();//重新繪制界面

    }

    /**
     * 設(shè)置當(dāng)前選中條目
     *
     * @param currentPosition
     */
    private void setCurrentSelectItem(int currentPosition) {
        getAlbum(currentPosition);//每次滑動就要算滑塊的寬度

        RadioButton radioButton = ((RadioButton) rg_indicator
                .getChildAt(currentPosition));
        if (radioButton != null)
            radioButton.performClick();
    }

    /**
     * 設(shè)置當(dāng)前選中條目
     *
     * @param currentPosition
     */
    boolean isSet = false;

    public void setCurrent(int currentPosition) {
        getAlbum(currentPosition);//每次滑動就要算滑塊的寬度

        final RadioButton radioButton = ((RadioButton) rg_indicator
                .getChildAt(currentPosition));
        if (radioButton != null) {
            //view加載完成時回調(diào)
            view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    if (!isSet) {
                        radioButton.performClick();
                    }
                    isSet = true;
                }
            });
        }
    }

    /**
     * 獲取當(dāng)前選中條目的position
     *
     * @return
     */
    public int getCurrentSelectPosition() {
        int currentIdPosition = rg_indicator.getCheckedRadioButtonId();
        return currentIdPosition;
    }


}

2> item_tab_radiogroup.xml 由于我不知道代碼怎么貼出來所以只有貼圖了(最外層就一個HorizontalScrollView)

image
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/hs_indicator"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="none">


    <RelativeLayout
        android:id="@+id/relativeLayout_indicator"
        android:layout_width="wrap_content"
        android:layout_height="53dp"
        android:gravity="center">

        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center_horizontal">

            <RadioGroup
                android:id="@+id/rg_indicator"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:orientation="horizontal" />

            <View
                android:id="@+id/iv_indicator"
                android:layout_width="50dp"
                android:layout_height="0dp"
                android:layout_alignParentBottom="true" />

        </RelativeLayout>
    </RelativeLayout>

</HorizontalScrollView>

attrs.xml的代碼

image
   <declare-styleable name="TabLayoutView">
        <attr name="tabTextColor" format="color" />
        <attr name="tabSelectedTextColor" format="color" />
        <attr name="tabWidth" format="dimension" />
        <attr name="tabTextSize" format="dimension" />
        <attr name="tabIndicatorColor" format="color" />
        <attr name="tabIndicatorHeight" format="dimension" />
        <attr name="tabIndicatorWidth" format="dimension" />
        <attr name="tabPadding" format="dimension" />
        <attr name="tabIndicatorMarginBottom" format="dimension" />
    </declare-styleable>

最后附上demo的地址鏈接:https://github.com/haijun0124/TabLayoutView

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市酿傍,隨后出現(xiàn)的幾起案子烙懦,更是在濱河造成了極大的恐慌,老刑警劉巖赤炒,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件氯析,死亡現(xiàn)場離奇詭異,居然都是意外死亡莺褒,警方通過查閱死者的電腦和手機掩缓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來遵岩,“玉大人你辣,你說我怎么就攤上這事〕局矗” “怎么了舍哄?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長誊锭。 經(jīng)常有香客問我表悬,道長,這世上最難降的妖魔是什么丧靡? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任蟆沫,我火速辦了婚禮,結(jié)果婚禮上温治,老公的妹妹穿的比我還像新娘饭庞。我一直安慰自己,他們只是感情好罐盔,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布但绕。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捏顺。 梳的紋絲不亂的頭發(fā)上六孵,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音幅骄,去河邊找鬼劫窒。 笑死,一個胖子當(dāng)著我的面吹牛拆座,可吹牛的內(nèi)容都是我干的主巍。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼挪凑,長吁一口氣:“原來是場噩夢啊……” “哼孕索!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起躏碳,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤搞旭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后菇绵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肄渗,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年咬最,在試婚紗的時候發(fā)現(xiàn)自己被綠了翎嫡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡永乌,死狀恐怖惑申,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情铆遭,我是刑警寧澤硝桩,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站枚荣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏啼肩。R本人自食惡果不足惜橄妆,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望祈坠。 院中可真熱鬧害碾,春花似錦、人聲如沸赦拘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至阁猜,卻和暖如春丸逸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背剃袍。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工黄刚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人民效。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓憔维,卻偏偏與公主長得像,于是被迫代替她去往敵國和親畏邢。 傳聞我的和親對象是個殘疾皇子业扒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容