國際慣例,無圖無真相
首先我們先過幾個概念,老手這個請自行跳過律秃。
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)題有點過了,請大家海涵斋日。牲览。。桑驱。竭恬。
附一張我們家起司的萌照