Files
assistant-android/app/src/main/java/com/gh/common/util/Extensions.kt
kehaoyuan 74e6c9b145 轮播图,触控到轮播区域暂停自动轮播
双击底部导航栏时相关列表自动回到顶部
2019-11-18 21:17:47 +08:00

434 lines
13 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.gh.common.util
import android.content.ClipboardManager
import android.content.Context
import android.text.Editable
import android.text.Html
import android.text.Spanned
import android.text.TextWatcher
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.widget.PopupWindow
import android.widget.TextView
import androidx.annotation.ColorRes
import androidx.core.text.HtmlCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.*
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.constant.Config
import com.gh.common.constant.Constants
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.R
import com.google.gson.reflect.TypeToken
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
import okhttp3.MediaType
import okhttp3.RequestBody
import java.net.URI
import kotlin.math.abs
/**
* 创建以 activity 为观察者上下文的 viewModel
*/
inline fun <reified VM : ViewModel> FragmentActivity.viewModelProvider(
provider: ViewModelProvider.Factory? = null
) =
ViewModelProviders.of(this, provider).get(VM::class.java)
/**
* 创建以 activity 为观察者上下文的 viewModel
*/
inline fun <reified VM : ViewModel> Fragment.viewModelProviderFromParent(
provider: ViewModelProvider.Factory? = null
) =
ViewModelProviders.of(requireActivity(), provider).get(VM::class.java)
/**
* 创建以 fragment 为观察者上下文的 viewModel
*/
inline fun <reified VM : ViewModel> Fragment.viewModelProvider(
provider: ViewModelProvider.Factory? = null
) =
ViewModelProviders.of(this, provider).get(VM::class.java)
/**
*
* ViewPager Extensions
*
*/
fun ViewPager.doOnPageSelected(action: (position: Int) -> Unit) = addOnPageChangeListener(onSelected = action)
fun ViewPager.addOnPageChangeListener(onSelected: ((position: Int) -> Unit)? = null) {
val listener = object : ViewPager.OnPageChangeListener {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
// Do nothing.
}
override fun onPageSelected(position: Int) {
onSelected?.invoke(position)
}
override fun onPageScrollStateChanged(state: Int) {
// Do nothing.
}
}
addOnPageChangeListener(listener)
}
fun ViewPager.addOnScrollStateChanged(onStateChanged: ((state: Int) -> Unit)? = null) {
val listener = object : ViewPager.OnPageChangeListener {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
}
override fun onPageScrollStateChanged(state: Int) {
onStateChanged?.invoke(state)
}
}
addOnPageChangeListener(listener)
}
/**
* RecyclerView Extensions
*/
// 监听滚动距离
fun RecyclerView.doOnScrolledSpecificDistance(distanceX: Int = 0, distanceY: Int = 0, singleTimeEvent: Boolean = false, action: () -> Unit) {
val listener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if ((distanceX != 0 && abs(dx) > distanceX) || (distanceY != 0 && abs(dy) > distanceY)) {
action.invoke()
if (singleTimeEvent) {
removeOnScrollListener(this)
}
}
}
}
addOnScrollListener(listener)
}
/**
* View Extensions
*/
fun View.visibleIf(predicate: Boolean) {
visibility = if (predicate) {
View.VISIBLE
} else {
View.INVISIBLE
}
}
fun View.goneIf(predicate: Boolean) {
visibility = if (predicate) {
View.GONE
} else {
View.VISIBLE
}
}
fun View.addSelectableItemBackground() {
val outValue = TypedValue()
context.theme.resolveAttribute(android.R.attr.selectableItemBackground, outValue, true)
setBackgroundResource(outValue.resourceId);
}
fun View.removeSelectableItemBackground() {
background = null
}
/**
* LiveData Extensions
*/
fun <T> LiveData<T?>.observeNonNull(owner: LifecycleOwner, callback: (T) -> Unit) {
observe(owner, Observer { value ->
if (value != null) {
callback(value)
}
})
}
/**
* Login related extensions
*/
fun Fragment.ifLogin(entrance: String, action: (() -> Unit)? = null) {
requireContext().ifLogin(entrance, action)
}
fun Context.ifLogin(entrance: String, action: (() -> Unit)? = null) {
CheckLoginUtils.checkLogin(this, entrance, action)
}
/**
* Gson related extensions.
*/
inline fun <reified T : Any> String.toObject(): T? {
return try {
GsonUtils.gson.fromJson(this, object : TypeToken<T>() {}.type)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
inline fun <reified T : Any> T.toJson(): String {
return GsonUtils.toJson(this)
}
/**
* 在限定 interval 里只触发一次 action
*/
fun debounceActionWithInterval(id: Int, interval: Long = 300, action: (() -> Unit)? = null) {
if (!ClickUtils.isFastDoubleClick(id, interval)) {
action?.invoke()
}
}
fun View.debounceActionWithInterval(interval: Long = 300, action: (() -> Unit)? = null) {
debounceActionWithInterval(this.id, interval, action)
}
/**
* 告诉需要返回 true or false 的外层这个事件已经被消费(即返回 true
*/
inline fun consume(f: () -> Unit): Boolean {
f()
return true
}
/**
* 简化那些不得不写 try catch 的代码块
*/
inline fun tryWithDefaultCatch(action: (() -> Unit)) {
try {
action.invoke()
} catch (e: Throwable) {
e.printStackTrace()
}
}
/**
* String related
*/
fun String.fromHtml(): Spanned {
return HtmlCompat.fromHtml(this, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
// 去掉文章/答案的插入内容
fun String.removeInsertedContent(): String {
val textRegex = "(?s)<div class=\"gh-internal-content content-right\".*?</div>"
return this.replace(textRegex.toRegex(), "")
}
// 去除视频相关文本
fun String.removeVideoContent(): String {
val videoRegex = "(?s)<div class=\"insert-video-container\".*?</div>"
return this.replace(videoRegex.toRegex(), "")
}
// 完全地清除所有 Html 格式
fun String.clearHtmlFormatCompletely(): String {
return Html.fromHtml(this).toString().replace('\n', 32.toChar())
.replace(160.toChar(), 32.toChar()).replace(65532.toChar(), 32.toChar()).trim { it <= ' ' }
}
// 如果该字符串长度超过固定长度的话,从头开始截取固定长度并返回
fun String.subStringIfPossible(length: Int): String {
return if (this.length > length) {
this.substring(0, length)
} else {
this
}
}
fun String.copyTextAndToast(toastText: String = "复制成功") {
val application = HaloApp.getInstance().application
val cmb = application.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
cmb.text = this
Utils.toast(application, toastText)
}
fun Map<String, String>.createRequestBody(): RequestBody {
val json = GsonUtils.toJson(this)
return RequestBody.create(MediaType.parse("application/json"), json)
}
// 对在浏览器(WebView)显示的路径进行转码
fun String.decodeURI(): String {
return URI(null, null, this, null).rawPath
}
/**
* 根据手机的分辨率从 dip(像素) 的单位 转成为 px
*/
fun Float.dip2px(): Int {
val scale = HaloApp.getInstance().application.resources.displayMetrics.density
return (this * scale + 0.5f).toInt()
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dip
*/
fun Float.px2dip(): Int {
val scale = HaloApp.getInstance().application.resources.displayMetrics.density
return (this / scale + 0.5f).toInt()
}
/**
* PopupWindow 自动适配方向
*/
fun PopupWindow.showAutoOrientation(anchorView: View, distanceY: Int = 0) {
val windowPos = IntArray(2)
val anchorLoc = IntArray(2)
// 获取锚点View在屏幕上的左上角坐标位置
anchorView.getLocationOnScreen(anchorLoc)
val anchorHeight = anchorView.height + distanceY
// 获取屏幕的高宽
val screenHeight = anchorView.context.resources.displayMetrics.heightPixels
val screenWidth = anchorView.context.resources.displayMetrics.widthPixels
// 测量contentView
contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
// 计算contentView的高宽
val windowHeight = contentView.measuredHeight
val windowWidth = contentView.measuredWidth
// 判断需要向上弹出还是向下弹出显示
val isNeedShowUp = screenHeight - anchorLoc[1] - anchorHeight < windowHeight
if (isNeedShowUp) {
windowPos[0] = screenWidth - windowWidth
windowPos[1] = anchorLoc[1] - windowHeight
} else {
windowPos[0] = screenWidth - windowWidth
windowPos[1] = anchorLoc[1] + anchorHeight
}
animationStyle = R.style.popwindow_option_anim_style
showAtLocation(anchorView, Gravity.TOP or Gravity.START, windowPos[0], windowPos[1])
}
/**
* 权限相关
*/
fun Fragment.checkReadPhoneStateAndStoragePermissionBeforeAction(action: (() -> Unit)) {
PermissionHelper.checkReadPhoneStateAndStoragePermissionBeforeAction(requireContext(), object : EmptyCallback {
override fun onCallback() {
action.invoke()
}
})
}
fun Fragment.checkReadPhoneStatePermissionBeforeAction(action: (() -> Unit)) {
PermissionHelper.checkReadPhoneStatePermissionBeforeAction(requireContext(), object : EmptyCallback {
override fun onCallback() {
action.invoke()
}
})
}
fun Fragment.checkStoragePermissionBeforeAction(action: (() -> Unit)) {
PermissionHelper.checkStoragePermissionBeforeAction(requireContext(), object : EmptyCallback {
override fun onCallback() {
action.invoke()
}
})
}
fun FragmentActivity.checkReadPhoneStateAndStoragePermissionBeforeAction(action: (() -> Unit)) {
PermissionHelper.checkReadPhoneStateAndStoragePermissionBeforeAction(this, object : EmptyCallback {
override fun onCallback() {
action.invoke()
}
})
}
fun FragmentActivity.checkReadPhoneStatePermissionBeforeAction(action: (() -> Unit)) {
PermissionHelper.checkReadPhoneStatePermissionBeforeAction(this, object : EmptyCallback {
override fun onCallback() {
action.invoke()
}
})
}
fun FragmentActivity.checkStoragePermissionBeforeAction(action: (() -> Unit)) {
PermissionHelper.checkStoragePermissionBeforeAction(this, object : EmptyCallback {
override fun onCallback() {
action.invoke()
}
})
}
/**
* TextView related.
*/
fun TextView.setTextWithHighlightedTextWrappedInsideWrapper(text: CharSequence,
wrapper: String = Constants.DEFAULT_TEXT_WRAPPER,
@ColorRes
highlightColorId: Int = R.color.theme,
copyClickedText: Boolean = false,
highlightedTextClickListener: (() -> Unit)? = null) {
TextHelper.highlightTextThatIsWrappedInsideWrapper(this, text, wrapper, highlightColorId, object : SimpleCallback<String> {
override fun onCallback(arg: String) {
if (copyClickedText) {
arg.copyTextAndToast("已复制:$arg")
}
highlightedTextClickListener?.invoke()
}
})
}
fun TextView.setTextChangedListener(action: (s: CharSequence, start: Int, before: Int, count: Int) -> Unit) {
this.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
action.invoke(s ?: "", start, before, count)
}
})
}
fun Int.toColor(): Int {
return HaloApp.getInstance().application.resources.getColor(this)
}
fun Int.toResString(): String {
return HaloApp.getInstance().application.resources.getString(this)
}
fun Int.toSimpleCount(): String {
return NumberUtils.transSimpleCount(this)
}
/**
* Image related
*/
fun SimpleDraweeView.display(url: String) {
ImageUtils.display(this, url)
}
/**
* 测试用包裹
*/
inline fun debugOnly(f: () -> Unit) {
if (BuildConfig.DEBUG) {
f()
}
}
inline fun testChannelOnly(f: () -> Unit) {
if (HaloApp.getInstance().channel == Config.DEFAULT_CHANNEL) {
f()
}
}