FLINTERS Engineer's Blog

FLINTERSのエンジニアによる技術ブログ

丸い画像に影を表示する

こんにちは、GANMA!のAndroidアプリを担当しているゆのうえです。

Androidで丸い画像を表示するには色々な方法があります。 RoundedImageViewやPicassoなどのライブラリを利用して表示しているケースも多いのではないでしょうか。

単に丸くするだけならこのように

class RoundImageView : ImageView {

    constructor(context: Context) : this(context, null)

    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
    this(context, attrs, defStyleAttr, 0)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) :
    super(context, attrs, defStyleAttr, defStyleRes)

    private val paint = Paint().apply { isAntiAlias = true }

    private var viewWidth = 0
    private var viewHeight = 0

    override fun onDraw(canvas: Canvas?) {
        val c = canvas ?: return
        (drawable as? BitmapDrawable)?.bitmap?.let { image ->
            val shader = BitmapShader(
                    Bitmap.createScaledBitmap(image, c.width, c.height, false),
                    Shader.TileMode.CLAMP,
                    Shader.TileMode.CLAMP)
            paint.shader = shader;
            val circleCenter = viewWidth.toFloat() / 2;
            canvas.drawCircle(circleCenter, circleCenter, circleCenter, paint);
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)

        viewWidth = MeasureSpec.getSize(widthMeasureSpec)
        viewHeight = MeasureSpec.getSize(heightMeasureSpec)
    }
}

簡単に実装することもできます。 ただ、このサンプルや先述のライブラリはelevationに対応していません。 丸画像でelevationを表現するにはどうすればいいのでしょう?

CardViewにImageViewをネストする

elevation付きで角が丸いViewというと、Support LibraryにCardViewがあります。 CardViewでImageViewをラップしてやると、こうなります。

f:id:n_yunoue:20161102122713j:plain

これで一応はelevationに対応した丸画像を表現できるのですが、画像表示にいちいちCardViewを利用するのには違和感があります。

CardViewの実装を見る

では、CardViewはどのように角丸の影を表示しているのでしょう。 CardViewの実装を見てみると、最初に背景画像としてRoundRectDrawableというDrawableを設定しています。

final RoundRectDrawable background = new RoundRectDrawable(backgroundColor, radius);
cardView.setCardBackground(background);

最近のAndroidは角を丸めたDrawableを背景にすると、そのDrawableの形状に合わせてelevationに対応した影がつくようになっているようです。 ちなみにApiバージョン20以前のCardViewではDrawable上に自力で影の描画を行っています。

どうやらこれを真似してImageViewの背景にしてやれば、elevation付き丸画像が実現できそうです。

完成

実際に作成したもので表示した画像がこちらです。

f:id:n_yunoue:20161102122738j:plain

分かりにくいので拡大。

f:id:n_yunoue:20161102122859j:plain

うっすらとですが、ちゃんと影が付いています。

コードは後ほどライブラリとして公開予定です。