前段時間一直整理java方面的知識了,先過渡一段時間到android上面來,後期仍是會整理java相關的東西,至於整理什麼方面的,還沒想好。好了,先不說廢話了,仍是回到正片上來,說說android中用得比較多的drawable類,drawable類是一個抽象的類,其實咱們日常開發的階段用的就是它的各類子類,好比有ColorDrawable
、BitmapDrawable
等等,後面全部相關的Drawable都會講到。相信你們用Bitmap也是用得比較多的,那他兩有啥區別呢。java
Bitmap是專門存儲圖片的一種形式,是對位圖的每個像素的顏色存儲器,而咱們的顏色值是由ARGB來標示的,所以咱們經常用16進制的6位數表示一個顏色值,而顏色模式通常有下面幾種:android
顏色模式 | 說明 |
---|---|
ARGB8888 | 四通道高精度(32位) |
ARGB4444 | 四通道低精度(24位) |
RGB565 | 三通道(16位) |
Alpha8 | 透明通道(8位) |
因此對於Bitmap的使用是須要指明Bitmap使用的顏色通道模式,通常若是沒有特殊要求最好是選擇三通道的就行。好了,關於Bitmap的說明就這麼多,仍是言歸正傳,說說drawable是怎麼一步步顯示在view上的,下面仍是經過一個簡單的例子,說明drawable的用法:canvas
定義了一個drawable文件:test_back.xml數組
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/colorPrimary" android:state_pressed="true" />
<item android:drawable="@color/colorPrimary" android:state_selected="true" />
<item android:drawable="@color/colorAccent" />
</selector>
複製代碼
接着在activity佈局中使用:緩存
<View
android:id="@+id/view"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="50dp"
android:background="@drawable/test_back" />
複製代碼
相信你們這個代碼很是熟悉了,還有個疑問就是爲何在view設置了setOnClickListener
纔會有view按下的效果呢,因此帶着這些疑問之前看下這些問題,你們都知道view的全部屬性是經過定義在attrs文件中的,而view的attrs的name是View:bash
緊接着第一個屬性就是獲取background屬性: app
@Nullable
public Drawable getDrawableForDensity(@StyleableRes int index, int density) {
//省略了代碼
return mResources.loadDrawable(value, value.resourceId, density, mTheme);
}
複製代碼
能夠看到上面調的是Resource類中的loadDrawable方法:ide
@NonNull
Drawable loadDrawable(@NonNull TypedValue value, int id, int density, @Nullable Theme theme)
throws NotFoundException {
return mResourcesImpl.loadDrawable(this, value, id, density, theme);
}
複製代碼
很簡單一句話,直接調了ResourcesImpl的loadDrawable方法:佈局
@Nullable
Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
int density, @Nullable Resources.Theme theme)
throws NotFoundException {
//省略代碼
try {
final boolean isColorDrawable;
//省略代碼
//若是傳過來的是#開頭的屬性值,直接返回colorDrawable
Drawable dr;
if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else {
//若是不是則調用該方法
dr = loadDrawableForCookie(wrapper, value, id, density);
}
return dr;
} catch (Exception e) {
}
}
複製代碼
上面代碼已經到了最精簡的代碼了,前面一大堆的工做判斷有沒有緩存的drawable,若是有直接返回cachedDrawable
,若是沒有接着判斷是否是以#開頭的clor顏色值,若是是直接返回colorDrawable,先不說colorDrawable,後面會講到,該節只是分析drawable的顯示流程。那我們看下loadDrawableForCookie
:ui
private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value,
int id, int density) {
final String file = value.string.toString();
final Drawable dr;
try {
try {
//注意了若是drawable是一個xml文件定義的走這裏
if (file.endsWith(".xml")) {
final XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
dr = Drawable.createFromXmlForDensity(wrapper, rp, density, null);
rp.close();
} else {
//不然從asset輸入流讀取
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
AssetInputStream ais = (AssetInputStream) is;
dr = decodeImageDrawable(ais, wrapper, value);
}
} finally {
stack.pop();
}
} catch (Exception | StackOverflowError e) {
}
return dr;
}
複製代碼
從上面能夠看到,咱們通常寫的xml都是用Drawable.createFromXmlForDensity方法來獲取的,接着看下該方法:
@NonNull
public static Drawable createFromXmlForDensity(@NonNull Resources r,
@NonNull XmlPullParser parser, int density, @Nullable Theme theme)
throws XmlPullParserException, IOException {
AttributeSet attrs = Xml.asAttributeSet(parser);
//省略了判斷
Drawable drawable = createFromXmlInnerForDensity(r, parser, attrs, density, theme);
return drawable;
}
複製代碼
該方法很簡單,直接是調用了createFromXmlInnerForDensity
方法:
@NonNull
static Drawable createFromXmlInnerForDensity(@NonNull Resources r,
@NonNull XmlPullParser parser, @NonNull AttributeSet attrs, int density,
@Nullable Theme theme) throws XmlPullParserException, IOException {
return r.getDrawableInflater().inflateFromXmlForDensity(parser.getName(), parser, attire
density, theme);
}
複製代碼
能夠看到經過resources.getDrawableInflater().inflateFromXmlForDensity方法返回的drawable對象,能夠直接看下resources.getDrawableInflater()返回的對象:
@NonNull
Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, int density, @Nullable Theme theme)
throws XmlPullParserException, IOException {
//若是xml中根標籤是drawable,那麼直接解析它的class屬性,通常若是是自定義drawable能夠這麼玩
if (name.equals("drawable")) {
name = attrs.getAttributeValue(null, "class");
if (name == null) {
throw new InflateException("<drawable> tag must specify class attribute");
}
}
//此處是關鍵
Drawable drawable = inflateFromTag(name);
if (drawable == null) {
drawable = inflateFromClass(name);
}
drawable.setSrcDensityOverride(density);
//該處也很重要,後面講各類drawable的時候會講到該方法
drawable.inflate(mRes, parser, attrs, theme);
return drawable;
}
複製代碼
上面代碼邏輯很清晰,若是獲取到的標籤是drawable,經過class屬性獲取到drawable對象,若是標籤不是drawable經過inflateFromTag方法獲取:
private Drawable inflateFromTag(@NonNull String name) {
switch (name) {
case "selector":
return new StateListDrawable();
case "animated-selector":
return new AnimatedStateListDrawable();
case "level-list":
return new LevelListDrawable();
case "layer-list":
return new LayerDrawable();
case "transition":
return new TransitionDrawable();
case "ripple":
return new RippleDrawable();
case "adaptive-icon":
return new AdaptiveIconDrawable();
case "color":
return new ColorDrawable();
case "shape":
return new GradientDrawable();
case "vector":
return new VectorDrawable();
case "animated-vector":
return new AnimatedVectorDrawable();
case "scale":
return new ScaleDrawable();
case "clip":
return new ClipDrawable();
case "rotate":
return new RotateDrawable();
case "animated-rotate":
return new AnimatedRotateDrawable();
case "animation-list":
return new AnimationDrawable();
case "inset":
return new InsetDrawable();
case "bitmap":
return new BitmapDrawable();
case "nine-patch":
return new NinePatchDrawable();
case "animated-image":
return new AnimatedImageDrawable();
default:
return null;
}
}
複製代碼
我去,這也太明顯了吧,整個drawable的子類都放出來了,像不像工廠方法呢,是的,沒錯,DrawableInflater類就是Drawable的工廠類,經過標籤的name,返回不一樣的drawable。若是標籤的名字是drawable的話,會調用inflateFromClass方法來生成drawable的:
@NonNull
private Drawable inflateFromClass(@NonNull String className) {
try {
Constructor<? extends Drawable> constructor;
synchronized (CONSTRUCTOR_MAP) {
constructor = CONSTRUCTOR_MAP.get(className);
if (constructor == null) {
final Class<? extends Drawable> clazz =
mClassLoader.loadClass(className).asSubclass(Drawable.class);
constructor = clazz.getConstructor();
CONSTRUCTOR_MAP.put(className, constructor);
}
}
return constructor.newInstance();
} catch (NoSuchMethodException e) {
final InflateException ie = new InflateException(
"Error inflating class " + className);
ie.initCause(e);
throw ie;
} catch (ClassCastException e) {
// If loaded class is not a Drawable subclass.
final InflateException ie = new InflateException(
"Class is not a Drawable " + className);
ie.initCause(e);
throw ie;
} catch (ClassNotFoundException e) {
// If loadClass fails, we should propagate the exception.
final InflateException ie = new InflateException(
"Class not found " + className);
ie.initCause(e);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(
"Error inflating class " + className);
ie.initCause(e);
throw ie;
}
}
複製代碼
這裏就不作過多的解釋了,經過反射生成Drawable對象的,關於反射你們能夠看我前面寫的java反射整理 緊接着調用了drawable.inflate方法。該方法對於後面分析各類Drawable有很大的幫助。
xml中定義的drawable是經過DrawableInflater生成不一樣的drawable,若是標籤直接定義drawable,去解析class屬性,class屬性是drawable的全類名;不然解析相應的標籤生成不一樣的drawable,好比
ColorDrawable
、StateListDrawable
、GradientDrawable
。
經過上面的分析,例子中的Drawable實際是一個StateListDrawable
,下面一塊兒來看看他是如何顯示到view上的
上面已經講了drawable是如何經過xml生成drawable,下面要將的是Drawable是怎麼做用到view上,在前面說到view中經過TypeArray.getDrawable獲取到Background是一個StateListDrawable,後面在view四個參數的構造方法中設置了background:
if (background != null) {
setBackground(background);
}
複製代碼
接着調用了下面該方法:
public void setBackground(Drawable background) {
//noinspection deprecation
setBackgroundDrawable(background);
}
複製代碼
@Deprecated
public void setBackgroundDrawable(Drawable background) {
computeOpaqueFlags();
if (background == mBackground) {
return;
}
boolean requestLayout = false;
mBackgroundResource = 0;
//1.銷燬以前用到的drawable
if (mBackground != null) {
if (isAttachedToWindow()) {
mBackground.setVisible(false, false);
}
mBackground.setCallback(null);
unscheduleDrawable(mBackground);
}
if (background != null) {
//2.須要刷新view的位置時候
if (mBackground == null
|| mBackground.getMinimumHeight() != background.getMinimumHeight()
|| mBackground.getMinimumWidth() != background.getMinimumWidth()) {
requestLayout = true;
}
mBackground = background;
if (background.isStateful()) {
//3.getDrawableState主要是獲取到drawable狀態的全集
background.setState(getDrawableState());
}
if (isAttachedToWindow()) {
background.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
}
//添加着色的代碼
applyBackgroundTint();
// 對當前的drawable設置回調
background.setCallback(this);
} else {
//若是當前background爲空,須要從新更新view在頁面上的位置
mBackground = null;
requestLayout = true;
}
computeOpaqueFlags();
if (requestLayout) {
requestLayout();
}
mBackgroundSizeChanged = true;
//從新走繪製
invalidate(true);
invalidateOutline();
}
複製代碼
能夠看到上面先是對以前的background進行銷燬,調用了drawable.setCallback(null),能夠看到將當前view實例傳給了drawable對象:
public final void setCallback(@Nullable Callback cb) {
mCallback = cb != null ? new WeakReference<>(cb) : null;
}
複製代碼
此處看到了沒,將view實例經過弱引用包裝起來了,防止drawable長時間不釋放view實例,因此在不用drawable的時候務必調用下setCallback(null)防止內存泄漏
緊接着註釋三處經過drawable.isStateful來判斷須要給drawale加各類狀態不,在drawable默認中實現了isStateful
方法:
/**
* 該drawable是否根據state來更改樣式
*
*/
public boolean isStateful() {
return false;
}
複製代碼
咋們看下ColorDrawable和StateListDrawable下是怎麼實現該方法的:
//stateLIstDrawable直接返回true,說明它是根據狀態改變樣式的drawable
@Override
public boolean isStateful() {
return true;
}
複製代碼
//colorDrawable會根據mColorState.mTint.isStateful()來判斷是否是根據狀態來變樣式
//mColorState.mTint是colorStateLIst對象
@Override
public boolean isStateful() {
return mColorState.mTint != null && mColorState.mTint.isStateful();
}
複製代碼
@Override
public boolean isStateful() {
return mStateSpecs.length >= 1 && mStateSpecs[0].length > 0;
}
複製代碼
判斷mStateSpecs長度大於1,而且第一個length大於0
public ColorStateList(int[][] states, @ColorInt int[] colors) {
mStateSpecs = states;
mColors = colors;
onColorsChanged();
}
複製代碼
mStateSpecs數組是根據states二維數組傳過來的,這裏寫一個簡單的例子來看看isStateful
方法的說明:
int pressed = Color.RED;
int focused = Color.RED;
int normal = Color.BLACK;
int unable = Color.GRAY;
//顏色數組值要和狀態值對應上
int[] colors = new int[]{pressed, focused, normal, focused, unable, normal}
int[][] states = new int[6][];
//定義6個狀態的數組
states[0] = new int[]{android.R.attr.state_pressed, android.R.attr.state_en
states[1] = new int[]{android.R.attr.state_enabled, android.R.attr.state_fo
states[2] = new int[]{android.R.attr.state_enabled};
states[3] = new int[]{android.R.attr.state_focused};
states[4] = new int[]{android.R.attr.state_window_focused};
states[5] = new int[]{};
ColorStateList colorStateList = new ColorStateList(states, colors);
ColorDrawable colorDrawable = new ColorDrawable();
//能夠經過該方法設置colorStateList,先只須要知道用就行,後面會講到
colorDrawable.setTintMode(PorterDuff.Mode.ADD);
colorDrawable.setTintList(colorStateList);
View test = findViewById(R.id.test);
test.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
test.setBackground(colorDrawable);
boolean stateful = colorDrawable.isStateful();
Log.d(TAG, "stateful:" + stateful);
複製代碼
此時獲取到的stateful是true,再來看不加狀態值的時候,代碼改爲以下:
ColorDrawable colorDrawable = new ColorDrawable(Color.BLACK);
View test = findViewById(R.id.test);
test.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
test.setBackground(colorDrawable);
boolean stateful = colorDrawable.isStateful();
Log.d(TAG, "stateful:" + stateful);
複製代碼
看到上面獲取的stateful爲false,由於此時咱們沒有傳state的數組,因此驗證了上面的代碼。關於isStateful方法說明到這裏,下面繼續回到 setBackgroundDrawable
方法的background.setState(getDrawableState())
這一行,你們能夠看下drawable.setState方法:
public boolean setState(@NonNull final int[] stateSet) {
if (!Arrays.equals(mStateSet, stateSet)) {
mStateSet = stateSet;
return onStateChange(stateSet);
}
return false;
}
複製代碼
該方法表示當前drawable是什麼狀態的,若是狀態不同,則會觸發到onStateChange
方法,drawable默認的onStateChange
是一個空的實現,所以須要子類本身實現,你們這會只須要知道這麼個流程,後面會仔細介紹drawable的子類時候再講該方法,繼續看view的getDrawableState
方法:
public final int[] getDrawableState() {
//若是已經獲取過mDrawableState的狀態值,而且mPrivateFlags等於標誌
if ((mDrawableState != null) && ((mPrivateFlags & PFLAG_DRAWABLE_STATE_DIRTY) == 0)) {
return mDrawableState;
} else {
mDrawableState = onCreateDrawableState(0);
mPrivateFlags &= ~PFLAG_DRAWABLE_STATE_DIRTY;
return mDrawableState;
}
}
複製代碼
剛開始mDrawableState
變量爲空,那麼此時經過onCreateDrawableState
方法來獲取mDrawableState
:
protected int[] onCreateDrawableState(int extraSpace) {
int[] drawableState;
int privateFlags = mPrivateFlags;
int viewStateIndex = 0;
//下面這些操做都是經過當前view的狀態來給viewStateIndex設置不一樣狀態的值
if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= StateSet.VIEW_STATE_PRESSED
if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= StateSet.VIEW_STATE_ENAB
if (isFocused()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED;
if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= StateSet.VIEW_STATE_SELECT
if (hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_WINDOW_FOCUSED;
if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= StateSet.VIEW_STATE_ACTIV
if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested &&
ThreadedRenderer.isAvailable()) {
viewStateIndex |= StateSet.VIEW_STATE_ACCELERATED;
}
if ((privateFlags & PFLAG_HOVERED) != 0) viewStateIndex |= StateSet.VIEW_STATE_HOVERED
final int privateFlags2 = mPrivateFlags2;
if ((privateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0) {
viewStateIndex |= StateSet.VIEW_STATE_DRAG_CAN_ACCEPT;
}
if ((privateFlags2 & PFLAG2_DRAG_HOVERED) != 0) {
viewStateIndex |= StateSet.VIEW_STATE_DRAG_HOVERED;
}
//獲取到上面的不一樣狀態的值後,獲取到當前drawable的狀態,以此來設置drawable在不一樣狀態下的樣式
drawableState = StateSet.get(viewStateIndex);
if (extraSpace == 0) {
return drawableState;
}
//省略代碼
}
複製代碼
上面的操做是經過全局mPrivateFlags標誌獲取到各類狀態下的索引,而且位運算或放到viewStateIndex中,最後經過StateSet.get(viewStateIndex)
賦值給drawableState。關於StateSet.get(viewStateIndex)
獲取到的都是R.attr.state_****
,關於經過view的狀態設置drawable的狀態就是這麼來的,下面繼續回到view的setBackgroundDrawable
方法,接着會調用applyBackgroundTint
方法,
private void applyBackgroundTint() {
if (mBackground != null && mBackgroundTint != null) {
//若是view設置了backgroundTint屬性,那麼mBackgroundTint就不會爲空
final TintInfo tintInfo = mBackgroundTint;
if (tintInfo.mHasTintList || tintInfo.mHasTintMode) {
mBackground = mBackground.mutate();
if (tintInfo.mHasTintList) {
//傳入colorStateList實現效果
mBackground.setTintList(tintInfo.mTintList);
}
if (tintInfo.mHasTintMode) {
//設置tintMode
mBackground.setTintMode(tintInfo.mTintMode);
}
if (mBackground.isStateful()) {
mBackground.setState(getDrawableState());
}
}
}
}
複製代碼
看到上面的代碼是否是很熟悉上面事例中setTintList
和setTintMode
的使用,沒錯上面代碼也能夠經過xml來實現setTintList的效果:
//定義一個drawable文件,實質是一個StateListDrawable,名字叫test.xml:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/holo_red_dark" android:state_enabled="true" android:state_pressed="true" />
<item android:drawable="@android:color/holo_red_dark" android:state_enabled="true" android:state_focused="true" />
</selector>
複製代碼
在相應的view佈局上使用以下:
<View
android:id="@+id/test"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/black"
android:backgroundTint="@drawable/test"
android:backgroundTintMode="add"/>
複製代碼
簡單來講,backgroundTint是往background上着色,至關於網上塗層,後面細講drawable的時候,會說到他的各類子類的狀況是如何控制backgroundTint的。繼續回到view的setBackgroundDrawable
方法上來,上面說完了applyBackgroundTint
方法,後面緊接着到了background.setCallback(this)
,此處是控制drawable繪製的回調,將view當前的實例傳給drawable。此時drawable的setState、setTintList、setCallback都已經完成了,緊接着就是繪製了,所以在最後調用了invalidate(true)
,最終會觸發view的從新繪製了。
你們知道view的繪製方法在draw---->onDraw,draw裏面作的就是系統的一些繪製,而onDraw是view的子類繪製的方法,因此咱們通常寫的繪製都是在onDraw裏面,下面來看看draw裏面繪製background,能夠看到draw裏面有一句drawBackground(canvas):
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
//設置drawable的大小,這個很重要
setBackgroundBounds();
//省略代碼
final int scrollX = mScrollX;
final int scrollY = mScrollY;
//最終會走drawable.draw方法,所以drawable的繪製,實際上是drawable本身完成的
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
複製代碼
上面繪製drawable很簡單,首先調用了setBackgroundBounds
方法,而後調用了drawable.draw(canvas),最終的繪製仍是交給了drawable本身去繪製。下面看下setBackgroundBounds
方法:
void setBackgroundBounds() {
if (mBackgroundSizeChanged && mBackground != null) {
//設置drawable的大小,大小其實就是view的大小
mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
rebuildOutline();
}
}
複製代碼
能夠看到setBackgroundBounds實際上是調用了mBackground.setBounds方法,在drawable的setBounds方法中只是設置了全局的mBounds變量:
public void setBounds(int left, int top, int right, int bottom) {
Rect oldBounds = mBounds;
if (oldBounds == ZERO_BOUNDS_RECT) {
oldBounds = mBounds = new Rect();
}
if (oldBounds.left != left || oldBounds.top != top ||
oldBounds.right != right || oldBounds.bottom != bottom) {
if (!oldBounds.isEmpty()) {
// first invalidate the previous bounds
invalidateSelf();
}
mBounds.set(left, top, right, bottom);
onBoundsChange(mBounds);
}
}
複製代碼
因此說drawable中只是設置了個全局變量mBounds,供子類使用,因此從這裏看得出來,若是要使用drawable必須調用drawable的setBounds方法,要否則在繪製drawable的時候顯示不出來。到最後就是background的繪製了,也就是drawable.draw(canvas)方法,能夠看出來drawable是不負責繪製的,繪製工做都交給了本身的子類。
view的點擊都是在ontouchEvent裏面,咱們主要看關鍵點就行
//手指在擡起的時候,而且mPrivateFlags等於PFLAG_PRESSED標誌
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
複製代碼
這裏能夠看出來,若是手擡起來了,而且狀態是PFLAG_PRESSED
,調用了setPressed(false),看看該方法:
public void setPressed(boolean pressed) {
//這句標明在按下的時候needsRefresh=true,由於剛開始mPrivateFlags&PFLAG_PRESSED!=PFLAG_PRESSED
//所以後面爲false,當pressed=true的時候,那麼needsRefresh=true,反之pressed=false的時候,
//所以此時mPrivateFlags&PFLAG_PRESSED=PFLAG_PRESSED,所以needsRefresh仍是爲true
//因此在按下和鬆開view的時候needsRefresh都爲true
final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);
//若是按下了mPrivateFlags|PFLAG_PRESSED,那此時mPrivateFlags=PFLAG_PRESSED了
//反之鬆開的時候mPrivateFlags!=PFLAG_PRESSED了
if (pressed) {
mPrivateFlags |= PFLAG_PRESSED;
} else {
mPrivateFlags &= ~PFLAG_PRESSED;
}
//若是needsRefresh爲true,此時須要刷新drawable
if (needsRefresh) {
refreshDrawableState();
}
dispatchSetPressed(pressed);
}
複製代碼
註釋寫得很清楚了,擡起和按下view都會觸發refreshDrawableState
方法,而且在按下的時候mPrivateFlags= PFLAG_PRESSED,擡起的時候mPrivateFlags!= PFLAG_PRESSED,下面來看看refreshDrawableState方法:
public void refreshDrawableState() {
mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
drawableStateChanged();
ViewParent parent = mParent;
if (parent != null) {
parent.childDrawableStateChanged(this);
}
}
複製代碼
接着看drawableStateChanged方法:
protected void drawableStateChanged() {
//能夠看到先去根據getDrawableState獲取到drawable的狀態,而後調用了各個drawable的setState方法
final int[] state = getDrawableState();
boolean changed = false;
//首先就是設置background的state
final Drawable bg = mBackground;
if (bg != null && bg.isStateful()) {
changed |= bg.setState(state);
}
//省略代碼
//foreground的state,後面會講到foreground水波效果是怎麼造成的
final Drawable fg = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (fg != null && fg.isStateful()) {
changed |= fg.setState(state);
}
//省略代碼
//最後刷新view來調用view的draw方法,最後調用drawable的draw方法
if (changed) {
invalidate();
}
}
複製代碼
前面講過,getDrawableState
方法會根據當前view的狀態的flag值,去StateSet類中去找各個狀態下對應的drawable的state,找到後,調用drawable的setState方法,在setState中會經過mStateSet
和傳進來的stateSet
對比,若是二者相等,那麼觸發drawable的onStateChange
方法,後面講drawable的時候細講,先明白是設置state就行。上面能夠看到foreground設置背景也是這麼幹的,後面會介紹foreground是怎麼來的。最後調用了invalidate方法,從新繪製drawable。上面在整個ontouchEvent的up事件中調用了setPress(false),那何時調用的setPress(true)的呢,很簡單,在view的down事件中觸發的,回到ontouchEvent裏面,在裏面有這麼一句if (clickable || (viewFlags & TOOLTIP) == TOOLTIP)
也就是說只有在clickable爲true的狀況下,drawable點擊纔有效果,因此在開篇的例子中,只有設置了view的監聽器,drawable點擊樣式才起效果。關於view的事件傳遞能夠看我以前寫的該篇android中view事件傳遞,在view的down事件中有這麼一句setPressed(true, x, y)
,也就是會觸發drawable按下的時候樣式。 在上面咱們提到過drawable.,setbackCall(this),這句代碼,此處是把view的事例傳給了drawable,在drawale的invalidateSelf
方法中會回調到view:
//該方法是drawale的重繪方法,回調到view的invalidateDrawable方法
public void invalidateSelf() {
final Callback callback = getCallback();
if (callback != null) {
callback.invalidateDrawable(this);
}
}
複製代碼
看下view中回調的invalidateDrawable方法:
@Override
public void invalidateDrawable(@NonNull Drawable drawable) {
if (verifyDrawable(drawable)) {
final Rect dirty = drawable.getDirtyBounds();
final int scrollX = mScrollX;
final int scrollY = mScrollY;
invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY);
rebuildOutline();
}
}
複製代碼
看到了沒,實際上調用了view的重繪,而view的重繪又會調用drawable的draw方法,所以上面的drawable.setbackCall(this)
,最終是繪製本身。好了,介紹到這裏,drawable顯示到view上基本梳理完了,後面還會介紹foreground
是怎麼有波紋點擊效果,以及介紹drawable在xml中定義的state_****是如何加載到drawale的state上來的,這個就涉及到xml解析了,其實跟xml佈局的解析是差很少的,後面還會介紹各類drawable的使用,以及如何自定義一個drawable。
DrawableInflater
來完成的background
,foreground
其實都是對應的drawable,若是咱們設置的background
只是一個color,那麼此時background就是一個colorDrawable
,若是是一個selector標籤的話,此時是一個StateListDrawable
。drawable.setTintList
是設置drawable的着色,它的實質是給paint設置setColorFilter
,關於ColorFilter
有三個子類,後面會講到drawable.setTintMode
是設置上面着色的模式,關於模式後面也會講到drawable.setCallback(this)
是將view的事例傳到drawable的WeakReference
弱引用包裝起來了,在invalidateSelf
方法中會觸發callback的invalidateDrawable
方法,從而回調到view的重繪,而最終會從新調用了drawable的draw方法,從而達到drawble的從新繪製。關於drawable的打造能夠看我另一篇實戰仿蘋果版小黃車(ofo)app主頁菜單效果