史上最巧妙自定義tablayout指示器

國際慣例,無圖無真相

device-2017-06-07-071140.png

首先我們先過幾個概念,老手這個請自行跳過律秃。

Android的View顯示在界面上需要三步:測量,定位和繪制治唤。

第一步:測量棒动,View的measure方法

這個方法用來測量View顯示的寬高值。這個寬高值是基于View自身寬高宾添,再加上父View的約束得到的船惨。這個約束使用MeasureSpec類傳遞。

@Override
        protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            }

measure方法是final型的缕陕,子類需要重寫的是onMeasure方法粱锐,這里做了兩件事:真正測量寬高值;保存寬高值扛邑。保存操作是調(diào)用setMeasuredDimension方法怜浅,以供后續(xù)步驟使用。
在View的onMeasure方法中蔬崩,使用getDefaultSize方法獲取在具體size和具體measureSpec下調(diào)整后的最終size恶座,并調(diào)用setMeasuredDimension方法保存。
要是有子View沥阳,需要在onMeasure方法中調(diào)用ViewGroup的measureChild方法跨琳。

第二步:定位,View的layout方法

這個方法用來將View(子View)放在確定的位置桐罕。這時View的左上右下的坐標(biāo)值就存在了脉让。
這個方法是final型的桂敛,子類需要重寫的是onLayout方法,

    protected void onLayout(boolean changed, int l, int t, int r, int b) {
            super.onLayout(changed, l, t, r, b);
}

layout方法最初傳入的右下參數(shù)溅潜,就是measure方法中保存的值术唬。
要是有子View,需要在onLayout方法中調(diào)用子View的layout方法伟恶。

第三步:繪制碴开,View的draw方法

這個方法用來繪制view內(nèi)容毅该。包括自身及子View博秫。
draw方法中將繪制過程分六步:背景;陰影層(if nesessary)眶掌;view自身挡育;子View;陰影邊緣(if nesessary)朴爬;裝飾部分(如前景色即寒,滾動條)。

其中召噩,view自身繪制在onDraw方法中實現(xiàn)母赵,子View繪制在dispatchDraw方法中實現(xiàn)。View類中這兩個方法均是空實現(xiàn)具滴,ViewGroup類中僅對dispatchView添加具體實現(xiàn)凹嘲,即依次調(diào)用子View的draw方法(用drawChild方法封裝)。
draw方法是final型的构韵,子類需要重寫的是onDraw方法周蹭,來完成自身的繪制。
要是有子View疲恢,一般直接使用ViewGroup的dispatchDraw方法就可以了凶朗,不需要重寫。

private class SlidingTabStrip extends LinearLayout {
    private int mSelectedIndicatorHeight;
    private final Paint mSelectedIndicatorPaint;

    int mSelectedPosition = -1;
    float mSelectionOffset;

    private int mIndicatorLeft = -1;
    private int mIndicatorRight = -1;

    private ValueAnimatorCompat mIndicatorAnimator;

    SlidingTabStrip(Context context) {
        super(context);
        setWillNotDraw(false);
        mSelectedIndicatorPaint = new Paint();
    }
    //省略部分代碼

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //省略部分代碼
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        //省略部分代碼
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        //這里是關(guān)鍵代碼显拳,畫指示線棚愤,那么我么也可以利用底下的幾個參數(shù)畫整個背景,后面需要用到
        // Thick colored underline below the current selection
        if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
            canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
                    mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
        }
    }
}

這里我們知道private class SlidingTabStrip 是私有方法杂数,而且 private final SlidingTabStrip mTabStrip; 是final宛畦,我們沒有通過重寫繼承更改。那么要怎么去修改呢耍休?有些人可能會想到反射刃永,但是反射怎么才能最簡單的修改呢?

如果對反射不是很熟悉的羊精,可以參考下這一篇博客 你必須掌的握反射用法

上面的關(guān)鍵代碼主要是其draw(Canvas canvas)方法斯够,我們發(fā)現(xiàn)他傳入的參數(shù)是canvas 囚玫,我們能否拿到這個對象,然后就可以在外部使用canvas進行畫圖了读规。

思路有了抓督,怎么實現(xiàn)呢?

通過源碼我們可以知道束亏,TabLayout 添加Tab最終是添加到我們上面說的mTabStrip里面

private void addTabView(Tab tab) {
        final TabView tabView = tab.mView;
        mTabStrip.addView(tabView, tab.getPosition(), createLayoutParamsForTabs());
    }

而TabLayout的子view就是我們上面說的SlidingTabStrip 铃在,通過mTabLayout.getChildAt(0)獲取。然后我們知道view實現(xiàn)了drawable的回調(diào)接口

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
        }

于是實現(xiàn)自定義drawable代理類如下:

public class ProxyDrawable  extends Drawable {
    View view;
    Paint paint;

    float paddingLeft ;
    float paddingTop;

    public ProxyDrawable(View view) {
        this.view = view;
        paint = new Paint();
        paint.setColor(0xFF6DA9FF);
        float density = view.getResources().getDisplayMetrics().density;
        //這兩個留白可以根據(jù)需求更改
        paddingLeft = 0 * density;
        paddingTop =  5 * density;
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
    //這里通過反射獲取SlidingTabStrip的兩個變量碍遍,源代碼畫的是下劃線定铜,我們現(xiàn)在畫的是帶圓角的矩形
        int mIndicatorLeft = getIntValue("mIndicatorLeft");
        int mIndicatorRight = getIntValue("mIndicatorRight");
        int height = view.getHeight();
        int radius = height / 2;
        if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
            canvas.drawRoundRect(new RectF(mIndicatorLeft + (int)paddingLeft, (int)paddingTop, mIndicatorRight - (int)paddingLeft, height - (int)paddingTop), radius, radius, paint);
        }
    }

    int getIntValue(String name) {
        try {
            Field f = view.getClass().getDeclaredField(name);
            f.setAccessible(true);
            Object obj = f.get(view);
            return (Integer) obj;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return 0;
    }

    @Override
    public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {

    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {

    }

    @Override
    public int getOpacity() {
        return PixelFormat.UNKNOWN;
    }
}

實現(xiàn)代碼如下即可實現(xiàn)指示器修改,存在的問題是系統(tǒng)畫的下劃線仍然存在怕敬,需要在布局將其隱藏

 View view1 = mTabLayout.getChildAt(0);
 view1.setBackgroundDrawable(new ProxyDrawable(view1));

其中app:tabBackground="@null"去除tab點擊的陰影效果
app:tabIndicatorColor="@null"去除tab的下劃線
布局代碼對比如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.philos.tablayoutdrawproxydemo.MainActivity"
    android:orientation="vertical">
    <android.support.design.widget.TabLayout
        android:id="@+id/tab0"
        android:layout_marginBottom="16dp"
        android:background="@color/colorPrimaryDark"
        app:tabBackground="@null"
        app:tabIndicatorColor="@null"
        app:tabSelectedTextColor="@color/colorAccent"
        android:layout_width="match_parent"
        android:layout_height="45dp"/>

    <android.support.design.widget.TabLayout
        android:id="@+id/tab"
        android:layout_marginBottom="16dp"
        android:background="@color/colorPrimaryDark"
        app:tabBackground="@null"
        app:tabSelectedTextColor="@color/colorAccent"
        android:layout_width="match_parent"
        android:layout_height="45dp"/>

    <android.support.design.widget.TabLayout
        android:id="@+id/tab1"
        android:layout_marginBottom="16dp"
        app:tabMode="fixed"
        app:tabMaxWidth="150dp"
        app:tabPadding="8dp"
        android:background="@color/colorPrimaryDark"
        app:tabBackground="@null"
        app:tabIndicatorColor="@null"
        app:tabSelectedTextColor="@color/colorAccent"
        android:layout_width="match_parent"
        android:layout_height="45dp"/>
</LinearLayout>

這樣就巧妙實現(xiàn)了TabLayout的指示器更改揣炕,本來昨晚寫這篇博客的,沒想到我們家喵大人過來霸占了電腦东跪,然后只能早上起來寫了畸陡。。虽填。丁恭。標(biāo)題有點過了,請大家海涵斋日。牲览。。桑驱。竭恬。

附一張我們家起司的萌照

IMG_5197.JPG
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市熬的,隨后出現(xiàn)的幾起案子痊硕,更是在濱河造成了極大的恐慌,老刑警劉巖押框,帶你破解...
    沈念sama閱讀 212,029評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岔绸,死亡現(xiàn)場離奇詭異,居然都是意外死亡橡伞,警方通過查閱死者的電腦和手機盒揉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兑徘,“玉大人刚盈,你說我怎么就攤上這事」夷裕” “怎么了藕漱?”我有些...
    開封第一講書人閱讀 157,570評論 0 348
  • 文/不壞的土叔 我叫張陵欲侮,是天一觀的道長。 經(jīng)常有香客問我肋联,道長威蕉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,535評論 1 284
  • 正文 為了忘掉前任橄仍,我火速辦了婚禮韧涨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘侮繁。我一直安慰自己虑粥,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,650評論 6 386
  • 文/花漫 我一把揭開白布鼎天。 她就那樣靜靜地躺著舀奶,像睡著了一般暑竟。 火紅的嫁衣襯著肌膚如雪斋射。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,850評論 1 290
  • 那天但荤,我揣著相機與錄音罗岖,去河邊找鬼。 笑死腹躁,一個胖子當(dāng)著我的面吹牛桑包,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播纺非,決...
    沈念sama閱讀 39,006評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼哑了,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了烧颖?” 一聲冷哼從身側(cè)響起弱左,我...
    開封第一講書人閱讀 37,747評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎炕淮,沒想到半個月后拆火,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,207評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡涂圆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,536評論 2 327
  • 正文 我和宋清朗相戀三年们镜,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片润歉。...
    茶點故事閱讀 38,683評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡模狭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出踩衩,到底是詐尸還是另有隱情嚼鹉,我是刑警寧澤邪意,帶...
    沈念sama閱讀 34,342評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站反砌,受9級特大地震影響雾鬼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宴树,卻給世界環(huán)境...
    茶點故事閱讀 39,964評論 3 315
  • 文/蒙蒙 一策菜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酒贬,春花似錦又憨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至零如,卻和暖如春躏将,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背考蕾。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評論 1 266
  • 我被黑心中介騙來泰國打工祸憋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人肖卧。 一個月前我還...
    沈念sama閱讀 46,401評論 2 360
  • 正文 我出身青樓蚯窥,卻偏偏與公主長得像,于是被迫代替她去往敵國和親塞帐。 傳聞我的和親對象是個殘疾皇子拦赠,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,566評論 2 349

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