Celeste Engineer

Androidとか自転車とか

StateListDrawable の子にあたる Drawable の背景色を Animator で変えてみる

概要

Android Framework に数ある Drawable のなかでも、View の状態に応じて使う Drawable を切り替えることのできる StateListDrawable は多くの人が使っていると思います。ListView のアイテムの背景、ボタンの背景などインタラクションをする View に対して使うことが多いでしょうか。

ところでAndroid の場合、ユーザの指による操作以外にもマウスやキーボードなどの外部入力装置による操作もインタラクションの手段として使えます。外部入力装置を使う場合、いまどこを操作しようとしているかを見せるために、StateListDrawable の state_focused を使って Drawable を切り替えます。

StateListDrawable の各状態で使う Drawable は ShapeDrawable や BitmapDrawable など他の Drawable です。今回は、StateListDrawable の各状態に ShapeDrawable を設定し、state_focused などを考慮しつつ Java や Kotlin のコードから ShapeDrawable の背景を Animator で変えてみようと思います。

<selector>

次のような StateListDrawable を xml で書きます。クリック時には背景を濃いグレーに、フォーカスが当たった時は枠線を描きます。クリック時でかつフォーカスが当たったときは濃いグレーの背景に枠線を描きます。

<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:state_focused="true" android:state_pressed="true">
    <shape android:shape="rectangle">
      <solid android:color="#333333"/>
      <stroke android:width="3dp" android:color="00AAFF"/>
    </shape>
  </item>
  <item android:state_pressed="true">
    <shape android:shape="rectangle">
      <solid android:color="#333333"/>
    </shape>
  </item>
  <item android:state_focused="true">
    <shape android:shape="rectangle">
      <solid android:color="#999999"/>
      <stroke android:width="3dp" android:color="00AAFF"/>
    </shape>
  </item>
  <item>
    <shape android:shape="rectangle">
      <solid android:color="#999999"/>
    </shape>
  </item>
</selector>

Drawable の背景色を変更する

StateListDrawableDrawableContainerの子クラスで、DrawableContainerにはgetCurrentというメソッドがあります。StateListDrawableのような状態を持つものの場合は、現在表示しているものを返してくれるのでこれを使います。

さきほど xml で作った StateListDrawable の子はどれも<shape>ですので、getCurrentメソッドで得られるのもその時の状態で指定している<shape>です。<shape>タグを読み込むと、どの形(四角形、円など)かという ShapeDrawable とどのような背景色や枠線を描画するかという GradientDrawable のふたつが作られます。StateListDrawable#getCurrent()で直接得られる Drawable はGradientDrawableであることに注意しましょう。 そして取り出したGradientDrawablesetColor(int)メソッドで背景色を変えることができます。 あとは、Animator を使えば背景色をアニメーションできます。

fun animateBackgroundColor(view: View, color1: Int, color2: Int, duration: Long) {
  val background = view.background as StateListDrawable
  val animator = ValueAnimator.ofObject(ArgbEvaluator(),
      ContextCompat.getColor(view.context, color1),
      ContextCompat.getColor(view.context, color2))
  animator.duration = duration
  animator.addUpdateListener {
    (background.current as GradientDrawable).setColor(it.animatedValue as Int)
  }
  animator.start()
}

懐かしいゲームがリメイクされていたのでテンションあがって遊んでみた

懐かしいゲームというのはこれのことです。

store.steampowered.com

内容は、60年代の米国とソ連が宇宙からきた未知の資源を巡って太陽系の惑星やら衛星でドンパチするという完全に硬派でシブいSFゲームですが、発売当時(98年)のPCでもモリモリ動く3Dのファーストパーソン・シューティングゲームで、かつリアルタイムストラテジーゲームでもあるという今でもなかなか無さそうなゲームです。シングルプレイモードとネットワーク対戦モードがあって、シングルプレイは米軍とソ連軍それぞれにシナリオがあり、プレイヤーは様々な司令をこなしていくゲーム、ネットワーク対戦モードはLANやインターネットで繋いでそれぞれ好きにたたかうゲームになります。どちらのモードにしても、プレイヤーは司令官として資源を集めて基地を作り、自軍を強化・防衛して敵を倒すのが大まかな流れになりますが、自分自身が先陣を切って敵陣に突撃してもよいし、ひたすら遠くから戦いを眺めるでもよいという感じで遊びの幅がある、本当に硬派でシブいSFゲームです。

それが20年ちかく経ってSteamでリメイクを果たしMacにまで配信されるようになると言うのは、本当に長きにわたってコアなファンがついている硬派でシブいゲームだということだと思います。いやほんと、プレイしてみると分かりますがとにかく硬派でシブいゲームです。やることは非常にたくさんあって同時にあれこれこなさないと敵にやられてしまうし、ミッションに失敗するとすごい勢いで上官(General なので一番えらい人)が怒ります。かと言って操作自体は単純なので遊びやすくはあります。

これを遊んでいた当時はまだ小学生でしたが、父親がどこからか原版(日本語翻訳版も売られていたそう)を手に入れていて、気がついたら一緒に遊んでいました。 小学生なので当然英語も分からないし、しかも軍隊なのでさらに英語が難しい。ゲームの解説もとくにないしインターネットも無かったので、自分にとってはとにかくトライ・アンド・エラーを繰り返してどうにかミッションをクリアする方法を見つけ出すゲームでした。今ならネットで調べて勘所を抑えれば失敗も少なくクリアできるところでしょうが、そういうわけにもいかなかったし、ゲームを買ってきた父親自身も英語が分かるわけではないので(!)聞くに聞けず、とにかくクリアしたければ自分で何とかするしかない感じがまた面白かったのです。

多分これを遊ばなかったら英語を頑張って勉強する気にもならなかったし、PCそのものもそんなに興味を持たなかったような気もするので、そういう意味でも懐かしいゲームなので、ついテンションが上ってしまいました。

RecyclerView の item に focus が当たったことを検知する

やりたいこと

旧来の AdapterView (ListView や GridView など)でいうところの OnItemSelectedListener と同じように、RecyclerView がもつアイテムにフォーカスが当たったときにその情報をコールバックする方法です。

方法

RecyclerView には OnChildAttachStateChangeListener というインタフェースが定義されています。このインタフェースのメソッドで、Adapter の生成する View が RecyclerView に attach されたタイミングが分かるので、そこで attach された View に OnFocusChangeListener をセットしてフォーカスが当たったことを検知します。

class FocusListeningRecyclerView(context: Context?, attrs: AttributeSet?, defStyle: Int)
     : RecyclerView(context, attrs, defStyle), RecyclerView.OnChildAttachStateChangeListener {
  var focusListener: OnItemFocusedListener? = null

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

  override fun onAttachedToWindow() {
    super.onAttachedToWindow()
    addOnChildAttachStateChangeListener(this)
  }

  override fun onDetachedFromWindow() {
    removeOnChildAttachStateChangeListener(this)
    super.onDetachedFromWindow()
  }

  override fun onChildViewAttachedToWindow(view: View?) {
    view?.setOnFocusChangeListener { child, hasFocus ->
      if (!hasFocus)
        return@setOnFocusChangeListener
      focusListener?.onItemFocused(getChildAdapterPosition(child))
    }
  }

  override fun onChildViewDetachedFromWindow(view: View?) {}

  interface OnItemFocusedListener {
    fun onItemFocused(position: Int)
  }
}

ツールド東北

ツールド東北に同僚と参加してきました。

出場するのは日曜日の南三陸コース。前日に民泊のお家に到着して、そこから受付をしに自転車を組み立てました。
f:id:KeithYokoma:20170920100235j:image

石巻の会場にはSubaru XV が!上には Bianchi の自転車が乗っかっていて、よさそう(小並感)と思いました。普段はFitやSwift、Demioくらいのコンパクトカーで十分だと思っていましたが、コンパクトSUVも使い勝手が良さそう。コンパクトカーだと荷室の広さではFitの圧勝なのでな…
f:id:KeithYokoma:20170920100257j:image
同僚は去年も参加していて、今年も多分ピカチュウがいるとおもう、ということで探したらありました。
f:id:KeithYokoma:20170920100320j:image

受付をサッと済ませて民泊のお家に帰りました。オーナーのおばちゃんが料理大好きで、晩御飯が超豪華!
f:id:KeithYokoma:20170920100336j:image

滅多なことでは食べないカニ、最近値上がり気味のサンマ、そして初めて食べたホヤ。お野菜や漬物などとにかくおかずがたくさん出てきて、ご飯なしでも満腹になりました😊
f:id:KeithYokoma:20170920100414j:image

 翌朝は晴れていて、台風の接近を感じさせるものの自転車には乗れる天気!
f:id:KeithYokoma:20170920100433j:image

すこし風が強い場所がありましたが、女川エイドステーションに到着。すごい人!
f:id:KeithYokoma:20170920100559j:image

女川汁いただきました。
f:id:KeithYokoma:20170920100615j:image

途中人だかりができていた、眺めの良い場所でパシャリ📷
f:id:KeithYokoma:20170920100729j:image

そんなこんなで雄勝エイドステーション。ホタテ!
f:id:KeithYokoma:20170920100808j:image

太鼓の演奏も見てゴールへ向かいました。
f:id:KeithYokoma:20170920100837j:image

無事ゴール。
f:id:KeithYokoma:20170920100908j:image

 帰ってお昼を食べてからは、市内を車で回ることに。

f:id:KeithYokoma:20170920101114j:image

日和山のうえから、津波の押し寄せてきた海を見ました(写真天地がひっくり返ってる…)。車の中でも、いろいろと震災当時のことを教えてもらいました。
f:id:KeithYokoma:20170920101138j:image

仮面ライダーとツーショット。
f:id:KeithYokoma:20170920101156j:image

原画展もやっていました。
f:id:KeithYokoma:20170920101217j:image

翌日のお昼前に東京へ向けて出発。途中柿の種の車が走っていました。

f:id:KeithYokoma:20170920101259j:image

那須高原SAで。
f:id:KeithYokoma:20170920101322j:image

牛肉〜。
f:id:KeithYokoma:20170920101336j:image

二泊三日のツールド東北でしたが、民泊でのご飯がずっと美味しくて、オーナーのおばちゃんとの会話は絶え間なく続いて実家に帰ってきたような安心感がありました。

嬬恋キャベツヒルクライムに参加してきた

今年からサイクリングイベントに参加し始めてはや3回目の大会となりました、群馬県嬬恋村の万座ハイウェイを自転車専用に貸し切って開催された嬬恋キャベツヒルクライムに参加してきました。

cabe-hill.net

距離は約20kmで1000mの標高差を登ります。平均勾配で見ると5%くらいと易しめに見えますが、実際には途中で平坦や若干の下りもあるため、たまに10%ほどの坂が現れたり、地味に長い緩斜面が続いたりと、走りきってみると結構キツめだったような印象があります。以前は参加者全員がフルコースを走るイベントだったようですが、今年はファンライドコースが設定されており、途中のホテルまでの序盤のじわじわくる坂を楽しめるコースもありました。

かなり地元を上げて町おこしも兼ねて力を入れているようで、プロサイクリングチームをゲストに迎えて一緒にヒルクライムレースをしたり、キャベツ(嬬恋村はキャベツ生産で有名)を使った料理をふるまっていただけたり、キャベツ1玉をお土産にもらえたりと、村ならではの特色があるイベントでした。自分もキャベツ1玉を持って帰ってきたので、お好み焼きにして食べたいと思います。また、周辺のホテルとの連携も強く、ホテル利用者専用の駐車場があったり、ホテルで参加受付ができたり、参加者向けの特典があったりもしました。自分が泊まったホテルはヒルクライムコースをさらに登ったところにあって、当日いちどコースを車で降りてくる必要がありますが、スタート時間が午前9時と余裕があるので、朝食を食べてひとっ風呂浴びてから降りても間に合うような感じです。

当日は天気もよく、標高が高いながらも程よく涼しい気温でした。全体で1000人ほどの参加者がいたようですが、滞りなくスタートができました。

序盤の万座ハイウェイにはいるまでの道も地味に斜度があって、ウォーミングアップにはちょうどよい気がします。ハイウェイに入ってからも斜度は地味にきつめなので、あまり飛ばしすぎない程度にスルスルと登っていきました。 ファンライドコースのゴール地点を過ぎたところや、給水地点の直前の坂がきつかったりもしましたが、終盤にかけてはいいペースで登れた気がします。時間はあまり意識していませんでしたが、一時間半くらいを目標に登れそうな感じだったので、そこを目指して登りました。

終盤も地味な斜度の強弱がありました。途中マルコファヴァロさんやNIPPOの選手と間近なところで走ることもできて楽しかったです。結局タイムは1時間20分と目標よりも10分はやく登りきりました。さすがにこの時期でも標高1800m付近は寒く、指まで覆うグローブを忘れてきたのは失敗したなと思いましたが、何とか無事に下山も済ませました。

下山後に特別にマッサージを受けて、腰回りを揉んでもらいました。すごい気持ちよかったのですが、どうやらお尻の筋肉が凝り固まっているようで、マッサージを受けているときもウッとなったのですが、アドバイスとしてそこをストレッチすると腰や下半身も楽になると教わったので、お尻のストレッチを試してみようと思います。

NuAns NEO Reloaded 使用感

Nexus5X から次の端末を探しているときに NuAns NEO Reloaded がリリースされたのを機に乗り換えて、2ヶ月ほどが経ったので使用感を書き残しておこうと思います。

概ね Vanilla Android なので、Nexus シリーズのリファレンス機に慣れていれば特に違和感なく使えると思います。若干指紋認証のハードウェアが鈍い気がしますが、表についているので便利ではあります。 性能としてはハイエンドではなくミドルレンジの端末なので、グラフィックがすごいゲーム(小並感)を楽しむには厳しい気もします。ただ自分はそこにこだわりはなく、普通のアプリを使う分には問題ないと思っています。少なくとも Nexus5X に比べれば圧倒的にサクサク動いてくれますし、使用していく中で発熱をしてもパフォーマンスに影響があるようには見えないので、Google Maps をカーナビ代わりに使うなどしても問題ないとおもいます(少なくとも3時間のドライブで Google Maps を立ち上げっぱなしにしてもサクサク動いてくれたので満足)。

問題があるのは、ディスプレイのタッチ検出の様子がおかしい場合があることです。熱を持つとよく起こる気がしますが、タップをしているのに拾わない、タップしていない場所でタップを誤検出する、の2つが稀によく起こります。 タップしているのに拾わないのは、開発者オプションでタップを表示すると、タップの表示は確かにされているのにタッチイベントがアプリに伝わっていないように見えます。ただ、ゲームでタップした場所に何らかの視覚効果が出るものでためすと、その視覚効果が出ているのも確認したので、一体何が問題でタップをハンドリングできてないのかよくわかりません…

開発者としては、開発者オプションにある「バグレポートのショートカットを追加する」機能が死んでいるのが気になります。有効にしても電源メニューにバグレポートのショートカットは出ないので、ここぞというときにもたもたしてしまうので残念です。

総じて、ちょっとしたゲームで遊んだり動画を見たり、普通のアプリを使っている分にはサクサク動いてくれてよいな、でも開発機としては困ることもあるな、という印象です。 現場からは以上です。

SMS が相手に届いたかどうか判定する

SMS や MMS と言ったメッセージのやりとりの仕組みには、Delivery Report という機能があります。SMS をおくると、キャリアが Delivery Report を送り主に返します。この Delivery Report を見ることで、SMS が相手に届いたかどうかが分かります*1

SMS や MMS をおくるには、SMSManager を使います。例えば、単純なテキストメッセージをおくるのであれば、sendTextMessage メソッドを呼び出してメッセージを送ります。 他にもいくつか用途に応じて異なるメソッドが用意されていますが、ほぼどのメソッドでも sentIntent と deliveryIntent というPendingIntent型の引数があります。

これらは、メッセージ送信中にエラーがあったかどうか、またメッセージが相手に届いたかどうかをブロードキャスト Intent としてコールバックするために使われます。 sentIntent はメッセージ送信が成功したかどうか、エラーならばどのようなエラーがあったかをブロードキャストします。電波がなかったり、PDU が空だったり、あるいはサービスに未登録だったり、様々な理由で SMS を送れない場合はこのブロードキャストが飛んできます。 一方で deliveryIntent は相手に届いたかどうかを、Delivery Report を元にブロードキャストします。

ここまでの話は公式のリファレンスにも記述してある内容ですが、実は deliveryIntent は必ずブロードキャストされるわけではありません。

これは、キャリアが Delivery Report をサポートしているかどうかによって変わります。例えば、T-Mobile や Verizon は Delivery Report をサポートしているようですが、AT&T はサポートしてないようです*2。また、場合によってはフェイクの Delivery Report を生成することもあるようで、届いていないのに届いているように見せかけるものもあるようです*3

単純に SMS を送信できたかどうかだけであれば、sentIntent のブロードキャストのみを使って判定できるので、届いたかどうかが必要なければ deliveryIntent の PendingIntent は null にしましょう。