Modifierの適用順序、難しすぎん?

最近になってようやくJetpack Composeを触り始めた。

はじめはとっつきにくいなあ、レイアウトはXMLでいいじゃんとか思っていたのだが、慣れてしまえば不思議なものである。レイアウト見るためにいちいちXMLファイル見に行くほうが面倒くさいじゃんと今では感じるようになった。

しかしJetpack Composeでもよくわからないことがある。それがModifierである。

Read full post gblog_arrow_right

Fragmentで初期化処理を行うのはどこでやるのか

Fragmentでの初期化処理を行う場所どこだっけとなったので備忘録として残しておく。

処理する場所が変わったんだな、ということだけは記憶にあったのだが、結局どこになったのだったかなと迷ってしまった。この先何回も遭遇しそうだったので、ブログに残しておこうと思う。

Read full post gblog_arrow_right

FragmentFactoryを使ってコンストラクタ経由でFragmentに値を渡す

Fragmentに値を渡す場合は、Bundle経由で渡すのが常識だった。Fragmentはシステムで生成されるため、引数付きのコンストラクタが認識されなかったからだ。

しかし最近ではFragmentFactoryを使うことで、Fragmentに引数付きのコンストラクタを定義しても大丈夫になったということで、今回試してみることにした。

Read full post gblog_arrow_right

OkHttpを使ってAndroidでネットワークリクエストを行う

Android端末でネットワークリクエストを行う方法について書いてみる。APIを叩く=Retrofitを使うという図式があるのだが、まずその前段階としてOkHttpを使ってネットワークリクエストをしてみようと思う。

Androidでネットワークアクセスする記事ってあんまりないかもって思ったので書いてみることにした。ついでにいうとリハビリを兼ねてというのと、自分でも振り返ってみるとどうやるんだっけってなったのがきっかけである。

Read full post gblog_arrow_right

RecyclerViewのマルチセレクションをActionModeと一緒に利用する(途中経過)

あまりにAndroidネタを書いていないので、たまにはAndroidの話を書こうと思う。

RecyclerViewのアイテムを任意で複数選択し、その選択したアイテムに対して何らかのアクションを実行するのを試してみている。自分のアプリに組み込もうと思ったのだが、いきなり導入するにはAndroid Developersの情報では一体どうしたらいいのかさっぱりわからなかった。

こういうときは専用プロジェクトを作って使い方を試すのが私のやり方。サンプルプロジェクトを作ったのでその話を書こうと思う。

ちなみにこの記事ではコードの話は一切ない。とりあえずそれっぽく動くが、未だに動きに納得できていないし、やっぱり理解しきれていないのが大きい。

Read full post gblog_arrow_right

TouchDelegateを使ってタッチ可能領域を拡張する

Buttonのクリックに反応する範囲を拡張したい時がある。 今回は下図のようなカスタムViewを作りたかった。 ImageButtonとTextViewを内包したViewである。 親レイアウトのViewGroupをタッチする=ImageButtonをタッチするという扱いにしたかった。 ImageButtonにしているのは、他のViewとの兼ね合いである。 他のViewはImageButtonにstyle="@style/Base.Widget.AppCompat.ActionButton"を適用して、アイコン画像の周りにだけリップルエフェクトがかかるようになっていて、このカスタムViewもそれに合わせたかったのである。 これはTouchDelegateを使うと実現できる。 TouchDelegate – Android Developers 使い方 TouchDelegateは親のViewGroupに設定する。 TouchDelegateのインスタンスを作成するには、タッチエリアを表すRectと委譲先のView(今回でいうとImageButton)への参照が必要。 Rectはローカル座標系(ディスプレイ上での絶対座標ではなく、ViewGroupの左上を0とした相対座標)を使う。 今回はViewGroupのgetDrawingRect()を使って取得したRectを使った。 ぐぐるとgetHitRect()を使った例がみつかったが、これだとうまく動かなかった。絶対座標になっているからだと思われる。 コード例 class CommentStatusView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr) { private lateinit var binding: ViewCommentStatusBinding init { binding = ViewCommentStatusBinding.inflate(LayoutInflater.from(context), this, true) doOnPreDraw { val rect = Rect() binding.viewGroup.getDrawingRect(rect) binding.viewGroup.touchDelegate = TouchDelegate(rect, binding.imageButton) } } override fun setOnClickListener(l: OnClickListener?) { binding.imageButton.setOnClickListener(l) } } Kotlinで、Android-ktxとDataBindingを使っている。 doOnPreDrawがAndroid-ktxを使っている部分で、ViewTreeObserverを利用してViewの大きさが決まった後で中のブロックの処理(TouchDelegateの設定)を行っているだけである。 後はこのカスタムViewに対するsetOnClicklistenerをImageButtonに対して設定するようにしている。 ポイントは 親のViewGroupに対してTouchDelegateを設定する rectはTouchDelegateを指定するViewGroupからみて、どの位置のタッチイベントを委譲先に渡すのかをローカル座標系で指定する ドキュメントに書いてあるとおりなのだが、英語力がなくていまいちわからず、ググったコードを参考にしながらやってもうまくいかずでちょっとハマった。 実際の動きはこんな感じになった。 リップルエフェクトもクリックリスナもViewGroupに対するものがImageButtonを押している扱いになっている。

TextViewに設定したテキスト内のURLに遷移する

TextViewに設定したテキスト内にURLがあった場合に、そのリンクをクリックできるようにしたい。 クリックしたらブラウザが開いて該当ページに移動できるようにしたい。 手っ取り早くこの要望を満たそうと思ったら、TextViewには便利な機能が用意されている。 TextViewにandroid:autoLink="web"を追加すればよいだけである。 これでテキスト内のURLをクリックしたらブラウザが開いてくれる。 めでたしめでたし。 といくなら楽でよかったのだが、この機能によるURLの処理はあまり正確ではない。 autoLinkの問題点 URLが半角スペースで区切られていたり、2バイト文字以外で区切られていたりしたら正しくリンクとして拾ってもらえる。 例えばあいうえお https://android.gcreate.jp/ かきくけこというテキストであればURLの部分のみがURLとして識別される。 しかしあいうえおhttps://android.gcreate.jp/だとリンクを拾ってくれない。 またあいうえお https://android.gcreate.jp/がリンクになってほしいとした場合、「がリンクになってほしい」という部分までURLとして拾われてしまう。 2バイト文字でない半角カッコで囲ってかっこで(https://android.gcreate.jp/)とした場合、閉じカッコもURLに含まれてしまう。 URLの抽出がうまくいかない場合があるのが最大の問題であるが、URLのハンドリングをカスタマイズできないのもちょっと不便である。 例えばChromeカスタムタブでリンクを開きたい場合に、autoLinkでは対応できない。 autoLinkの場合、リンクをクリックするとACTION_VIEWの暗黙的インテントが発行される。 自前で処理する 以上の問題点を回避するには、自分でテキストにClickableSpanを設定してやると良い。 テキストをSpannableStringに変換する テキストから正規表現を利用してURLを抽出する 抽出したURLを用いてSpannableStringにClickableSpanを設定する TextViewにSpannableStringをsetTextで設定する TextViewにsetMovementMethodを設定する 以上の手順で独自のClickableSpanを設定することができる。 コード的にはこんな感じ(Kotlinとandroid-ktxを利用している)。 val text = "あいうえおhttps://android.gcreate.jp/かきくけこ" val spannable = text.toSpannable() val matcher = Pattern.compile("(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]").matcher(text) while (matcher.find()) { val url = matcher.group() val start = matcher.start() val end = matcher.end() spannable.setSpan(MyUrlSpan(url), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } textView.text = spannable textView.movementMethod = LinkMovementMethod.getInstance() MyUrlSpanは自分で定義する。 といっても大したことはやっていない。 class MyUrlSpan(val url: String) : ClickableSpan() { override fun onClick(view: View) { Snackbar.make(view, "$url clicked", Snackbar.LENGTH_SHORT).show() } } SnackbarでURLを表示しているだけ。 リンクをクリックした際の挙動はこのonClickでカスタマイズできる。 もう少し詳しく このあたりの挙動が知りたければ、TextViewのソースコードを確認するのが手っ取り早い。 mAutoLinkMaskをキーに見ていくとだいたい分かると思う。 ClickableSpanを設定する際に必要なのは、文字列中のどこからどこまでにClickableSpanを適用するかが必要になる。 これは正規表現を使ってマッチさせれば該当する文字列、その文字列の開始位置・終了位置が分かるのでそれを使えば良い。 正規表現はこちらの記事を参考にさせていただいた。 AndroidのTextViewのautolink=webが冗長になる setSpan()する際の第4引数はflagsである。 https://developer.android.com/reference/android/text/Spanned これはEditTextにSpanを設定しないのであれば、おそらく何を設定しても影響はないと思う。 (Spanを設定したテキストの内容が動的に変化する場合に、変更前に設定したClickableSpanの開始位置・終了位置がどう変動するかを指定するフラグだと思うので) TextViewのTextをSpannableにして、ClickableSpanを設定しただけではリンクをクリックすることはできない。 ClickableSpanのonClickが呼び出されるためには、setMovementMethodで何らかのMovementMethodがTextViewに設定されていなければならない。 autoLinkを使った場合のLinkをタップしたときの動きはLinkMovementMethodが使われているのでここはそのまま流用する。 ClickableSpanのonClickを呼び出すかどうかは、TextViewのonTouchメソッド内の処理を確認すれば分かるが、mMovementがnullではないことが条件になっている。 だからTextView.
Read full post gblog_arrow_right

RoomでLiveDataを扱うときに注意

Architecture Componentで追加されたRoomを実際に使ってみた。 なんかややこしそうと思って敬遠していたのだけど、やってみると意外とそうでもないなという感じで「あれ、これだけ?」っていう感じで実装できた。実装できたと言っても、適当なサンプルだから簡単だっただけなんだけども。 https://github.com/gen0083/SampleArchitectureComponent RoomではDatabaseとDaoとEntityの3種類を用意してやると、AnnotationProcessorによるコード生成が行われてSQLiteを簡単に利用できるというライブラリ。 LiveDataを返すようにDAOで定義することができ、この場合Databaseが更新されるとその変更がLiveDataを通じて受け取れる。いわゆるいいねボタン問題を解決する一つの解決策と言える。 LiveDataを返すようにDAOで定義を行うと、その処理はバックグラウンドで実行されるようなコードが生成される。逆にそれ以外の処理は自分で実行スレッドを考慮しないといけない。デフォルトだとRoomで生成されたコードをメインスレッドで実行すると例外が吐かれる。 LiveDataを返すようにする場合は注意しなければいけないことがある。 https://github.com/gen0083/SampleArchitectureComponent/blob/master/app/src/androidTest/java/jp/gcreate/sample/samplearchitecturecomponent/data/repository/TestRepositoryByRoomTest.kt#L66 このコードのようにsut.watch().valueとLiveDataのvalueを取得しても、その時点ではvalueはnullである。該当のテーブルのデータ全件取得と変更通知を兼ねてLiveDataを使おうとすると想定通りに動かない。実際にデータが取れるまでは時間がかかる。 LiveData<List<TestData>>としておけば、変更通知も受け取れる、データ全件取得もできる、便利じゃんと思ったけど、そうそううまくはいかなかった。といっても、RecyclerViewに表示する目的であれば、LiveDataを使うとすごい楽ちんであった。同じくSupport LibraryのListAdapterと組み合わせるとすごい楽。 ちなみにこのLiveDataを返すメソッドのユニットテストを行う場合、android.arch.core:core-testing:$architecture_version"を追加して、@get:Rule var executorRule = InstantTaskExecutorRule()とJunitTestRuleを適用してやるとうまいことテストができる。このルールを適用しないとバックグラウンドでの処理を待たずにテストメソッドが終了してしまい、LiveDataからデータを受け取ることなくテストが終了してしまう。