この記事は Goodpatch Advent Calendar 2018 の7日目です.

枠が空いていたのでスマブラをしたい気持ちを抑えて少しだけ書くことにします.
目新しいトピックではないので,ご存知の方々は今すぐブラウザを閉じ Switch を起動してください.

概要

独自の Circular Progress を表示するために Custom Drawing View を作った.

Result

環境

  • Android SDK 28
  • Android Studio 3.4

Custom View の実装方針

Android の View には onDraw メソッドがあります. このメソッドには Canvas が引数として渡されるので,その Canvas を使って独自 View の描画をすることができます.

おおまかな流れとしては以下のとおりです.

  • View を継承した独自クラスを作る
  • Paint のインスタンスを作る
  • onDraw メソッドをオーバーライドする
    • Paint のインスタンスと onDraw の引数に渡された Canvas を使って View を描画する

手順

View を継承した独自クラスを作る

class CircularProgressView(context: Context, attrs: AttributeSet) : View(context, attrs)

空の独自クラスを定義したら,好きなレイアウト XML に <CircularProgressView> 要素を追記しておきます. レイアウトエディタで表示がエラーになるかもしれませんが,ビルドをすればなおります.

Paint のインスタンスをつくる

Canvas に描画するためには,まず Paint のインスタンスを作ります.この Paint のインスタンスで描画の色やアンチエイリアシングの設定などをしていきます.(そして今回いちばん重要な strokeCap を設定する!!)

class CircularProgressView(context: Context, attrs: AttributeSet) : View(context, attrs) {
    private val paint = Paint()

    init {
        paint.apply {
            this.color = Color.rgb(43, 112, 196)
            this.style = Paint.Style.STROKE
            this.strokeWidth = circleStrokeWidth
            this.strokeCap = Paint.Cap.ROUND
            this.isAntiAlias = true
        }
    }
}

onDraw メソッドをオーバーライドする

Paint のインスタンスが用意できたので, onDraw メソッドで Canvas に描画していきます.

class CircularProgressView(context: Context, attrs: AttributeSet) : View(context, attrs) {
    private val paint = Paint()

    private val circleBounds = RectF()
    private val circleStrokeWidth = 12.0f

    // Prevent the edge of the circle is cut out by the view rect.
    private val edgePadding = 4.0f

    var progressRate = 0.0f
        set(value) {
            field = MathUtils.clamp(value, 0.0f, 1.0f)
            invalidate()
        }

    init {
        paint.apply {
            // ...
        }
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas ?: return

        val size = min(width, height).toFloat()
        val padding = circleStrokeWidth * 0.5f + edgePadding

        circleBounds.set(padding, padding, size - padding, size - padding)

        val arcStartAngle = 270f
        val arcSweepAngle = 360f * progressRate

        canvas.drawArc(circleBounds, arcStartAngle, arcSweepAngle, false, paint)
    }
}

小さなポイントですが,円弧の外側に padding を設定しています. Paint の strokeWidth を増やすと,線の太さが大きくなった分だけ外側にはみ出てしまうためです. また,円弧の padding がギリギリだと View の矩形領域と接する部分が切れて潰れてしまうため,少しだけ padding を追加しています.

これで,Custom Drawing View を使って独自の Circular Progress を表示することができました.

Next Steps

要点を絞るためにここでは説明しませんでしたが,実際に View を使っていくと上記のソースでは不便なところが残っています.例えば以下のようなものでしょうか.

  • circleStrokeWidth を変えたい
  • paint.color を変えたい
  • …というのをレイアウト XML で指定したい!!

これらは attrs.xml にプロパティを定義することで実現できるようになります.詳しくは Creating a View Class | Android Developers にあります.

[コラム] ProgressBar ではダメだったのか?

確かに Android SDK 標準の ProgressBar でもかなり近いものはできるのですが,1点だけ, Bar に丸みを持たせることができませんでした. 独自 View で描画すれば,こういった細かいデザインも再現することができます.今回は Paint の strokeCap を設定しました.

まとめ・感想

  • 独自の Circular Progress を表示するために Custom Drawing View を作った
    • View を継承して独自クラスを作った
    • Paint に描画の設定をした
    • Paint と Canvas を使って描画した
  • 標準 ProgressBar の範囲では手の届かないかゆいところに対応できた

Goodpatch ではみんなで楽しくスマブラをしています細部までこだわったアプリを作っています. Custom View は特に目新しいトピックではないのですが,細部へのこだわりが少しでも伝わればうれしいです.

参考