便利で汎用性高めのExtension集です。
今後も便利なExtensionができ次第、本記事を更新していきます。
この他にも便利なものがあればコメントでお願いします!!
Swift版の記事を見て、Kotlin版も欲しいなと思ったのがきっかけです。
クラス名の取得
- ProGuardを使用していると、クラス名は難読化されるので注意しましょう。
Any.kt
/**
* クラス名を取得
*
* @return String
*/
fun Any.className(): String = javaClass.simpleName
/**
* クラス名を取得
*
* @param any Any
* @return String
*/
fun Any.className(any: Any?): String {
if (any == null) {
return "Null"
}
return any.javaClass.simpleName
}
Viewの表示切り替え
ViewExt.kt
/**
* VisibleとGoneを切り替える
*
* @param isVisible Visibleであるかどうか
*/
fun View.visibleOrGone(isVisible: Boolean) {
visibility = if (isVisible) View.VISIBLE else View.GONE
}
/**
* VisibleとInvisibleを切り替える
*
* @param isVisible Visibleであるかどうか
*/
fun View.visibleOrInvisible(isVisible: Boolean) {
visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
}
/**
* GoneとVisibleを切り替える
*
* @param isGone Goneであるかどうか
*/
fun View.goneOrVisible(isGone: Boolean) {
visibility = if (isGone) View.GONE else View.VISIBLE
}
/**
* InvisibleとVisibleを切り替える
*
* @param isInvisible Invisibleであるかどうか
*/
fun View.invisibleOrVisible(isInvisible: Boolean) {
visibility = if (isInvisible) View.INVISIBLE else View.VISIBLE
}
/**
* VISIBLEに切り替える
*/
fun View.toVisible() {
visibility = View.VISIBLE
}
/**
* INVISIBLEに切り替える
*/
fun View.toInvisible() {
visibility = View.INVISIBLE
}
/**
* GONEに切り替える
*/
fun View.toGone() {
visibility = View.GONE
}
Viewの連打対策
ViewExt.kt
/**
* 連打対策のためViewを一定時間クリック無効にするクリックリスナー
*
* @param onClickListener Unit
*/
fun View.setOnClickPauseListener(onClickListener: (View) -> Unit) {
if (hasOnClickListeners()) {
return
}
setOnClickListener {
pauseClickTimer()
onClickListener.invoke(it)
}
}
/**
* 連打対策のためViewを一定時間クリック無効にする
*
* @param delayMillis クリックの無効化時間(ミリ秒)
*/
fun View.pauseClickTimer(delayMillis: Long = 500) {
isClickable = false
Handler().postDelayed({ isClickable = true }, delayMillis)
}
ViewPager
ViewPagerExt.kt
fun ViewPager.setOnPageChangeListener(onPageChangeListener: (Int) -> Unit) {
addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
// do nothing
}
override fun onPageSelected(position: Int) {
onPageChangeListener(position)
}
override fun onPageScrollStateChanged(state: Int) {
// do nothing
}
})
}
RecyclerView with Groupie
- Groupieライブラリについてはこちら
RecyclerViewExt.kt
fun RecyclerView.setGroupieAdapter() {
adapter = GroupAdapter<ViewHolder>()
}
fun RecyclerView.setLinearLayoutManager() {
layoutManager = LinearLayoutManager(context)
}
fun RecyclerView.setLinearLayoutManagerWithDivider() {
val linearLayoutManager = LinearLayoutManager(context)
layoutManager = linearLayoutManager
val dividerItemDecoration = DividerItemDecoration(context, linearLayoutManager.orientation)
addItemDecoration(dividerItemDecoration)
}
fun RecyclerView.setGridLayoutManager(span: Int) {
layoutManager = GridLayoutManager(context, span)
}
inline fun <reified T : Group> RecyclerView.setGroupieOnItemClickListener(crossinline onItemClickListener: (T, View) -> Unit) {
val adapter = adapter
if (adapter is GroupAdapter) {
adapter.setOnItemClickListener { item, view ->
if (item is T) onItemClickListener(item, view)
}
}
}
inline fun <reified T : Group> RecyclerView.setGroupieOnItemLongClickListener(crossinline onItemLongClickListener: (T, View) -> Boolean) {
val adapter = adapter
if (adapter is GroupAdapter) {
adapter.setOnItemLongClickListener { item, view ->
return@setOnItemLongClickListener if (item is T) onItemLongClickListener(item, view) else false
}
}
}
EditText
EditTextExt.kt
fun EditText.setOnChangedTextListener(onChangedTextListener: (String) -> Unit) {
addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
// do nothing
}
override fun afterTextChanged(s: Editable?) {
// do nothing
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
onChangedTextListener.invoke(s.toString())
}
})
}
TextView
TextViewExt.kt
/**
* 通貨を設定
*
* @param price Int
* @param locale ロケール
*/
fun TextView.setTextCurrency(price: Int?, locale: Locale = Locale.JAPAN) {
if (price != null) {
try {
text = NumberFormat.getCurrencyInstance(locale).format(price)
} catch (e: Exception) {
Log.e(className(), "通貨を表示できません", e)
}
}
}
ImageView with Glide
ImageViewExt.kt
/**
* 画像URLから画像を設定する
*
* @param imageUrl 画像URL
*/
fun ImageView.setImageUrl(imageUrl: String?) {
if (imageUrl.isNullOrEmpty()) {
return
}
Glide.with(context).load(imageUrl).into(this)
}
/**
* 画像Fileから画像を設定する
*
* @param file 画像File
*/
fun ImageView.setImageFile(file: File?) {
if (file == null) {
return
}
Glide.with(context).load(file).into(this)
}
/**
* 画像URIから画像を設定する
*
* @param uri 画像URI
*/
fun ImageView.setImageUri(uri: Uri?) {
if (uri == null) {
return
}
Glide.with(context).load(uri).into(this)
}
Android Architecture Component
- Android Architecture Componentについてはこちら
LiveData
LiveDataExt.kt
// region 監視(observer)
fun <T> LiveData<T>.observe(owner: LifecycleOwner, observer: (T) -> Unit) = observe(owner, Observer {
if (it != null) observer.invoke(it)
})
fun <T> LiveData<T>.observeOrNull(owner: LifecycleOwner, observer: (T?) -> Unit) = observe(owner, Observer {
observer.invoke(it)
})
// endregion
// region 変換(map)
fun <X, Y> LiveData<X>.map(func: (X) -> Y) = Transformations.map(this, func)
fun <X, Y> LiveData<X>.switchMap(func: (X) -> LiveData<Y>) = Transformations.switchMap(this, func)
// endregion
// region 初期(default)
fun <T, U : MutableLiveData<T>> U.default(value: T) = apply { setValue(value) }
// endregion
// region 取得(is/get)
inline val <reified T> LiveData<T>?.requireValue: T
get() = this?.value ?: throw IllegalArgumentException("LiveData value is null.")
fun <T> LiveData<T>?.isNotNull(): Boolean = this?.value != null
fun LiveData<String>?.isNotEmpty(): Boolean = this?.value?.isNotEmpty() ?: false
fun LiveData<String>?.isEmpty(): Boolean = this?.value?.isEmpty() ?: false
fun LiveData<Boolean>?.isTrue(): Boolean = this?.value ?: false
fun LiveData<Boolean>?.isFalse(): Boolean = this?.value?.not() ?: false
// endregion
追記:
- バージョンアップでMutableLiveDataの初期値が設定できるようになったみたいです
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0-alpha01'
LiveData with RxKotlin
LiveDataExt.kt
// region bindTo
/**
* RxをLiveDataにバインドする
*
* @param data データ
* @return Disposable
*/
fun <T : Any> Flowable<T>.bindTo(data: MutableLiveData<T>): Disposable {
return subscribeBy(onNext = { data.value = it }, onError = defaultError())
}
/**
* RxをLiveDataにバインドする
*
* @param data データ
* @return Disposable
*/
fun <T : Any> Observable<T>.bindTo(data: MutableLiveData<T>): Disposable {
return subscribeBy(onNext = { data.value = it }, onError = defaultError())
}
/**
* RxをLiveDataにバインドする
*
* @param data データ
* @return Disposable
*/
fun <T : Any> Single<T>.bindTo(data: MutableLiveData<T>): Disposable {
return subscribeBy(onSuccess = { data.value = it }, onError = defaultError())
}
/**
* RxをLiveDataにバインドする
*
* @param data データ
* @return Disposable
*/
fun <T : Any> Maybe<T>.bindTo(data: MutableLiveData<T>): Disposable {
return subscribeBy(onSuccess = { data.value = it }, onError = defaultError())
}
// endregion
// region withProgress
/**
* プログレス状態のフラグ切り替え
*
* @param progress MutableLiveData
* @return Flowable
*/
@CheckResult
fun <T> Flowable<T>.withProgress(progress: MutableLiveData<Boolean>): Flowable<T> {
return compose { item ->
item
.doOnSubscribe { progress.postValue(true) }
.doFinally { progress.postValue(false) }
}
}
/**
* プログレス状態のフラグ切り替え
*
* @param progress MutableLiveData
* @return Observable
*/
@CheckResult
fun <T> Observable<T>.withProgress(progress: MutableLiveData<Boolean>): Observable<T> {
return compose { item ->
item
.doOnSubscribe { progress.postValue(true) }
.doFinally { progress.postValue(false) }
}
}
/**
* プログレス状態のフラグ切り替え
*
* @param progress MutableLiveData
* @return Observable
*/
@CheckResult
fun <T> Single<T>.withProgress(progress: MutableLiveData<Boolean>): Observable<T> {
return toObservable().withProgress(progress)
}
/**
* プログレス状態のフラグ切り替え
*
* @param progress MutableLiveData
* @return Observable
*/
@CheckResult
fun <T> Maybe<T>.withProgress(progress: MutableLiveData<Boolean>): Observable<T> {
return toObservable().withProgress(progress)
}
/**
* プログレス状態のフラグ切り替え
*
* @param progress MutableLiveData
* @return Observable
*/
@CheckResult
fun <T> Completable.withProgress(progress: MutableLiveData<Boolean>): Observable<T> {
return toObservable<T>().withProgress(progress)
}
// endregion
Date
DateExt.kt
/**
* 日付フォーマット
*
* @param pattern パターン
*/
enum class DateFormat(val pattern: String) {
YYYY_MM("yyyy/MM"),
YYYY_MM_DD("yyyy/MM/dd"),
YYYY_MM_DD_HH_MM_SS("yyyy/MM/dd HH:mm:ss")
}
/**
* 日付をフォーマットする
*
* @param format DateFormat
* @param locale ロケール
*/
fun Date.toString(format: DateFormat, locale: Locale = Locale.getDefault()): String {
try {
val sdf = SimpleDateFormat(format.pattern, locale)
return sdf.format(this)
} catch (e: Exception) {
Log.e(className(), "日付変換エラー : $e")
}
return ""
}
/**
* 文字列から日付に変換
*
* @param format 日付フォーマット
* @return 日付
*/
fun String.toDate(format: DateFormat, locale: Locale = Locale.getDefault()): Date? {
try {
val sdf = SimpleDateFormat(format.pattern, locale)
return sdf.parse(this)
} catch (e: Exception) {
Log.e(className(), "日付変換エラー : $e")
}
return null
}