android中foreground水波實現過程分析

xiangcman發表於2019-09-07

hello,大家好,上一篇介紹了drawable如何顯示到view上,基本上是以background屬性來講的,其實在view中用到的drawable地方還是挺多的,還不屬性drawable顯示到view的流程,可以看下我寫的上一篇android中drawable顯示到view上的過程,今天要介紹的也是跟drawable一個相關的屬性foreground屬性,不過該屬性之前只是針對FrameLayout的,後來在23的api之後所有的view都能用該屬性,因此大家知道這麼回事就行了,而且在後面view原始碼中也會看到該屬性相容的程式碼,該屬性一般在開發中能實現水波點選的效果,不知道大家平時用得多不多,好了,下面還是跟往常一樣,通過一個簡單的例子來介紹該屬性的使用:

<TextView
    android:id="@+id/view"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:layout_marginTop="50dp"
    android:background="#cccccc"
    android:foreground="#ff0000"
    android:gravity="center"
    android:text="我是測試的view" />

<TextView
    android:id="@+id/view1"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:layout_marginTop="50dp"
    android:background="#cccccc"
    android:foreground="?attr/selectableItemBackground"
    android:gravity="center"
    android:text="我是測試的view" />
複製程式碼

simple
demo是很簡單,為了演示效果,上面textview的foreground屬性是一個顏色值,下面textview的foreground是獲取應用的style裡面的selectableItemBackground屬性。第一個textview的foreground屬性顏色直接把background屬性覆蓋掉了,而第二個textview的foreground是一個波紋效果,因此帶著這些問題順著原始碼看下這些問題,直接看獲取view的foreground屬性地方:

android中foreground水波實現過程分析
在此處看到該屬性值在api>=23或view是frameLayout的時候呼叫了setForeground方法,該方法其實跟setBackground方法做的是類似的事,先是判斷有沒有foreground,如果有先銷燬掉foreground,然後呼叫applyForegroundTint方法設定foreground的著色情況,最後也是觸發了重新繪製view。那直接看view繪製的時候,是怎麼繪製foreground的:

public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
  

    if (!dirtyOpaque) {
        drawBackground(canvas);
    }

    if (!dirtyOpaque) onDraw(canvas);

    onDrawForeground(canvas);
}
複製程式碼

這裡我把draw方法幾個關鍵方法給列出來了,先是繪製background,然後是onDraw,最後才是foreground,所以說在上面第一個例子中,因為最後才繪製foreground,因此顯示的結果只有foreground的顏色了,下面來看看onDrawForeground方法是怎麼繪製foreground的:

public void onDrawForeground(Canvas canvas) {
    onDrawScrollIndicators(canvas);
    onDrawScrollBars(canvas);

    final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
    if (foreground != null) {
        if (mForegroundInfo.mBoundsChanged) {
            mForegroundInfo.mBoundsChanged = false;
            final Rect selfBounds = mForegroundInfo.mSelfBounds;
            final Rect overlayBounds = mForegroundInfo.mOverlayBounds;

            if (mForegroundInfo.mInsidePadding) {
                selfBounds.set(0, 0, getWidth(), getHeight());
            } else {
                selfBounds.set(getPaddingLeft(), getPaddingTop(),
                        getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
            }

            final int ld = getLayoutDirection();
            //根據mForegroundInfo.mGravity得到foreground的bounds
            Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
                    foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
            foreground.setBounds(overlayBounds);
        }

        foreground.draw(canvas);
    }
}
複製程式碼

其實跟background的繪製差不多,只不過在foreground設定bounds的時候,多了一個foreground.gravity的判斷,意思是foreground的權重,但是我測試過權重只有fill的情況下才起作用,其他的其中foreground.gravity都會讓foreground的顏色失去作用。

寫到這的時候,大家知道了事例一中為什麼加了foreground屬性顏色值之後,為什麼設定textview的background以及text屬性都看不到了吧,因為foreground是在繪製之後最後繪製的,所以被foreground的顏色給覆蓋了。那第二個事例中為什麼會有點選的波紋效果呢,這個就需要了解?attr/selectableItemBackground代表的是啥,這個其實是跟我們們的主題style屬性相關,也就是順著app的application的style屬性可以找到該屬性是什麼:

android中foreground水波實現過程分析
直接來到21下面的Base.Theme.AppCompat.Light下面找:

android中foreground水波實現過程分析
此處找到了關於selectableItemBackground屬性,但還是style裡面的屬性,不要緊,我們們繼續找父style,最後在Theme.Material.Lightstyle下面找到了:

android中foreground水波實現過程分析
也就是說水波效果用到的資原始檔是item_background_material的drawable檔案,繼續看下該資原始檔是怎麼定義的:

<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="?attr/colorControlHighlight">
    <item android:id="@id/mask">
        <color android:color="@color/white" />
    </item>
</ripple>
複製程式碼

color顏色用的是?attr/colorControlHighlight,我們們可以看下該屬性是怎麼定義的,該屬性也是在Theme.Material.Lightstyle下面定義的:

android中foreground水波實現過程分析
繼續看下ripple_material_light是怎麼定義的:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:alpha="@dimen/highlight_alpha_material_light"
          android:color="@color/foreground_material_light" />
</selector>
複製程式碼

此處定義了一個透明度為0.12,顏色為黑色的selector顏色值。在上一節我們知道drawable的子類是根據子類的各種標籤生成不同的drawable,而水波的資原始檔是ripple標籤,所以從這裡可以知道實質是一個RippleDrawable,關於RippleDrawable後面再講解它們怎麼繪製的。下面我們嘗試下改變水波效果的顏色,按照系統自帶的這個水波效果來寫寫,定義了一個change.xml的drawable檔案:

<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="@drawable/ripple_color">
    <item android:id="@android:id/mask">
        <color android:color="@android:color/white" />
    </item>
</ripple>
複製程式碼

可以看到這裡引用了一個ripple_color的檔案:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:alpha="0.5" android:color="@color/colorPrimary" />
</selector>
複製程式碼

用到了一個透明度為0.5,並且顏色用的是系統生成的顏色值。最後在view上引用change.xml檔案:

android中foreground水波實現過程分析
效果大家可以錄製的gif:

android中foreground水波實現過程分析

好了關於水波效果就說到這裡,後面主要說說StateListDrawable、RippleDrawable實現效果的繪製是怎麼來的,以及介紹drawable相關的api是如何使用的,以及使用drawable下面其他的不常用的drawable來實現好玩的功能。

相關文章