見出し画像

盆栽愛好者向けスマートウォッチを探す旅15 ウォッチフェイスを作ってみた2

はじめに

前回記事

にて、自作のウォッチフェイスを作成しました。
でもこれ、よく考えると標準機能でも背景画像は変更できるし、わざわざプログラムでやることではないと思います。

今回はその先のステップへチャレンジした模様をお伝えします。

第2バージョン:針を盆栽の枝に変える

ここからが本番。世の中にない唯一のウォッチフェイスを作りたいと思います。
と言っても、私の中のイメージは子供の頃使っていたミッキーマウスの時計。長針、短針の先がミッキーの指になっており、文字盤を指さしてくれているもの。時間によって、ミッキーの姿勢が変わるので、文字盤を見るのが楽しくなるあの時計。あれを真似てみたい。そんな思いで取り掛かりました。

ラフスケッチ

絵は下手なんですが、とにかく表現しなければという思いで紙にイメージしたものを書き出しました。そうそうこんな感じ。できるかな?

戦うための道具を揃えます。

まず、前回記事で扱ったWatchFaceのソースを読むところから始めます。
もっとも重要な部分は時計の針を描画する部分です。
drawWatchFace関数に集約されていました。

private fun drawWatchFace(canvas: Canvas) {

    /*
     * Draw ticks. Usually you will want to bake this directly into the photo, but in
     * cases where you want to allow users to select their own photos, this dynamically
     * creates them on top of the photo.
     */
    val innerTickRadius = mCenterX - 10
    val outerTickRadius = mCenterX
    for (tickIndex in 0..11) {
        val tickRot = (tickIndex.toDouble() * Math.PI * 2.0 / 12).toFloat()
        val innerX = Math.sin(tickRot.toDouble()).toFloat() * innerTickRadius
        val innerY = (-Math.cos(tickRot.toDouble())).toFloat() * innerTickRadius
        val outerX = Math.sin(tickRot.toDouble()).toFloat() * outerTickRadius
        val outerY = (-Math.cos(tickRot.toDouble())).toFloat() * outerTickRadius
        canvas.drawLine(
            mCenterX + innerX, mCenterY + innerY,
            mCenterX + outerX, mCenterY + outerY, mTickAndCirclePaint
        )
    }

    /*
     * These calculations reflect the rotation in degrees per unit of time, e.g.,
     * 360 / 60 = 6 and 360 / 12 = 30.
     */
    val seconds =
        mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f
    val secondsRotation = seconds * 6f

    val minutesRotation = mCalendar.get(Calendar.MINUTE) * 6f

    val hourHandOffset = mCalendar.get(Calendar.MINUTE) / 2f
    val hoursRotation = mCalendar.get(Calendar.HOUR) * 30 + hourHandOffset

    /*
     * Save the canvas state before we can begin to rotate it.
     */
    canvas.save()

    canvas.rotate(hoursRotation, mCenterX, mCenterY)
    canvas.drawLine(
        mCenterX,
        mCenterY - CENTER_GAP_AND_CIRCLE_RADIUS,
        mCenterX,
        mCenterY - sHourHandLength,
        mHourPaint
    )

    canvas.rotate(minutesRotation - hoursRotation, mCenterX, mCenterY)
    canvas.drawLine(
        mCenterX,
        mCenterY - CENTER_GAP_AND_CIRCLE_RADIUS,
        mCenterX,
        mCenterY - sMinuteHandLength,
        mMinutePaint
    )

    /*
     * Ensure the "seconds" hand is drawn only when we are in interactive mode.
     * Otherwise, we only update the watch face once a minute.
     */
    if (!mAmbient) {
        canvas.rotate(secondsRotation - minutesRotation, mCenterX, mCenterY)
        canvas.drawLine(
            mCenterX,
            mCenterY - CENTER_GAP_AND_CIRCLE_RADIUS,
            mCenterX,
            mCenterY - mSecondHandLength,
            mSecondPaint
        )

    }
    canvas.drawCircle(
        mCenterX,
        mCenterY,
        CENTER_GAP_AND_CIRCLE_RADIUS,
        mTickAndCirclePaint
    )

    /* Restore the canvas" original orientation. */
    canvas.restore()
}

この中で、インデックス、秒針、短針、長針、中央の丸を描画しており、canvas.drawLine、canvas.drawCircle関数が使われています。そのもととなる情報は、変数mCalendarから取り出しています。

この部分をデザインし直せば、思いのままの結果が得られます。ソースを見ていて気になるのは、秒針、短針、長針の描画です。

例えば、長針の描画はこのようになっています。

canvas.rotate(hoursRotation, mCenterX, mCenterY)
canvas.drawLine(
    mCenterX,
    mCenterY - CENTER_GAP_AND_CIRCLE_RADIUS,
    mCenterX,
    mCenterY - sHourHandLength,
    mHourPaint
)

角度hoursRotationだけcanvasを回転させ、その後、直線を描いています。
ミッキーマウスの指を描くのであればこのやり方で全く問題ありませんが、松の葉は常に上(12時の方向)を向いていて欲しいので、この方法を使わずに作図します。針の先に松葉のかたまりが付いており、下に重りを付けて、常に上を向くような仕掛けをイメージしています。

また、今回、canvasライブラリで描くには少し大変なイラストを配置したいと思っています。Figmaでデザインした素材を組み合わせて表示するにはどうしたらいいのでしょうか?

この辺りが作成前の疑問点です。
これらの課題を明らかにしながら進めて行きたいと思います。

Figmaによる部品作成

無料で使えてカスタマイズが容易なコラボレーション・ドローソフト。という程度の認識ですが、私のように絵心のない人間でもなんとか使えています。

以前、自動水やり装置をスマホからコントロールするためのWebページを作成するときに使いました。

今回もFigmaのお世話になり、デザインを進めたいと思います。

手描きイラストのイメージをどんな風に形にするか?もう販売していないかもしれませんが、以前こんなものを購入していました。

キャビコ 可動盆栽

今回作成するウォッチフェイスも時間によって角度が変わるので、可動盆栽と呼ぶことができるでしょう。時間によっては、面白くない樹形になることもあるでしょうが、まぁそれもご愛嬌。
少しずつ形にしていきます。

私はそれほどセンスはよくありません。でもそれなりに自分で満足できるレベルに仕上げていこうと思っています。

今回この課題に取り組んで、とても大きな収穫がありました。
松の葉の描き方を探していました。

イメージとしては、能舞台の鏡板に描かれた松から出発したのですが、もっとデフォルメした年賀状とかで使われる松の感じにしていきたいと思いました。

そこでよく用いられる松葉のかたまりをFigmaで描きたいと思いました。
でもどうやって描くのでしょう。私は描き方を知りませんでした。

ネットで調べてみると、

イラストレーター 松のイラストの描き方

イラストレーターなどのドローツールは、平面上での図形の集合演算ができるようになっていて、これで複雑な形状を作れることが分かりました。
同様の機能はFigmaでも実装されているようなので、ちょっとやってみます。

できました。
このやり方を知って、作図の幅が広がりました。

例えば、主幹のデザイン

私の苦手なベジェ曲線で作図しています。
苦手ながらも、何とか地上部分を描くことができました。

でも、足元の部分が気に入らない。
今までなら、ベジェ曲線の修正を行っていましたが、これがまた難しく全く思った通りに制御できません。
ドローソフトの難しい所がここだったのですが、この部分について、今回学んだ方法で編集してみると、

トリミングしたい図形を描き、「選択範囲の交差」を使うと、

足元がすっきり。
これを知り、ドローソフトの苦手をひとつ克服することができました。

時計のロジックをテストする

Figmaで妄想をどんどん膨らませていますが、本当に思った通りに作図できるのか不安になってきたので、単純な作図で思っていることができるかテストすることにしました。

まずは、canvas.rotateを使わない書き方に変更します。

前述の、長針の描画

canvas.rotate(hoursRotation, mCenterX, mCenterY)
canvas.drawLine(
    mCenterX,
    mCenterY - CENTER_GAP_AND_CIRCLE_RADIUS,
    mCenterX,
    mCenterY - sHourHandLength,
    mHourPaint
)

val hR = Math.toRadians(hoursRotation.toDouble())
val hXs = mCenterX - Math.sin(hR) * (-CENTER_GAP_AND_CIRCLE_RADIUS)
val hYs = mCenterY + Math.cos(hR) * (-CENTER_GAP_AND_CIRCLE_RADIUS)
val hXe = mCenterX - Math.sin(hR) * (-sHourHandLength)
val hYe = mCenterY + Math.cos(hR) * (-sHourHandLength)
canvas.drawLine(hXs.toFloat(), hYs.toFloat(), hXe.toFloat(), hYe.toFloat(), mHourPaint)
canvas.drawCircle(hXe.toFloat(), hYe.toFloat(), 10f, mTickAndCirclePaint)

こんな感じで書き換えました。
引数がFloat型かDouble型かで注意されるため、変換があちこち挟まっていてきれいでないのと、単独では何のための変数かわからない命名がエレガントさに欠けています。
C言語ではキャストを使って型変換しますが、Kotlinではクラスで用意された型変換メソッドを使います。

こだわっている割には、自分の書くソースは「動けばよい」的なところがあり、人に厳しく自分に甘い性格が出てしまっていてお恥ずかしい限りです。
ソースの見直しについては、最後に気が向いたらやることにします。

次にFigmaで描いたデータをどのようにしてAndroid Studioで扱うのかを調べてみます。

私の知っている方法は、Android Studioのリソースにビットマップを登録しておくと、デザインやコードで利用できるということです。アプリのアイコンはビットマップで登録できますが、画面の解像度によってサイズの異なるものを用意しておく必要があります。

その際、xmlファイルでアイコンを登録しておくとよいということを学んでいました。

学習ノートには、下記のように書いています。

ベクター型ドローアブルは Android のベクター グラフィックの実装であり、関連する色情報とともに、点、線、曲線のセットとして XML で定義される。ベクター型ドローアブルでは、画質を損なうことなく任意の密度にスケーリングできる。

学習ノートより

私の短絡的な解釈だと、アイコンもイメージも同じじゃないかと思うので、xmlファイルを取りこめれば、何とかなるんじゃないかと思うのです。

と思って探していると、

さまざまな密度に適用可能なベクター グラフィックの追加

SVG または PSD ファイルのインポート

がありました。

val drawable = resources.getDrawable(R.drawable.myimage, theme)

これで取り込めるようです。

Figmaからのエクスポートは

Figmaでオブジェクトを選択すると、エクスポートが選べます。ファイルタイプは「PNG」、「JPG」、「SVG」、「PDF」が選べます。
この中の「SVG」の中身はxmlなので、これで行けるんじゃないかと。

[File]>[新規]>[Vector Asset]でAsset Studioを立ち上げ、Local file(SVG, PSD)で取り込みます。

ばっちり取り込めました。

ここまでは全く問題ありません。

でもここからが大変でした。

前述の

val drawable = resources.getDrawable(R.drawable.myimage, theme)

で取り込んでみたものの、そこから先の描画がどうもうまくいきません。

Canvas上にDrawableを描画する方法が正しくないものと思われます。

と思って、少しdrawableについて調べてみました。比較的新しいクラスで、画面に描画できるものを保持するクラスのようです。

ListViewなどの登録のようにAndroidのユーザーインターフェイスへ引き渡すには良いようですが、Androidの比較的低レベルAPIであるCanvasへ引き渡すにはBitmap型への変換が必要になります。

この変換方法が今の私の実力では見つけ出すことができませんでした。

今の自分でもできる方法を模索します。

Figmaというドローソフトを用いたためにXMLでデータを渡してスケーラブルな状態で画像を扱いたいと思いましたが、そもそもGoogle Pixel Watch用にWatchi Faceを作っているので、ターゲットの画面解像度も明確だし、PNGファイルでデータを保持するやり方で問題ないと思いました。

現在のサンプルで背景画像を取り込んでいます。
ここでやっていることを参考にすれば何とかなるのではないでしょうか。

もがいていた最中で見つけた有益情報
kenji matsuokaさんのブログ
Watch Faceを作る 画像を使ってオリジナル時計を作る

2015年の記事です。
このころから基本的な所は変わっていないようです。

まずは、背景画像を差し替えます。

前回記事で行った背景画像の入れ替えの要領です。

watchface_service_bg.png

わー!できました!

長針、短針、秒針の色が変わってしまった理由は不明ですが一つ進んだようです。鉢の色は、青色から焦げ茶色へ変えています。

次に、針の描画に取り掛かります。

おおよそのプランはあります。
松葉は常に上を向いています。

松葉の下に基準となる点があり、ここが時間を指し示します。

画像はこんな感じです。

時計の中心から松葉の基準まで、曲がった枝を描画します。

まずは、大体の位置を見るために配置した状態を作ります。
長針、短針は中心から基準点までを結ぶ直線を引きます。
これを縦にして、始点、終点を結ぶ枝を描きます。

これを回転させればよいと思います。
先人の方々のありがたい情報を見ながら、なんとか分針を書き上げることができました。

val mR = Math.toRadians(minutesRotation.toDouble())
val mXe = mCenterX - Math.sin(mR) * (-sMinuteHandLength)
val mYe = mCenterY + Math.cos(mR) * (-sMinuteHandLength)

val mMatrix = Matrix()
mMatrix.setTranslate(mCenterX - 40f, mCenterY - 150f)
mMatrix.postRotate(minutesRotation.toFloat(), mCenterX, mCenterY)

canvas.drawBitmap(mBranchLBitmap, mMatrix, mBackgroundPaint)
canvas.drawBitmap(
    mMatsubaLBitmap,
    (hXe - (mMatsubaLBitmap.width / 2)).toFloat(),
    (hYe - mMatsubaLBitmap.height).toFloat() + 30f,
    mBackgroundPaint
)

分針の描画

mMatrix.setTranslate(mCenterX - 40f, mCenterY - 150f)

ここはアンカーポイントを決めている部分です。
針の画像の中で、回転中心はどこかを指示しています。

秒針は松葉でなんとかなるか?

ずっとここまで、秒針のことを考えずに進めてきました。
イラスト風の盆栽にどうやって秒針を表現しようか考えていました。

ちょっと松葉が細いようです。
太くして再度挑戦します。

樹形が。。。


こちらの方がよいかな。
時計を見ることが楽しくなってきました。

さいごに

今回はこのあたり締めくくりたいと思います。
ウオッチフェイスは毎日何度も見ます。
自分の気に入ったデザインの時計をすると気分があがります。

今回作成したWatch Faceのソースは以下に置きました。
https://github.com/bbonsaid/BonsaiFace2Pub.git
もしご興味があれば、チャレンジしてみてください。

最後までお読みいただき誠にありがとうございました。

#盆栽愛好者向けアプリ #PixcelWatch #WatchFace #Kotlin #Canvas #Drawable #Figma #Bitmap #Matrix #つくってみた・やってみた


この記事が気に入ったらサポートをしてみませんか?