android-ktx
Android-KTXとは...?
Android KTX は、Android Jetpack ファミリーの一部である Kotlin 拡張機能 セットです。Kotlin で使用するために、Jetpack と Android プラットフォーム API を 最適化します。Android KTX の目的は、Kotlin 言語の機能(拡張機能 / プロパティ、 ラムダ、名前付きパラメータ、パラメータのデフォルト値など)を 活用することで、Kotlin による Android 開発を、より簡潔で快適かつ言語特有のもの にすることです。Android KTX により、既存の Android API に新機能が追加される わけではありません。
Android開発で使うと幸せになれる拡張という感じでしょうか。
※注) 現在こちらのリポジトリでは開発されてないみたいですね
ATTENTION: This repository is no longer being used for development, tracking issues, or accepting pull requests.
Please file any bug reports or feature suggestions for core-ktx and the other "KTX" libraries at
https://issuetracker.google.com/issues/new?component=396204
Please contribute to core-ktx and the other "KTX" libraries in AOSP by following the instructions in the README of
https://android.googlesource.com/platform/frameworks/support/
Thanks!
環境構築
Android-KTXはモジュール化されており、必要に応じたパッケージを依存関係に含めるようになっています。
今回はコアパッケージに絞って便利そうな拡張を試してみたいと思います。
app/build.gradle
に以下を追加していきます。
repositories {
google() // 必要であれば
}
dependencies {
implementation 'androidx.core:core-ktx:1.0.1'
}
コアパッケージのみであれば↑だけでひとまずOKです。
実装
Toast
1.0.1からは実装自体が無くなっています
-
toast
1.0.1未満のバージョンで実装は以下のようになっていました
(個人的には結構便利だと思うのですが何故無くなってしまったんだろう...)Toast.ktinline fun Context.toast(text: CharSequence, duration: Int = Toast.LENGTH_SHORT): Toast { return Toast.makeText(this, text, duration).apply { show() } } inline fun Context.toast(@StringRes resId: Int, duration: Int = Toast.LENGTH_SHORT): Toast { return Toast.makeText(this, resId, duration).apply { show() } }
使い方
// Contextを拡張しているので、Activity内で以下の様に書ける toast("android-ktx toast sample") toast(R.string.app_name) toast("length long", Toast.LENGTH_LONG) // defaultはLENGTH_SHORT // Fragment内だと以下の様に書く activity?.toast("toast from fragment")
View
-
doOnNextLayout
// Viewが配置されたら呼ばれる (addOnLayoutChangeListener) // (widthやheightが取得できる状態) button.doOnNextLayout { view -> Log.d(TAG, "width: ${view.width}, height: ${view.height}") }
-
doOnLayout
// 既に配置済みであればview自信を返す // 配置されていない場合は内部でdoOnNextLayoutを呼ぶ button.doOnLayout { view -> Log.d(TAG, "width: ${view.width}, height: ${view.height}") }
-
doOnPreDraw
// ビューツリーを描画しようとしているときに呼ばれる button.doOnPreDraw { view -> Log.d(TAG, "$view") }
-
updatePadding / setPadding
// paddingの更新 (left, top, right, bottom) button.updatePadding(5, 15, 5, 15) // left, top, right, bottomに同じ値を設定 button.setPadding(5)
-
postDelayed
// 200 milisec後に何か処理する button.postDelayed(200) { Log.d(TAG, "200 delayInMillis") }
-
drawToBitmap
// viewに描画されている内容をBitmapとして取得 // 但しviewの配置がされる前に呼ばれるとIllegalStateExceptionを投げる button.doOnPreDraw { val bitmap = button.drawToBitmap() imageView.setImageBitmap(bitmap) }
-
isVisible / isInvisible / isGone
// buttonが表示された状態 Log.d(TAG, "isVisible: ${button.isVisible}, isInvisible: ${button.isInvisible}, isGone: ${button.isGone}") // => isVisible: true, isInvisible: false, isGone: false
ArrayMap
こちらも1.0.1からは実装自体が無くなっています
-
arrayMapOf
1.0.1未満のバージョンで実装は以下のようになっていました
ArrayMap.kt@RequiresApi(19) inline fun <K, V> arrayMapOf(): ArrayMap<K, V> = ArrayMap() @RequiresApi(19) fun <K, V> arrayMapOf(vararg pairs: Pair<K, V>): ArrayMap<K, V> { val map = ArrayMap<K, V>(pairs.size) for (pair in pairs) { map[pair.first] = pair.second } return map }
使い方
// 初期値なし val arrayMap = arrayMapOf<String, String>() arrayMap["key1"] = "value1" Log.d(TAG, "key1: ${arrayMap["key1"]}") // key1: value1 Log.d(TAG, "keyX: ${arrayMap["keyX"]}") // keyX: null // 初期値をkotlin.Pairで指定 val arrayMap2 = arrayMapOf(Pair("key2", "value2"), Pair("key3", "value3")) Log.d(TAG, "key2: ${arrayMap2["key2"]}") // key2: value2 Log.d(TAG, "key3: ${arrayMap2["key3"]}") // key3: value3
SparseArray
-
getOrDefault/getOrElse
val sa = SparseArray<String>() sa.put(1, "apple") sa.put(2, "banana") sa.put(3, "orange") Log.d(TAG, "getOrDefault: ${sa.getOrDefault(4, "lemon")}") // getOrDefault: lemon Log.d(TAG, "getOrElse: ${sa.getOrElse(4, defaultValue = { "${sa.size}" })}") // getOrElse: 3
-
isEmpty/isNotEmpty
val sa = SparseArray<String>() Log.d(TAG, "isEmpty: ${sa.isEmpty()} / isNotEmpty: ${sa.isNotEmpty()}") // isEmpty: true / isNotEmpty: false
-
forEach
val sa = SparseArray<String>() sa.put(1, "apple") sa.put(2, "banana") sa.put(3, "orange") sa.forEach { key, value -> Log.d(TAG, "$key : $value") } // 1 : apple, 2 : banana, 3 : orange
-
keyIterator / valueIterator
val sa = SparseArray<String>() sa.put(1, "apple") sa.put(2, "banana") sa.put(3, "orange") for (k in sa.keyIterator()) { Log.d(TAG, "key: $k") // key: 1, ... } for (v in sa.valueIterator()) { Log.d(TAG, "value: $v") // value: apple, ... }
CharSequence / String
-
isDigitsOnly
Log.d(TAG, "12345 idDigitsOnly: ${"12345".isDigitsOnly()}") // true Log.d(TAG, "12s45 idDigitsOnly: ${"12s45".isDigitsOnly()}") // false
-
trimmedLength
// トリムされた文字列の長さを返してくれる Log.d(TAG, "[ aa ] trimmedLength: ${" aa ".trimmedLength()}") // 2 Log.d(TAG, "[ b b ] trimmedLength: ${" b b ".trimmedLength()}") // 4
-
htmlEncode/parseAsHtml/toHtml
// htmlEncode Log.d(TAG, "<>&\"".htmlEncode()) // <>&" // parseAsHtml val parsedHtml = "<p>http://www.example.com</p>".parseAsHtml() // Spannedを返す Log.d(TAG, parsedHtml.toHtml()) // <p dir="ltr">http://www.example.com</p>
SpannableString/SpannableStringBuilder/SpannedString
// index:0~4の間の文字を太文字にする
val s1 = "abcdefg".toSpannable()
s1[0, 5] = StyleSpan(Typeface.BOLD)
spannableText1.text = s1
// index:0~3の間の文字をテキストサイズ100spにする
val s2 = "hijklmn".toSpannable()
s2[0..4] = AbsoluteSizeSpan(100)
spannableText2.text = s2
// index:0~5の間の文字をリンク付き文字にする
val s3 = "opqrstuvwxyz".toSpannable()
s3[0..6] = object : ClickableSpan() {
override fun onClick(view: View) {
Log.d(TAG, "ClickableSpan#onClick")
}
}
spannableText3.movementMethod = LinkMovementMethod.getInstance()
spannableText3.text = s3
Bundle
-
bundleOf
data class User(val name: String) : Serializable // key/valueのペアから新しいBundleを作成してくれる val bundle = bundleOf(Pair("a", null), Pair("b", 1), Pair("c", true), Pair("d", intArrayOf(4, 5)), Pair("e", User("taro"))) Log.d(TAG, "bundle a => ${bundle.get("a")}") // bundle a => null Log.d(TAG, "bundle b => ${bundle.get("b")}") // bundle b => 1 Log.d(TAG, "bundle c => ${bundle.get("c")}") // bundle c => true Log.d(TAG, "bundle d => ${(bundle.get("d") as? IntArray)?.size}") // bundle d => 2 Log.d(TAG, "bundle e => ${bundle.get("e")}") // bundle e => User(name=taro)
Handler
-
postDelayed / postAtTime
// 元々あるpostDelayedとpostAtTimeメソッドのパラメータ位置を変更し書きやすくした拡張 val handler = Handler() handler.postDelayed(200) { Log.d(TAG, "Handler#postDelayed") } handler.postAtTime(200) { Log.d(TAG, "Handler#postAtTime") }
Trace
-
trace
// 起動して約10秒後にセクション名「section-X」として記録 handler.postDelayed(10000) { trace("section-X") { Thread.sleep(1000) } }
記録されたデータはAndroidStudioの「Profiler」>「CPU」で確認できる
https://developer.android.com/studio/profile/cpu-profiler
その他
公式の拡張には実装されていないのですが、こちらの拡張がとても便利だったので試してみました。
Resource
- resolveAttribute / resolveAttributeResId
inline fun Resources.Theme.resolveAttribute(
resId: Int,
resolveRefs: Boolean = true
): TypedValue = TypedValue().apply { resolveAttribute(resId, this, resolveRefs) }
// こっちは↑の実装をさらに拡張したもの
inline fun Resources.Theme.resolveAttributeResId(
resId: Int,
resolveRefs: Boolean = true
): Int = this.resolveAttribute(resId, resolveRefs).resourceId
Themeのattributeのidを取得したい時とかに便利
使い方
theme.resolveAttribute(R.attr.colorAccent, false)
theme.resolveAttributeResId(R.attr.colorAccent)
まとめ
コアパッケージだけでも十分便利な拡張が実装されている印象でした。
そして自分で拡張書く時の参考になりますw
他のモジュールに関しても時間があれば色々試してみたいと思います。