1. 程式人生 > >Android L限制Ripple水波紋範圍大小

Android L限制Ripple水波紋範圍大小

Meterial Design

Ripple簡介

Android 5.0之後google推出了Material Design,Botton預設的觸控反饋會有水波紋漣漪效果。而這種水波紋的效果實現主要依賴於RippleDrawable

以下會介紹Ripple的基本使用及關於控制水波紋範圍的三種處理方法,僅作點明思路及學習筆記不作具體實現。

基本使用

該效果通常以background的形式呈現,在XML中可以引用以下兩個系統自帶屬性:
- android:background=”?android:attr/selectableItemBackground” 有邊界波紋
- android:background=”?android:attr/··” 超出邊界波紋。該波紋由父佈局繪製及限制邊界(API 21提供)
selectableItemBackground

為例看下系統屬性的實現原理,發現該屬性的定義最終指向<item name="selectableItemBackground">@drawable/item_background_material</item>,
檢視該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>

selectableItemBackgroundBorderless所對應Drawable內容為:

<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?attr/colorControlHighlight" />

RippleDrawable

XML控制

特點:簡單,用於固定的view的處理,但靈活性不高。

目前網路上的資料偏向於如何在xml的item下做文章,如在ripple中新增shape來限制範圍,驗證效果反而有各種小坑(誰驗誰知道)。殊不知官方早已提供解決方案。

根據官方對於RippleDrawable的說明文件,ripple的xml標籤支援兩個屬性,分別是color色調和radius波紋半徑。故我們在使用時只需要新建ripple並以`android:background的形式呼叫即可.

<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:attr/colorControlHighlight"
android:radius="@dimen/ripple_radius" />

自定義RippleDrawable

特點:可以動態控制,靈活性超級高,但對應的處理複雜度和難度也較高。

設定水波紋點選效果的本質其實就是設定一個background,最為靈活的方法當然是自定義ripple,然後對目標View直接setBackground即可.

RippleDrawable繼承於Drawable

java.lang.Object
↳ android.graphics.drawable.Drawable
↳ android.graphics.drawable.LayerDrawable
↳ android.graphics.drawable.RippleDrawable

自定義時可以繼承RippleDrawable也可以直接繼承Drawable,兩者的本質分別是實現setRadius()和實現setHotspotBounds(),殊途同歸,均可以達到動態限制波紋大小的效果。系統的虛擬鍵NavigationBar就是使用的後者。

折中方案

特點:簡單,靈活適中,易上手

selectableItemBackgroundBorderless超出邊界範圍為基礎,以setHotspotBounds()的方式動態控制其波紋範圍。

以下提供的是個簡易工具demo,呼叫時傳入對應的viewxxx.setBackground(RippleUtils. getRippleDrawable(context, targetView)),也可以自己定義增加一個控制ripple範圍的方法:

/**
 * Created by vito on 16-11-1.
 */
public class RippleUtils {
    private static RippleDrawable mRipple;
    private static Drawable mTileBackground;

    private static Drawable newTileBackground(Context context) {
        final int[] attrs = new int[]{android.R.attr.selectableItemBackgroundBorderless};
        final TypedArray ta = context.obtainStyledAttributes(attrs);
        final Drawable d = ta.getDrawable(0);
        ta.recycle();
        return d;
    }

    private static void setRipple(RippleDrawable tileBackground, View v) {
        mRipple = tileBackground;
        updateRippleSize(v);
    }

    //以view的中心為圓心,寬的1/4為半徑的ripple範圍
    private static void updateRippleSize(View v) {
        // center the touch feedback on the center of the icon, and dial it down a bit
        if (v.getWidth() != 0) {
            final int cx = v.getWidth() / 2;
            final int cy = v.getHeight() / 2;
            final int rad = (int) (v.getWidth() * .25f);
            Log.d("ripple", "updateRippleSize: rad=" + rad);
            mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad);
        } else {
            // TODO: 17-1-9  
        }
    }

    //對外介面
    public static RippleDrawable getRippleDrawable(Context context, View view) {
        mTileBackground = newTileBackground(context);
        if (mTileBackground instanceof RippleDrawable) {
            setRipple((RippleDrawable) mTileBackground, view);
        }
        return mRipple;
    }
}