【Android】ちょっと便利なDrawableを書く


この記事はand factory Advent Calendar 2020の11日目の記事です。
昨日は@tokiyanさんの [BigSur] アプリケーションを開くためのアクセス権限がありません。の対処法 でした。

はじめに

Viewを角丸にする・色を変えるなど、Drawableを書く機会は多いと思います。
この記事では、私が個人的によく使っているDrawableの書き方のサンプルを紹介したいと思います。

色と同時にEffectを指定する

backgroundにcolorを指定し、foregroundにRippleEffectを指定する、なんて書き方をしている人は多いのでは無いでしょうか。
もちろんそれでも可能ですが、Effectを付け忘れること心配や、foreground指定がAPIレベルが23以上でないと使えないといった問題もあります。
そんな時に、Backgroundの色とEffectを同時に指定したDrawableを用意してしまうと便利です。

bg_color_effect.xml

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="?android:attr/colorControlHighlight">
    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/blue" />
        </shape>
    </item>
</ripple>

shapeのみで使うパターンにrippleを被せただけです。
これをbackgroundに指定すれば、背景色 @color/blue と、RiipleEffectが同時に適応できます。
なお、 ?android:attr/colorControlHighlight はデフォルトのRiipleEffectのカラーです。

角丸などの変形View+Effect

角丸のViewなどをDrawableで作っても、Effectがそれをはみ出してしまう例をよく見ると思います。
あれはRipleEffectがView全体に広がってしまうのが原因です。
これも先程と同じ様にrippleを被せると、Effectがその範囲だけに広がってくれます。

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="?android:attr/colorControlHighlight">
    <item>
        <shape android:shape="rectangle">
            <corners android:radius="@dimen/button_radius" /> <!-- 角の丸み -->
            <solid android:color="@color/blue" />
        </shape>
    </item>
</ripple>


Effectが角をはみ出ません

Effect Only

ベタ塗りや枠の描画は行わず、Effectだけを適応するDrawableを作ることもできます。
複雑なViewの上部にFrameLayoutをかぶせて、そこにEffectを適用したい場合に使えます。

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="?android:attr/colorControlHighlight">
    <item android:id="@android:id/mask"> <!-- maskを指定する -->
        <shape android:shape="rectangle">
            <solid android:color="@color/white" />
        </shape>
    </item>
</ripple>

id/mask以下の要素でEffectがMaskされます。
これは塗りつぶしているので、EffectだけのDrawableになります。

円形Effect Only

先程のを応用すると、円形のEffectだけのDrawableなんかもできます。
円形のViewをはみ出すようなEffectを付けたい場合などに使えると思います。

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="?android:attr/colorControlHighlight">
    <item android:id="@android:id/mask">
        <shape android:shape="oval"> <!-- ovalを指定 -->
            <solid android:color="@color/white" />
        </shape>
    </item>
</ripple>


Viewに沿ったovalになるので、長方形、正方形のViewでそれぞれ形が変わります。

Effectを動的に作る

動的にViewを作りつつ、背景色を変え、Effectも付ける、なんていう時にはDrawableを動的に書く必要がでてきます。
#ffffff などを引数にして、以下の様に作る事ができます。

fun TextView.setCustomDrawable(textColor: String, backgroundColor: String, borderColor: String) {
    if (textColor.isNotBlank() && backgroundColor.isNotBlank() && borderColor.isNotBlank()) {
        setTextColor(Color.parseColor(textColor))
        // 角丸と色の設定
        val pressedDrawable = GradientDrawable().apply {
            setColor(Color.parseColor(backgroundColor))
            cornerRadius = context.resources.getDimensionPixelSize(R.dimen.radius).toFloat() // radiusは定義しているとします
            setStroke(4, Color.parseColor(borderColor)) // `1`はStorkeのWidth
        }
        // `?android:attr/colorControlHighlight` の取得
        val typedValue = TypedValue()
        context.theme.resolveAttribute(android.R.attr.colorControlHighlight, typedValue, true)
        val color = ContextCompat.getColor(context, typedValue.resourceId)
        // RippleEffectの設定
        val rippleDrawable = RippleDrawable(ColorStateList.valueOf(color), pressedDrawable, pressedDrawable)
        this.background = rippleDrawable
    }
}


少し複雑ですが、これで目的は果たせると思います。

placeholderを自作する

少し毛色を変え、placeholderに使えるDrawableについてです。
対象として、アプリのロゴを入れたplaceholderを使う場合を想定しています。
placeholderを設定する箇所毎に、サイズの違う画像をデザイナーが用意してそれを使用する、なんていう事があると思います。
そんな時は、placeholder用のアプリロゴだけ用意してもらい、Drawableで必要サイズの画像を用意してしまえば、
画像分の容量が削減でき、デザイナーの負担も減ると思います。

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle">
            <solid android:color="@android:color/darker_gray" /> <!-- 背景色を指定 -->
            <size
                android:width="100dp"
                android:height="100dp" />
        </shape>
    </item>
    <item
        android:width="24dp"
        android:height="24dp"
        android:drawable="@drawable/icon_logo"
        android:gravity="center" />
</layer-list>

また、これで作成した画像は scaleType="fitXY" で配置すると、伸縮するのが背景色部分になり、ロゴのサイズ自体は変わりません。
これで更に細かく画像を用意する必要が減ると思います。

おわりに

Drawableをうまく活用すると、結構いろいろなViewが作れます。
是非Drawableを活用し、UIのブラッシュアップや、容量・手間の削減に役立ててください。