Modify: 添加扩展方法、添加自定义组件、添加工具类、基类调整

This commit is contained in:
Quyunshuo
2020-09-17 15:12:37 +08:00
parent ff5af81f01
commit f0f398e68c
16 changed files with 2261 additions and 26 deletions

View File

@ -0,0 +1,30 @@
package com.quyunshuo.base.handler
import android.os.Handler
import android.os.Looper
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
/**
* @Author: QuYunShuo
* @Time: 2020/9/17
* @Class: LifecycleHandler
* @Remark: 自动在UI销毁时移除msg和任务的Handler不会有内存泄露
*/
class LifecycleHandler(
private val lifecycleOwner: LifecycleOwner,
looper: Looper = Looper.getMainLooper()
) : Handler(looper), LifecycleObserver {
init {
lifecycleOwner.lifecycle.addObserver(this)
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
removeCallbacksAndMessages(null)
lifecycleOwner.lifecycle.removeObserver(this)
}
}

View File

@ -0,0 +1,19 @@
package com.quyunshuo.base.ktx
import android.text.InputFilter
import android.widget.EditText
/**
* @Author: QuYunShuo
* @Time: 2020/9/17
* @Class: EditTextKtx
* @Remark: EditText相关扩展方法
*/
/**
* 过滤掉空格和回车
*/
fun EditText.filterBlankAndCarriageReturn() {
this.filters =
arrayOf(InputFilter { source, _, _, _, _, _ -> if (source == " " || source == "\n") "" else null })
}

View File

@ -0,0 +1,75 @@
package com.quyunshuo.base.ktx
import android.content.Context
import androidx.fragment.app.Fragment
/**
* @Author: QuYunShuo
* @Time: 2020/9/17
* @Class: SizeUnitKtx
* @Remark: 尺寸单位换算相关扩展属性
*/
/**
* dp 转 px
*/
fun Context.dp2px(dpValue: Float): Int {
val scale = resources.displayMetrics.density
return (dpValue * scale + 0.5f).toInt()
}
/**
* px 转 dp
*/
fun Context.px2dp(pxValue: Float): Int {
val scale = resources.displayMetrics.density
return (pxValue / scale + 0.5f).toInt()
}
/**
* sp 转 px
*/
fun Context.sp2px(spValue: Float): Int {
val scale = resources.displayMetrics.scaledDensity
return (spValue * scale + 0.5f).toInt()
}
/**
* px 转 sp
*/
fun Context.px2sp(pxValue: Float): Int {
val scale = resources.displayMetrics.scaledDensity
return (pxValue / scale + 0.5f).toInt()
}
/**
* dp 转 px
*/
fun Fragment.dp2px(dpValue: Float): Int {
val scale = resources.displayMetrics.density
return (dpValue * scale + 0.5f).toInt()
}
/**
* px 转 dp
*/
fun Fragment.px2dp(pxValue: Float): Int {
val scale = resources.displayMetrics.density
return (pxValue / scale + 0.5f).toInt()
}
/**
* sp 转 px
*/
fun Fragment.sp2px(spValue: Float): Int {
val scale = resources.displayMetrics.scaledDensity
return (spValue * scale + 0.5f).toInt()
}
/**
* px 转 sp
*/
fun Fragment.px2sp(pxValue: Float): Int {
val scale = resources.displayMetrics.scaledDensity
return (pxValue / scale + 0.5f).toInt()
}

View File

@ -4,12 +4,13 @@ import android.content.Context
import android.view.Gravity
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.fragment.app.Fragment
/**
* @Author: QuYunShuo
* @Time: 2020/8/17
* @Class: ContextKtx
* @Remark: Context相关的扩展方法
* @Time: 2020/9/17
* @Class: ToastKtx
* @Remark: Toast相关的扩展方法
*/
/**
@ -49,33 +50,33 @@ fun Context.centerToast(@StringRes resId: Int, duration: Int = Toast.LENGTH_SHOR
}
/**
* dp px
* Toast
* @param text CharSequence 类型文本
*/
fun Context.dp2px(dpValue: Float): Int {
val scale = resources.displayMetrics.density
return (dpValue * scale + 0.5f).toInt()
fun Fragment.toast(text: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
context?.toast(text, duration)
}
/**
* px dp
* Toast
* @param resId String 类型资源id
*/
fun Context.px2dp(pxValue: Float): Int {
val scale = resources.displayMetrics.density
return (pxValue / scale + 0.5f).toInt()
fun Fragment.toast(@StringRes resId: Int, duration: Int = Toast.LENGTH_SHORT) {
context?.toast(resId, duration)
}
/**
* sp px
* 居中Toast
* @param text CharSequence 类型文本
*/
fun Context.sp2px(spValue: Float): Int {
val scale = resources.displayMetrics.scaledDensity
return (spValue * scale + 0.5f).toInt()
fun Fragment.centerToast(text: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
context?.centerToast(text, duration)
}
/**
* px sp
* 居中Toast
* @param resId String 类型资源id
*/
fun Context.px2sp(pxValue: Float): Int {
val scale = resources.displayMetrics.scaledDensity
return (pxValue / scale + 0.5f).toInt()
fun Fragment.centerToast(@StringRes resId: Int, duration: Int = Toast.LENGTH_SHORT) {
context?.centerToast(resId, duration)
}

View File

@ -1,23 +1,242 @@
package com.quyunshuo.base.ktx
import android.animation.Animator
import android.animation.IntEvaluator
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.os.Build
import android.view.View
import android.view.ViewGroup
import com.quyunshuo.base.ktx.ViewClickDelay.SPACE_TIME
import com.quyunshuo.base.ktx.ViewClickDelay.hash
import com.quyunshuo.base.ktx.ViewClickDelay.lastClickTime
/**
* @Author: QuYunShuo
* @Time: 2020/8/17
* @Time: 2020/9/1
* @Class: ViewKtx
* @Remark: View相关的扩展方法
*/
/*************************************** View可见性相关 ********************************************/
/**
* 隐藏View
*/
fun View.gone() {
visibility = View.GONE
}
/**
* 显示View
* @receiver View
*/
fun View.visible() {
visibility = View.VISIBLE
}
/**
* View不可见但存在原位置
*/
fun View.invisible() {
visibility = View.INVISIBLE
}
/**
* 判断View是不是[View.VISIBLE]状态
*/
val View.isVisible: Boolean
get() {
return visibility == View.VISIBLE
}
/**
* 判断View是不是[View.INVISIBLE]状态
*/
val View.isInvisible: Boolean
get() {
return visibility == View.INVISIBLE
}
/**
* 判断View是不是[View.GONE]状态
*/
val View.isGone: Boolean
get() {
return visibility == View.GONE
}
/*************************************** View宽高相关 ********************************************/
/**
* 设置View的高度
*/
fun View.height(height: Int): View {
val params = layoutParams ?: ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
params.height = height
layoutParams = params
return this
}
/**
* 设置View的宽度
*/
fun View.width(width: Int): View {
val params = layoutParams ?: ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
params.width = width
layoutParams = params
return this
}
/**
* 设置View的宽度和高度
* @param width 要设置的宽度
* @param height 要设置的高度
*/
fun View.widthAndHeight(width: Int, height: Int): View {
val params = layoutParams ?: ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
params.width = width
params.height = height
layoutParams = params
return this
}
/**
* 设置宽度,带有过渡动画
* @param targetValue 目标宽度
* @param duration 时长
* @param action 可选行为
* @return 动画
*/
fun View.animateWidth(
targetValue: Int, duration: Long = 400, listener: Animator.AnimatorListener? = null,
action: ((Float) -> Unit)? = null
): ValueAnimator? {
var animator: ValueAnimator? = null
post {
animator = ValueAnimator.ofInt(width, targetValue).apply {
addUpdateListener {
width(it.animatedValue as Int)
action?.invoke((it.animatedFraction))
}
if (listener != null) addListener(listener)
setDuration(duration)
start()
}
}
return animator
}
/**
* 设置高度,带有过渡动画
* @param targetValue 目标高度
* @param duration 时长
* @param action 可选行为
* @return 动画
*/
fun View.animateHeight(
targetValue: Int,
duration: Long = 400,
listener: Animator.AnimatorListener? = null,
action: ((Float) -> Unit)? = null
): ValueAnimator? {
var animator: ValueAnimator? = null
post {
animator = ValueAnimator.ofInt(height, targetValue).apply {
addUpdateListener {
height(it.animatedValue as Int)
action?.invoke((it.animatedFraction))
}
if (listener != null) addListener(listener)
setDuration(duration)
start()
}
}
return animator
}
/**
* 设置宽度和高度,带有过渡动画
* @param targetWidth 目标宽度
* @param targetHeight 目标高度
* @param duration 时长
* @param action 可选行为
* @return 动画
*/
fun View.animateWidthAndHeight(
targetWidth: Int,
targetHeight: Int,
duration: Long = 400,
listener: Animator.AnimatorListener? = null,
action: ((Float) -> Unit)? = null
): ValueAnimator? {
var animator: ValueAnimator? = null
post {
val startHeight = height
val evaluator = IntEvaluator()
animator = ValueAnimator.ofInt(width, targetWidth).apply {
addUpdateListener {
widthAndHeight(
it.animatedValue as Int,
evaluator.evaluate(it.animatedFraction, startHeight, targetHeight)
)
action?.invoke((it.animatedFraction))
}
if (listener != null) addListener(listener)
setDuration(duration)
start()
}
}
return animator
}
/*************************************** View其他 ********************************************/
/**
* 获取View id如果没有idSDK>17, 使用[View.generateViewId];否则使用[View.hashCode]
*/
@SuppressLint("ObsoleteSdkInt")
fun View.getViewId(): Int {
var id = id
if (id == View.NO_ID) {
id = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
View.generateViewId()
} else {
this.hashCode()
}
}
return id
}
object ViewClickDelay {
var hash: Int = 0
var lastClickTime: Long = 0
var SPACE_TIME: Long = 2000 // 间隔时间
}
/**
* 防快速点击
* @receiver View
* @param clickAction 要响应的操作
*/
infix fun View.clickDelay(clickAction: () -> Unit) {
this.setOnClickListener {
if (this.hashCode() != hash) {
hash = this.hashCode()
lastClickTime = System.currentTimeMillis()
clickAction()
} else {
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime > SPACE_TIME) {
lastClickTime = System.currentTimeMillis()
clickAction()
}
}
}
}

View File

@ -0,0 +1,35 @@
package com.quyunshuo.base.livedata
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean
/**
* @Author: QuYunShuo
* @Time: 2020/9/17
* @Class: OnceLiveData
* @Remark: 只执行一次的LiveData
*/
class OnceLiveData<T> : MutableLiveData<T>() {
private var isRead: AtomicBoolean = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner, Observer {
if (isRead.compareAndSet(false, true)) {
observer.onChanged(it)
}
})
}
override fun postValue(value: T) {
isRead.set(false)
super.postValue(value)
}
override fun setValue(value: T) {
isRead.set(false)
super.setValue(value)
}
}

View File

@ -0,0 +1,39 @@
package com.quyunshuo.base.mvvm.v
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding
import com.alibaba.android.arouter.launcher.ARouter
import com.quyunshuo.base.utils.EventBusRegister
import com.quyunshuo.base.utils.EventBusUtils
/**
* @Author: QuYunShuo
* @Time: 2020/9/10
* @Class: BaseFrameNotMVVMActivity
* @Remark: 不使用 MVVM 的 Activity 基类
*/
abstract class BaseFrameNotMVVMActivity<VB : ViewBinding> : AppCompatActivity() {
protected val mBinding: VB by lazy(mode = LazyThreadSafetyMode.NONE) { initViewBinding() }
protected abstract fun initViewBinding(): VB
protected abstract fun initView()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(mBinding.root)
// ARouter 依赖注入
ARouter.getInstance().inject(this)
// 注册EventBus
if (javaClass.isAnnotationPresent(EventBusRegister::class.java)) EventBusUtils.register(this)
initView()
}
override fun onDestroy() {
if (javaClass.isAnnotationPresent(EventBusRegister::class.java)) EventBusUtils.unRegister(
this
)
super.onDestroy()
}
}

View File

@ -0,0 +1,49 @@
package com.quyunshuo.base.mvvm.v
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
import com.alibaba.android.arouter.launcher.ARouter
import com.quyunshuo.base.utils.EventBusRegister
import com.quyunshuo.base.utils.EventBusUtils
/**
* @Author: QuYunShuo
* @Time: 2020/9/10
* @Class: BaseFrameNotMVVMFragment
* @Remark: 不使用 MVVM 的 Fragment 基类
*/
abstract class BaseFrameNotMVVMFragment<VB : ViewBinding> : Fragment() {
protected val mBinding: VB by lazy(mode = LazyThreadSafetyMode.NONE) { initViewBinding() }
protected abstract fun initViewBinding(): VB
protected abstract fun initView()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return mBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// ARouter 依赖注入
ARouter.getInstance().inject(this)
// 注册EventBus
if (javaClass.isAnnotationPresent(EventBusRegister::class.java)) EventBusUtils.register(this)
initView()
}
override fun onDestroy() {
if (javaClass.isAnnotationPresent(EventBusRegister::class.java)) EventBusUtils.unRegister(
this
)
super.onDestroy()
}
}

View File

@ -15,6 +15,9 @@ abstract class BaseViewModel<R : BaseRepository> : ViewModel() {
// Loading 状态
val isLoading = MutableLiveData(false)
// 请求异常
val requestError = MutableLiveData<Throwable?>()
protected val mRepository: R by lazy { initRepository() }
protected abstract fun initRepository(): R

View File

@ -0,0 +1,114 @@
package com.quyunshuo.base.utils
import android.app.Activity
import java.util.*
/**
* @Author: QuYunShuo
* @Time: 2020/9/11
* @Class: ActivityStackManager
* @Remark: Activity 栈管理类
*/
object ActivityStackManager {
// 管理栈
val activityStack by lazy { Stack<Activity>() }
/**
* 添加 Activity 到管理栈
* @param activity Activity
*/
fun addActivityToStack(activity: Activity) {
activityStack.push(activity)
}
/**
* 弹出栈内指定Activity 不finish
* @param activity Activity
*/
fun popActivityToStack(activity: Activity) {
if (!activityStack.empty()) {
activityStack.forEach {
if (it == activity) {
activityStack.remove(activity)
return
}
}
}
}
/**
* 返回到上一个 Activity 并结束当前 Activity
*/
fun backToPreviousActivity() {
if (!activityStack.empty()) {
val activity = activityStack.pop()
if (!activity.isFinishing) activity.finish()
}
}
/**
* 根据类名 判断是否是当前的 Activity
* @param cls Class<*> 类名
* @return Boolean
*/
fun isCurrentActivity(cls: Class<*>): Boolean {
val currentActivity = getCurrentActivity()
return if (currentActivity != null) currentActivity.javaClass == cls else false
}
/**
* 获取当前的 Activity
*/
fun getCurrentActivity(): Activity? =
if (!activityStack.empty()) activityStack.lastElement() else null
/**
* 结束一个栈内指定类名的 Activity
* @param cls Class<*>
*/
fun finishActivity(cls: Class<*>) {
activityStack.forEach {
if (it.javaClass == cls) {
if (!it.isFinishing) it.finish()
return
}
}
}
/**
* 弹出其他 Activity
*/
fun popOtherActivity() {
val activityList = activityStack.toList()
getCurrentActivity()?.run {
activityList.forEach { activity ->
if (this != activity) {
activityStack.remove(activity)
activity.finish()
}
}
}
}
/**
* 返回到指定 Activity
*/
fun backToSpecifyActivity(activityClass: Class<*>) {
val activityList = activityStack.toList()
// 获取栈最上面的Activity
val lastElement = activityStack.lastElement()
activityList.forEach {
// 如果栈内存在该Activity就进行下一步操作
if (it.javaClass == activityClass) {
// 判断最上面的Activity是不是指定的Activity 不是就finish
if (lastElement.javaClass == activityClass) {
return
} else {
activityStack.remove(lastElement)
lastElement.finish()
}
}
}
}
}

View File

@ -0,0 +1,35 @@
package com.quyunshuo.base.utils
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
/**
* @Author: QuYunShuo
* @Time: 2020/9/8
* @Class: DateUtils
* @Remark: 时间工具类
*/
object DateUtils {
/**
* 获取时间格式化String
* @param timestamp 时间戳
* @param dateFormat 日期格式
*/
fun getDateFormatString(timestamp: Long, dateFormat: String): String =
SimpleDateFormat(dateFormat, Locale.CHINESE).format(Date(timestamp))
/**
* 将固定格式[dateFormat]的时间字符串[dateString]转换为时间值
*/
fun getDateStringToDate(dateString: String, dateFormat: String): Long {
val simpleDateFormat = SimpleDateFormat(dateFormat, Locale.CHINESE)
var date = Date()
try {
date = simpleDateFormat.parse(dateString)
} catch (e: ParseException) {
e.printStackTrace()
}
return date.time
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,15 @@
package com.quyunshuo.base.utils
import android.annotation.SuppressLint
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkInfo
import android.os.Build
import android.util.Log
import android.widget.Toast
import com.alibaba.android.arouter.launcher.ARouter
import com.quyunshuo.base.BaseApplication
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
@ -13,8 +20,11 @@ import kotlinx.coroutines.flow.flowOn
* 以顶层函数存在的常用工具方法
* startPolling() -> 开启一个轮询
* sendEvent() -> 发送普通EventBus事件
* toastShow() -> Toast
* isNetworkAvailable() -> 检查是否连接网络
* aRouterJump() -> 阿里路由不带参数跳转
*/
/**************************************************************************************************/
/**
* 使用 Flow 做的简单的轮询
* 请使用单独的协程来进行管理该 Flow
@ -22,7 +32,6 @@ import kotlinx.coroutines.flow.flowOn
* @param intervals 轮询间隔时间/毫秒
* @param block 需要执行的代码块
*/
@InternalCoroutinesApi
suspend fun startPolling(intervals: Long, block: () -> Unit) {
flow {
while (true) {
@ -34,8 +43,62 @@ suspend fun startPolling(intervals: Long, block: () -> Unit) {
.flowOn(Dispatchers.Main)
.collect { block.invoke() }
}
/**************************************************************************************************/
/**
* 发送普通EventBus事件
*/
fun sendEvent(event: Any) = EventBusUtils.postEvent(event)
fun sendEvent(event: Any) = EventBusUtils.postEvent(event)
/**************************************************************************************************/
private var mToast: Toast? = null
/**
* Toast
* Android 9.0之上 已做优化
*/
fun toastShow(text: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Toast.makeText(BaseApplication.context, text, duration).show()
} else {
if (mToast != null) {
mToast?.setText(text)
mToast?.show()
} else {
mToast = Toast.makeText(BaseApplication.context, text, duration)
mToast?.show()
}
}
}
/**************************************************************************************************/
/**
* 判断是否连接网络
*/
@SuppressLint("MissingPermission")
fun isNetworkAvailable(): Boolean {
val connectivityManager: ConnectivityManager? =
BaseApplication.context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (connectivityManager == null) {
return false
} else {
val allNetworkInfo: Array<NetworkInfo>? = connectivityManager.allNetworkInfo
if (allNetworkInfo != null && allNetworkInfo.isNotEmpty()) {
allNetworkInfo.forEach {
if (it.state == NetworkInfo.State.CONNECTED) {
return true
}
}
}
}
return false
}
/**************************************************************************************************/
/**
* 阿里路由不带参数跳转
* @param routerUrl String 路由地址
*/
fun aRouterJump(routerUrl: String) {
ARouter.getInstance().build(routerUrl).navigation()
}