This commit is contained in:
HapeLee committed 2025-06-04 13:33:51 +08:00
1 parent b86d90021c
commit 6c0757c91b
972 files changed
+6885 -7321

No files matched your search

@@ -0,0 +1,99 @@
package io.legato.kazusa.base
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Build
import android.os.LocaleList
import io.legato.kazusa.constant.PreferKey
import io.legato.kazusa.utils.getPrefInt
import io.legato.kazusa.utils.getPrefString
import io.legato.kazusa.utils.sysConfiguration
import java.util.*
@Suppress("unused")
object AppContextWrapper {
@SuppressLint("ObsoleteSdkInt")
fun wrap(context: Context): Context {
val resources: Resources = context.resources
val configuration: Configuration = resources.configuration
val targetLocale = getSetLocale(context)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
configuration.setLocale(targetLocale)
configuration.setLocales(LocaleList(targetLocale))
} else {
@Suppress("DEPRECATION")
configuration.locale = targetLocale
}
configuration.fontScale = getFontScale(context)
return context.createConfigurationContext(configuration)
}
fun getFontScale(context: Context): Float {
var fontScale = context.getPrefInt(PreferKey.fontScale) / 10f
if (fontScale !in 0.8f..1.6f) {
fontScale = sysConfiguration.fontScale
}
return fontScale
}
/**
* 当前系统语言
*/
@SuppressLint("ObsoleteSdkInt")
private fun getSystemLocale(): Locale {
val locale: Locale
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //7.0有多语言设置获取顶部的语言
locale = sysConfiguration.locales.get(0)
} else {
@Suppress("DEPRECATION")
locale = sysConfiguration.locale
}
return locale
}
/**
* 当前App语言
*/
@SuppressLint("ObsoleteSdkInt")
private fun getAppLocale(context: Context): Locale {
val locale: Locale
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
locale = context.resources.configuration.locales[0]
} else {
@Suppress("DEPRECATION")
locale = context.resources.configuration.locale
}
return locale
}
/**
* 当前设置语言
*/
private fun getSetLocale(context: Context): Locale {
return when (context.getPrefString(PreferKey.language)) {
"zh" -> Locale.SIMPLIFIED_CHINESE
"tw" -> Locale.TRADITIONAL_CHINESE
"en" -> Locale.ENGLISH
else -> getSystemLocale()
}
}
/**
* 判断App语言和设置语言是否相同
*/
fun isSameWithSetting(context: Context): Boolean {
val locale = getAppLocale(context)
val language = locale.language
val country = locale.country
val pfLocale = getSetLocale(context)
val pfLanguage = pfLocale.language
val pfCountry = pfLocale.country
return language == pfLanguage && country == pfCountry
}
}
@@ -0,0 +1,208 @@
package io.legato.kazusa.base
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
import android.graphics.drawable.BitmapDrawable
import android.os.Build
import android.os.Bundle
import android.util.AttributeSet
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.widget.FrameLayout
import androidx.activity.addCallback
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding
import com.google.android.material.appbar.AppBarLayout
import io.legato.kazusa.R
import io.legato.kazusa.constant.AppConst
import io.legato.kazusa.constant.AppLog
import io.legato.kazusa.constant.Theme
import io.legato.kazusa.help.config.ThemeConfig
//import io.legado.app.lib.theme.backgroundColor
//import io.legado.app.lib.theme.primaryColor
import io.legato.kazusa.utils.applyOpenTint
import io.legato.kazusa.utils.applyTint
import io.legato.kazusa.utils.disableAutoFill
import io.legato.kazusa.utils.hideSoftInput
import io.legato.kazusa.utils.toastOnUi
import io.legato.kazusa.utils.windowSize
abstract class BaseActivity<VB : ViewBinding>(
val fullScreen: Boolean = true,
private val theme: Theme = Theme.Auto,
private val toolBarTheme: Theme = Theme.Auto,
private val transparent: Boolean = false,
private val imageBg: Boolean = true
) : AppCompatActivity() {
protected abstract val binding: VB
val isInMultiWindow: Boolean
@SuppressLint("ObsoleteSdkInt")
get() {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
isInMultiWindowMode
} else {
false
}
}
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(AppContextWrapper.wrap(newBase))
}
override fun onCreateView(
parent: View?,
name: String,
context: Context,
attrs: AttributeSet
): View? {
if (AppConst.menuViewNames.contains(name) && parent?.parent is FrameLayout) {
//(parent.parent as View).setBackgroundColor(backgroundColor)
}
return super.onCreateView(parent, name, context, attrs)
}
@SuppressLint("ObsoleteSdkInt")
override fun onCreate(savedInstanceState: Bundle?) {
window.decorView.disableAutoFill()
//initTheme()
super.onCreate(savedInstanceState)
//setupSystemBar()
setContentView(binding.root)
upBackgroundImage()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
findViewById<AppBarLayout>(R.id.title_bar)
//?.onMultiWindowModeChanged(isInMultiWindowMode, fullScreen)
}
onBackPressedDispatcher.addCallback(this) {
finish()
}
observeLiveBus()
onActivityCreated(savedInstanceState)
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean, newConfig: Configuration) {
super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig)
// findViewById<TitleBar>(R.id.title_bar)
// ?.onMultiWindowModeChanged(isInMultiWindowMode, fullScreen)
//setupSystemBar()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
//findViewById<TitleBar>(R.id.title_bar)
//?.onMultiWindowModeChanged(isInMultiWindow, fullScreen)
//setupSystemBar()
}
abstract fun onActivityCreated(savedInstanceState: Bundle?)
final override fun onCreateOptionsMenu(menu: Menu): Boolean {
val bool = onCompatCreateOptionsMenu(menu)
menu.applyTint(this, toolBarTheme)
return bool
}
override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {
menu.applyOpenTint(this)
return super.onMenuOpened(featureId, menu)
}
open fun onCompatCreateOptionsMenu(menu: Menu) = super.onCreateOptionsMenu(menu)
final override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
supportFinishAfterTransition()
return true
}
return onCompatOptionsItemSelected(item)
}
open fun onCompatOptionsItemSelected(item: MenuItem) = super.onOptionsItemSelected(item)
open fun initTheme() {
when (theme) {
Theme.Transparent -> setTheme(R.style.AppTheme_Transparent)
Theme.Dark -> {
setTheme(R.style.AppTheme_Dark)
//window.decorView.applyBackgroundTint(backgroundColor)
}
Theme.Light -> {
setTheme(R.style.AppTheme_Light)
//window.decorView.applyBackgroundTint(backgroundColor)
}
else -> {
// if (ColorUtils.isColorLight(primaryColor)) {
// setTheme(R.style.AppTheme_Light)
// } else {
// setTheme(R.style.AppTheme_Dark)
// }
//window.decorView.applyBackgroundTint(backgroundColor)
}
}
}
open fun upBackgroundImage() {
if (imageBg) {
try {
ThemeConfig.getBgImage(this, windowManager.windowSize)?.let {
window.decorView.background = BitmapDrawable(resources, it)
}
} catch (e: OutOfMemoryError) {
toastOnUi("背景图片太大,内存溢出")
} catch (e: Exception) {
AppLog.put("加载背景出错\n${e.localizedMessage}", e)
}
}
}
// open fun setupSystemBar() {
// if (fullScreen && !isInMultiWindow) {
// fullScreen()
// }
// //val isTransparentStatusBar = AppConfig.isTransparentStatusBar
// //val statusBarColor = ThemeStore.statusBarColor(this, isTransparentStatusBar)
// //setStatusBarColorAuto(statusBarColor, isTransparentStatusBar, fullScreen)
//// if (toolBarTheme == Theme.Dark) {
//// setLightStatusBar(false)
//// } else if (toolBarTheme == Theme.Light) {
//// setLightStatusBar(true)
//// }
// upNavigationBarColor()
// }
open fun upNavigationBarColor() {
// if (AppConfig.immNavigationBar) {
// setNavigationBarColorAuto(ThemeStore.navigationBarColor(this))
// } else {
// val nbColor = ColorUtils.darkenColor(ThemeStore.navigationBarColor(this))
// setNavigationBarColorAuto(nbColor)
// }
}
open fun observeLiveBus() {
}
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
return try {
super.dispatchTouchEvent(ev)
} catch (e: IllegalArgumentException) {
e.printStackTrace()
false
}
}
override fun finish() {
currentFocus?.hideSoftInput()
super.finish()
}
}
@@ -0,0 +1,66 @@
package io.legato.kazusa.base
import android.content.DialogInterface
import android.content.DialogInterface.OnDismissListener
import android.os.Bundle
import android.view.View
import androidx.annotation.LayoutRes
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.lifecycleScope
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import io.legato.kazusa.constant.AppLog
import io.legato.kazusa.help.coroutine.Coroutine
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlin.coroutines.CoroutineContext
abstract class BaseBottomSheetDialogFragment(
@LayoutRes layoutID: Int
) : BottomSheetDialogFragment(layoutID) {
private var onDismissListener: OnDismissListener? = null
fun setOnDismissListener(onDismissListener: OnDismissListener?) {
this.onDismissListener = onDismissListener
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.N) {
// setStyle(STYLE_NO_TITLE, 0)
// }
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
onFragmentCreated(view, savedInstanceState)
observeLiveBus()
}
abstract fun onFragmentCreated(view: View, savedInstanceState: Bundle?)
override fun show(manager: FragmentManager, tag: String?) {
kotlin.runCatching {
manager.beginTransaction().remove(this).commit()
super.show(manager, tag)
}.onFailure {
AppLog.put("显示BottomSheetDialog失败 tag:$tag\n$it")
}
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
onDismissListener?.onDismiss(dialog)
}
fun <T> execute(
scope: CoroutineScope = lifecycleScope,
context: CoroutineContext = Dispatchers.IO,
block: suspend CoroutineScope.() -> T
) = Coroutine.async(scope, context) { block() }
open fun observeLiveBus() {
}
}
@@ -0,0 +1,103 @@
package io.legato.kazusa.base
import android.content.DialogInterface
import android.content.DialogInterface.OnDismissListener
import android.os.Bundle
import android.view.View
import androidx.annotation.LayoutRes
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.lifecycleScope
import io.legato.kazusa.R
import io.legato.kazusa.constant.AppLog
import io.legato.kazusa.help.config.AppConfig
import io.legato.kazusa.help.coroutine.Coroutine
import io.legato.kazusa.lib.theme.ThemeStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlin.coroutines.CoroutineContext
abstract class BaseDialogFragment(
@LayoutRes layoutID: Int,
private val adaptationSoftKeyboard: Boolean = false
) : DialogFragment(layoutID) {
private var onDismissListener: OnDismissListener? = null
fun setOnDismissListener(onDismissListener: OnDismissListener?) {
this.onDismissListener = onDismissListener
}
override fun onStart() {
super.onStart()
// if (adaptationSoftKeyboard) {
// dialog?.window?.setBackgroundDrawableResource(R.color.transparent)
// } else if (AppConfig.isEInkMode) {
// dialog?.window?.let {
// it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
// val attr = it.attributes
// attr.dimAmount = 0.0f
// attr.windowAnimations = 0
// it.attributes = attr
// it.decorView.setBackgroundKeepPadding(R.color.transparent)
// }
// // 修改gravity的时机一般在子类的onStart方法中, 因此需要在onStart之后执行.
// lifecycle.addObserver(LifecycleEventObserver { _, event ->
// if (event == Lifecycle.Event.ON_START) {
// when (dialog?.window?.attributes?.gravity) {
// Gravity.TOP -> view?.setBackgroundResource(R.drawable.bg_eink_border_bottom)
// Gravity.BOTTOM -> view?.setBackgroundResource(R.drawable.bg_eink_border_top)
// else -> {
// val padding = 2.dpToPx();
// view?.setPadding(padding, padding, padding, padding)
// view?.setBackgroundResource(R.drawable.bg_eink_border_dialog)
// }
// }
// }
// })
// }
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (adaptationSoftKeyboard) {
view.findViewById<View>(R.id.vw_bg)?.setOnClickListener(null)
view.setOnClickListener { dismiss() }
} else if (!AppConfig.isEInkMode) {
view.setBackgroundColor(ThemeStore.backgroundColor())
}
onFragmentCreated(view, savedInstanceState)
observeLiveBus()
}
abstract fun onFragmentCreated(view: View, savedInstanceState: Bundle?)
override fun show(manager: FragmentManager, tag: String?) {
kotlin.runCatching {
//在每个add事务前增加一个remove事务,防止连续的add
manager.beginTransaction().remove(this).commit()
super.show(manager, tag)
}.onFailure {
AppLog.put("显示对话框失败 tag:$tag", it)
}
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
onDismissListener?.onDismiss(dialog)
}
fun <T> execute(
scope: CoroutineScope = lifecycleScope,
context: CoroutineContext = Dispatchers.IO,
block: suspend CoroutineScope.() -> T
) = Coroutine.async(scope, context) { block() }
open fun observeLiveBus() {
}
}
@@ -0,0 +1,76 @@
package io.legato.kazusa.base
import android.annotation.SuppressLint
import android.content.res.Configuration
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.annotation.LayoutRes
import androidx.appcompat.view.SupportMenuInflater
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.Fragment
import io.legato.kazusa.utils.applyTint
@Suppress("MemberVisibilityCanBePrivate")
abstract class BaseFragment(@LayoutRes layoutID: Int) : Fragment(layoutID) {
var supportToolbar: Toolbar? = null
private set
val menuInflater: MenuInflater
@SuppressLint("RestrictedApi")
get() = SupportMenuInflater(requireContext())
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
onMultiWindowModeChanged()
observeLiveBus()
onFragmentCreated(view, savedInstanceState)
}
abstract fun onFragmentCreated(view: View, savedInstanceState: Bundle?)
override fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean) {
super.onMultiWindowModeChanged(isInMultiWindowMode)
onMultiWindowModeChanged()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
onMultiWindowModeChanged()
}
private fun onMultiWindowModeChanged() {
// (activity as? BaseActivity<*>)?.let {
// view?.findViewById<TitleBar>(R.id.title_bar)
// ?.onMultiWindowModeChanged(it.isInMultiWindow, it.fullScreen)
// }
}
fun setSupportToolbar(toolbar: Toolbar) {
supportToolbar = toolbar
supportToolbar?.let {
it.menu.apply {
onCompatCreateOptionsMenu(this)
applyTint(requireContext())
}
it.setOnMenuItemClickListener { item ->
onCompatOptionsItemSelected(item)
true
}
}
}
open fun observeLiveBus() {
}
open fun onCompatCreateOptionsMenu(menu: Menu) {
}
open fun onCompatOptionsItemSelected(item: MenuItem) {
}
}
@@ -0,0 +1,39 @@
package io.legato.kazusa.base
import androidx.lifecycle.LifecycleEventObserver
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import io.legato.kazusa.help.config.AppConfig
abstract class BasePrefDialogFragment(
) : BottomSheetDialogFragment() {
override fun onStart() {
super.onStart()
if (AppConfig.isEInkMode) {
// dialog?.window?.let {
// it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
// val attr = it.attributes
// attr.dimAmount = 0.0f
// attr.windowAnimations = 0
// it.attributes = attr
// it.setBackgroundDrawableResource(R.color.transparent)
// }
// 修改gravity的时机一般在子类的onStart方法中, 因此需要在onStart之后执行.
lifecycle.addObserver(LifecycleEventObserver { _, event ->
// if (event == Lifecycle.Event.ON_START) {
// when (dialog?.window?.attributes?.gravity) {
// Gravity.TOP -> view?.setBackgroundResource(R.drawable.bg_eink_border_bottom)
// Gravity.BOTTOM -> view?.setBackgroundResource(R.drawable.bg_eink_border_top)
// else -> {
// val padding = 2.dpToPx();
// view?.setPadding(padding, padding, padding, padding)
// view?.setBackgroundResource(R.drawable.bg_eink_border_dialog)
// }
// }
// }
})
}
}
}
@@ -0,0 +1,107 @@
package io.legato.kazusa.base
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.annotation.CallSuper
import androidx.lifecycle.LifecycleService
import androidx.lifecycle.lifecycleScope
import io.legato.kazusa.R
import io.legato.kazusa.help.LifecycleHelp
import io.legato.kazusa.help.coroutine.Coroutine
import io.legato.kazusa.lib.permission.Permissions
import io.legato.kazusa.lib.permission.PermissionsCompat
import io.legato.kazusa.utils.LogUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.sync.Semaphore
import kotlin.coroutines.CoroutineContext
abstract class BaseService : LifecycleService() {
private val simpleName = this::class.simpleName.toString()
private var isForeground = false
fun <T> execute(
scope: CoroutineScope = lifecycleScope,
context: CoroutineContext = Dispatchers.IO,
start: CoroutineStart = CoroutineStart.DEFAULT,
executeContext: CoroutineContext = Dispatchers.Main,
semaphore: Semaphore? = null,
block: suspend CoroutineScope.() -> T
) = Coroutine.async(scope, context, start, executeContext, semaphore, block)
@CallSuper
override fun onCreate() {
super.onCreate()
LifecycleHelp.onServiceCreate(this)
checkPermission()
}
@CallSuper
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
LogUtils.d(simpleName) {
"onStartCommand $intent ${intent?.toUri(0)}"
}
if (!isForeground) {
startForegroundNotification()
isForeground = true
}
return super.onStartCommand(intent, flags, startId)
}
@CallSuper
override fun onTaskRemoved(rootIntent: Intent?) {
LogUtils.d(simpleName, "onTaskRemoved")
super.onTaskRemoved(rootIntent)
stopSelf()
}
override fun onBind(intent: Intent): IBinder? {
super.onBind(intent)
return null
}
@CallSuper
override fun onDestroy() {
super.onDestroy()
LifecycleHelp.onServiceDestroy(this)
}
@CallSuper
override fun onTimeout(startId: Int) {
super.onTimeout(startId)
LogUtils.d(simpleName, "onTimeout startId:$startId")
stopSelf()
}
/**
* 开启前台服务并发送通知
*/
open fun startForegroundNotification() {
}
/**
* 检测通知权限和后台权限
*/
private fun checkPermission() {
PermissionsCompat.Builder()
.addPermissions(Permissions.POST_NOTIFICATIONS)
.rationale(R.string.notification_permission_rationale)
.onGranted {
if (lifecycleScope.isActive) {
startForegroundNotification()
}
}
.request()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PermissionsCompat.Builder()
.addPermissions(Permissions.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
.rationale(R.string.ignore_battery_permission_rationale)
.request()
}
}
}
@@ -0,0 +1,52 @@
package io.legato.kazusa.base
import android.app.Application
import android.content.Context
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import io.legato.kazusa.App
import io.legato.kazusa.help.coroutine.Coroutine
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Semaphore
import kotlin.coroutines.CoroutineContext
@Suppress("unused")
open class BaseViewModel(application: Application) : AndroidViewModel(application) {
val context: Context by lazy { this.getApplication<App>() }
fun <T> execute(
scope: CoroutineScope = viewModelScope,
context: CoroutineContext = Dispatchers.IO,
start: CoroutineStart = CoroutineStart.DEFAULT,
executeContext: CoroutineContext = Dispatchers.Main,
semaphore: Semaphore? = null,
block: suspend CoroutineScope.() -> T
): Coroutine<T> {
return Coroutine.async(scope, context, start, executeContext, semaphore, block)
}
fun <T> executeLazy(
scope: CoroutineScope = viewModelScope,
context: CoroutineContext = Dispatchers.IO,
executeContext: CoroutineContext = Dispatchers.Main,
semaphore: Semaphore? = null,
block: suspend CoroutineScope.() -> T
): Coroutine<T> {
return Coroutine.async(
scope, context, CoroutineStart.LAZY, executeContext, semaphore, block
)
}
fun <R> submit(
scope: CoroutineScope = viewModelScope,
context: CoroutineContext = Dispatchers.IO,
block: suspend CoroutineScope.() -> Deferred<R>
): Coroutine<R> {
return Coroutine.async(scope, context) { block().await() }
}
}
@@ -0,0 +1 @@
# 基类
@@ -0,0 +1,17 @@
package io.legato.kazusa.base
import androidx.lifecycle.ViewModel
import androidx.viewbinding.ViewBinding
import io.legato.kazusa.constant.Theme
abstract class VMBaseActivity<VB : ViewBinding, VM : ViewModel>(
fullScreen: Boolean = true,
theme: Theme = Theme.Auto,
toolBarTheme: Theme = Theme.Auto,
transparent: Boolean = false,
imageBg: Boolean = true
) : BaseActivity<VB>(fullScreen, theme, toolBarTheme, transparent, imageBg) {
protected abstract val viewModel: VM
}
@@ -0,0 +1,9 @@
package io.legato.kazusa.base
import androidx.lifecycle.ViewModel
abstract class VMBaseFragment<VM : ViewModel>(layoutID: Int) : BaseFragment(layoutID) {
protected abstract val viewModel: VM
}
@@ -0,0 +1,223 @@
package io.legato.kazusa.base.adapter
import android.content.Context
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import splitties.views.onLongClick
/**
* Created by Invincible on 2017/12/15.
*/
@Suppress("unused", "MemberVisibilityCanBePrivate")
abstract class DiffRecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Context) :
RecyclerView.Adapter<ItemViewHolder>() {
val inflater: LayoutInflater = LayoutInflater.from(context)
private val asyncListDiffer: AsyncListDiffer<ITEM> by lazy {
AsyncListDiffer(this, diffItemCallback).apply {
addListListener { _, _ ->
onCurrentListChanged()
if (keepScrollPosition) {
layoutManager?.onRestoreInstanceState(layoutState)
layoutState = null
}
}
}
}
private var itemClickListener: ((holder: ItemViewHolder, item: ITEM) -> Unit)? = null
private var itemLongClickListener: ((holder: ItemViewHolder, item: ITEM) -> Boolean)? = null
private var layoutManager: RecyclerView.LayoutManager? = null
private var layoutState: Parcelable? = null
var itemAnimation: ItemAnimation? = null
abstract val diffItemCallback: DiffUtil.ItemCallback<ITEM>
open val keepScrollPosition = false
fun setOnItemClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Unit) {
itemClickListener = listener
}
fun setOnItemLongClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Boolean) {
itemLongClickListener = listener
}
fun bindToRecyclerView(recyclerView: RecyclerView) {
recyclerView.adapter = this
}
fun setItems(items: List<ITEM>?) {
kotlin.runCatching {
if (keepScrollPosition) {
layoutState = layoutManager?.onSaveInstanceState()
}
asyncListDiffer.submitList(items?.toMutableList())
}
}
fun setItem(position: Int, item: ITEM) {
kotlin.runCatching {
asyncListDiffer.currentList[position] = item
notifyItemChanged(position)
}
}
fun updateItem(item: ITEM) {
kotlin.runCatching {
val index = asyncListDiffer.currentList.indexOf(item)
if (index >= 0) {
asyncListDiffer.currentList[index] = item
notifyItemChanged(index)
}
}
}
fun updateItem(position: Int, payload: Any) {
kotlin.runCatching {
val size = itemCount
if (position in 0 until size) {
notifyItemChanged(position, payload)
}
}
}
fun updateItems(fromPosition: Int, toPosition: Int, payloads: Any) {
kotlin.runCatching {
val size = itemCount
if (fromPosition in 0 until size && toPosition in 0 until size) {
notifyItemRangeChanged(
fromPosition,
toPosition - fromPosition + 1,
payloads
)
}
}
}
fun isEmpty() = asyncListDiffer.currentList.isEmpty()
fun isNotEmpty() = asyncListDiffer.currentList.isNotEmpty()
fun getItem(position: Int): ITEM? = asyncListDiffer.currentList.getOrNull(position)
fun getItems(): List<ITEM> = asyncListDiffer.currentList
/**
* grid 模式下使用
*/
protected open fun getSpanSize(viewType: Int, position: Int) = 1
final override fun getItemCount() = getItems().size
final override fun getItemViewType(position: Int): Int {
return 0
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
return ItemViewHolder(getViewBinding(parent))
}
protected abstract fun getViewBinding(parent: ViewGroup): VB
final override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {}
open fun onCurrentListChanged() {
//可继承
}
@Suppress("UNCHECKED_CAST")
final override fun onBindViewHolder(
holder: ItemViewHolder,
position: Int,
payloads: MutableList<Any>
) {
registerListener(holder, (holder.binding as VB))
registerItemListener(holder)
getItem(holder.layoutPosition)?.let {
convert(holder, holder.binding, it, payloads)
}
}
private fun registerItemListener(holder: ItemViewHolder) {
if (itemClickListener != null) {
holder.itemView.setOnClickListener {
getItem(holder.layoutPosition)?.let {
itemClickListener?.invoke(holder, it)
}
}
}
if (itemLongClickListener != null) {
holder.itemView.onLongClick {
getItem(holder.layoutPosition)?.let {
itemLongClickListener?.invoke(holder, it)
}
}
}
}
override fun onViewAttachedToWindow(holder: ItemViewHolder) {
super.onViewAttachedToWindow(holder)
addAnimation(holder)
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
val manager = recyclerView.layoutManager
layoutManager = manager
if (manager is GridLayoutManager) {
manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return getSpanSize(getItemViewType(position), position)
}
}
}
}
private fun addAnimation(holder: ItemViewHolder) {
itemAnimation?.let {
if (it.itemAnimEnabled) {
if (!it.itemAnimFirstOnly || holder.layoutPosition > it.itemAnimStartPosition) {
startAnimation(holder, it)
it.itemAnimStartPosition = holder.layoutPosition
}
}
}
}
protected open fun startAnimation(holder: ItemViewHolder, item: ItemAnimation) {
item.itemAnimation?.let {
for (anim in it.getAnimators(holder.itemView)) {
anim.setDuration(item.itemAnimDuration).start()
anim.interpolator = item.itemAnimInterpolator
}
}
}
/**
* 如果使用了事件回调,回调里不要直接使用item,会出现不更新的问题,
* 使用getItem(holder.layoutPosition)来获取item
*/
abstract fun convert(
holder: ItemViewHolder,
binding: VB,
item: ITEM,
payloads: MutableList<Any>
)
/**
* 注册事件
*/
abstract fun registerListener(holder: ItemViewHolder, binding: VB)
}
@@ -0,0 +1,85 @@
package io.legato.kazusa.base.adapter
import android.view.animation.Interpolator
import android.view.animation.LinearInterpolator
import io.legato.kazusa.base.adapter.animations.*
/**
* Created by Invincible on 2017/12/15.
*/
@Suppress("unused")
class ItemAnimation private constructor() {
var itemAnimEnabled = false
var itemAnimFirstOnly = true
var itemAnimation: BaseAnimation? = null
var itemAnimInterpolator: Interpolator = LinearInterpolator()
var itemAnimDuration: Long = 300L
var itemAnimStartPosition: Int = -1
fun interpolator(interpolator: Interpolator) = apply {
itemAnimInterpolator = interpolator
}
fun duration(duration: Long) = apply {
itemAnimDuration = duration
}
fun startPosition(startPos: Int) = apply {
itemAnimStartPosition = startPos
}
fun animation(animationType: Int = NONE, animation: BaseAnimation? = null) = apply {
if (animation != null) {
itemAnimation = animation
} else {
when (animationType) {
FADE_IN -> itemAnimation = AlphaInAnimation()
SCALE_IN -> itemAnimation = ScaleInAnimation()
BOTTOM_SLIDE_IN -> itemAnimation = SlideInBottomAnimation()
LEFT_SLIDE_IN -> itemAnimation = SlideInLeftAnimation()
RIGHT_SLIDE_IN -> itemAnimation = SlideInRightAnimation()
}
}
}
fun enabled(enabled: Boolean) = apply {
itemAnimEnabled = enabled
}
fun firstOnly(firstOnly: Boolean) = apply {
itemAnimFirstOnly = firstOnly
}
companion object {
const val NONE: Int = 0x00000000
/**
* Use with [.openLoadAnimation]
*/
const val FADE_IN: Int = 0x00000001
/**
* Use with [.openLoadAnimation]
*/
const val SCALE_IN: Int = 0x00000002
/**
* Use with [.openLoadAnimation]
*/
const val BOTTOM_SLIDE_IN: Int = 0x00000003
/**
* Use with [.openLoadAnimation]
*/
const val LEFT_SLIDE_IN: Int = 0x00000004
/**
* Use with [.openLoadAnimation]
*/
const val RIGHT_SLIDE_IN: Int = 0x00000005
fun create() = ItemAnimation()
}
}
@@ -0,0 +1,10 @@
package io.legato.kazusa.base.adapter
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
/**
* Created by Invincible on 2017/11/28.
*/
@Suppress("MemberVisibilityCanBePrivate")
class ItemViewHolder(val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root)
@@ -0,0 +1,486 @@
package io.legato.kazusa.base.adapter
import android.annotation.SuppressLint
import android.content.Context
import android.util.SparseArray
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import io.legato.kazusa.help.coroutine.Coroutine
import io.legato.kazusa.utils.buildMainHandler
import io.legato.kazusa.utils.withTimeoutOrNullAsync
import kotlinx.coroutines.ensureActive
import splitties.views.onLongClick
import java.util.Collections
/**
* Created by Invincible on 2017/11/24.
*
* 通用的adapter 可添加header,footer,以及不同类型item
*/
@Suppress("unused", "MemberVisibilityCanBePrivate")
abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Context) :
RecyclerView.Adapter<ItemViewHolder>() {
val inflater: LayoutInflater = LayoutInflater.from(context)
private val headerItems: SparseArray<(parent: ViewGroup) -> ViewBinding> by lazy { SparseArray() }
private val footerItems: SparseArray<(parent: ViewGroup) -> ViewBinding> by lazy { SparseArray() }
private val items: MutableList<ITEM> = mutableListOf()
private var itemClickListener: ((holder: ItemViewHolder, item: ITEM) -> Unit)? = null
private var itemLongClickListener: ((holder: ItemViewHolder, item: ITEM) -> Boolean)? = null
private var diffJob: Coroutine<*>? = null
var itemAnimation: ItemAnimation? = null
fun setOnItemClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Unit) {
itemClickListener = listener
}
fun setOnItemLongClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Boolean) {
itemLongClickListener = listener
}
fun bindToRecyclerView(recyclerView: RecyclerView) {
recyclerView.adapter = this
}
@Synchronized
fun addHeaderView(header: ((parent: ViewGroup) -> ViewBinding)) {
kotlin.runCatching {
val index = headerItems.size()
headerItems.put(TYPE_HEADER_VIEW + headerItems.size(), header)
notifyItemInserted(index)
}
}
@Synchronized
fun addFooterView(footer: ((parent: ViewGroup) -> ViewBinding)) {
kotlin.runCatching {
val index = getActualItemCount() + footerItems.size()
footerItems.put(TYPE_FOOTER_VIEW + footerItems.size(), footer)
notifyItemInserted(index)
}
}
@Synchronized
fun removeHeaderView(header: ((parent: ViewGroup) -> ViewBinding)) {
kotlin.runCatching {
val index = headerItems.indexOfValue(header)
if (index >= 0) {
headerItems.remove(index)
notifyItemRemoved(index)
}
}
}
@Synchronized
fun removeFooterView(footer: ((parent: ViewGroup) -> ViewBinding)) {
kotlin.runCatching {
val index = footerItems.indexOfValue(footer)
if (index >= 0) {
footerItems.remove(index)
notifyItemRemoved(getActualItemCount() + index - 2)
}
}
}
@SuppressLint("NotifyDataSetChanged")
@Synchronized
fun setItems(items: List<ITEM>?) {
kotlin.runCatching {
if (this.items.isNotEmpty()) {
this.items.clear()
}
if (items != null) {
this.items.addAll(items)
}
notifyDataSetChanged()
onCurrentListChanged()
}
}
@Synchronized
fun setItems(
items: List<ITEM>?,
itemCallback: DiffUtil.ItemCallback<ITEM>,
skipDiff: Boolean = false
) {
kotlin.runCatching {
val oldItems = this.items.toList()
val itemsSize = items?.size ?: 0
val headerCount = getHeaderCount()
val footerCount = getFooterCount()
val callback = object : DiffUtil.Callback() {
override fun getOldListSize(): Int {
return itemCount
}
override fun getNewListSize(): Int {
return itemsSize + headerCount + footerCount
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems.getOrNull(oldItemPosition - headerCount)
?: return true
val newItem = items?.getOrNull(newItemPosition - headerCount)
?: return true
return itemCallback.areItemsTheSame(oldItem, newItem)
}
override fun areContentsTheSame(
oldItemPosition: Int,
newItemPosition: Int
): Boolean {
val oldItem = oldItems.getOrNull(oldItemPosition - headerCount)
?: return true
val newItem = items?.getOrNull(newItemPosition - headerCount)
?: return true
return itemCallback.areContentsTheSame(oldItem, newItem)
}
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
val oldItem = oldItems.getOrNull(oldItemPosition - headerCount)
?: return null
val newItem = items?.getOrNull(newItemPosition - headerCount)
?: return null
return itemCallback.getChangePayload(oldItem, newItem)
}
}
diffJob?.cancel()
diffJob = Coroutine.async {
val diffResult = if (skipDiff) withTimeoutOrNullAsync(500L) {
DiffUtil.calculateDiff(callback, itemsSize < 2000)
} else {
DiffUtil.calculateDiff(callback, itemsSize < 2000)
}
ensureActive()
handler.post {
if (diffResult == null) {
setItems(items)
return@post
}
if (this@RecyclerAdapter.items.isNotEmpty()) {
this@RecyclerAdapter.items.clear()
}
if (items != null) {
this@RecyclerAdapter.items.addAll(items)
}
diffResult.dispatchUpdatesTo(this@RecyclerAdapter)
onCurrentListChanged()
}
}
}
}
@Synchronized
fun setItem(position: Int, item: ITEM) {
kotlin.runCatching {
val oldSize = getActualItemCount()
if (position in 0 until oldSize) {
this.items[position] = item
notifyItemChanged(position + getHeaderCount())
}
onCurrentListChanged()
}
}
@Synchronized
fun addItem(item: ITEM) {
kotlin.runCatching {
val oldSize = getActualItemCount()
if (this.items.add(item)) {
notifyItemInserted(oldSize + getHeaderCount())
}
onCurrentListChanged()
}
}
@Synchronized
fun addItems(position: Int, newItems: List<ITEM>) {
kotlin.runCatching {
if (this.items.addAll(position, newItems)) {
notifyItemRangeInserted(position + getHeaderCount(), newItems.size)
}
onCurrentListChanged()
}
}
@SuppressLint("NotifyDataSetChanged")
@Synchronized
fun addItems(newItems: List<ITEM>) {
kotlin.runCatching {
val oldSize = getActualItemCount()
if (this.items.addAll(newItems)) {
if (oldSize == 0 && getHeaderCount() == 0) {
notifyDataSetChanged()
} else {
notifyItemRangeInserted(oldSize + getHeaderCount(), newItems.size)
}
}
onCurrentListChanged()
}
}
@Synchronized
fun removeItem(position: Int) {
kotlin.runCatching {
if (this.items.removeAt(position) != null) {
notifyItemRemoved(position + getHeaderCount())
}
onCurrentListChanged()
}
}
@Synchronized
fun removeItem(item: ITEM) {
kotlin.runCatching {
if (this.items.remove(item)) {
notifyItemRemoved(this.items.indexOf(item) + getHeaderCount())
}
onCurrentListChanged()
}
}
@SuppressLint("NotifyDataSetChanged")
@Synchronized
fun removeItems(items: List<ITEM>) {
kotlin.runCatching {
if (this.items.removeAll(items)) {
notifyDataSetChanged()
}
onCurrentListChanged()
}
}
@Synchronized
fun swapItem(oldPosition: Int, newPosition: Int) {
kotlin.runCatching {
val size = getActualItemCount()
if (oldPosition in 0 until size && newPosition in 0 until size) {
val srcPosition = oldPosition + getHeaderCount()
val targetPosition = newPosition + getHeaderCount()
Collections.swap(this.items, srcPosition, targetPosition)
notifyItemMoved(srcPosition, targetPosition)
}
onCurrentListChanged()
}
}
@Synchronized
fun updateItem(item: ITEM) {
kotlin.runCatching {
val index = this.items.indexOf(item)
if (index >= 0) {
this.items[index] = item
notifyItemChanged(index)
}
onCurrentListChanged()
}
}
@Synchronized
fun updateItem(position: Int, payload: Any) {
kotlin.runCatching {
val size = getActualItemCount()
if (position in 0 until size) {
notifyItemChanged(position + getHeaderCount(), payload)
}
}
}
@Synchronized
fun updateItems(fromPosition: Int, toPosition: Int, payloads: Any) {
kotlin.runCatching {
val size = getActualItemCount()
if (fromPosition in 0 until size && toPosition in 0 until size) {
notifyItemRangeChanged(
fromPosition + getHeaderCount(),
toPosition - fromPosition + 1,
payloads
)
}
}
}
@SuppressLint("NotifyDataSetChanged")
@Synchronized
fun clearItems() {
kotlin.runCatching {
this.items.clear()
notifyDataSetChanged()
onCurrentListChanged()
}
}
fun isEmpty() = items.isEmpty()
fun isNotEmpty() = items.isNotEmpty()
/**
* 除去header和footer
*/
fun getActualItemCount() = items.size
fun getHeaderCount() = headerItems.size()
fun getFooterCount() = footerItems.size()
fun getItem(position: Int): ITEM? = items.getOrNull(position)
fun getItemByLayoutPosition(position: Int) = items.getOrNull(getActualPosition(position))
fun getItems(): List<ITEM> = items.toList()
protected open fun getItemViewType(item: ITEM, position: Int) = 0
/**
* grid 模式下使用
*/
protected open fun getSpanSize(viewType: Int, position: Int) = 1
final override fun getItemCount() = getActualItemCount() + getHeaderCount() + getFooterCount()
final override fun getItemViewType(position: Int) = when {
isHeader(position) -> TYPE_HEADER_VIEW + position
isFooter(position) -> TYPE_FOOTER_VIEW + position - getActualItemCount() - getHeaderCount()
else -> getItemByLayoutPosition(position)?.let {
getItemViewType(it, getActualPosition(position))
} ?: 0
}
open fun onCurrentListChanged() {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when {
viewType < TYPE_HEADER_VIEW + getHeaderCount() -> {
ItemViewHolder(headerItems.get(viewType).invoke(parent))
}
viewType >= TYPE_FOOTER_VIEW -> {
ItemViewHolder(footerItems.get(viewType).invoke(parent))
}
else -> {
ItemViewHolder(getViewBinding(parent))
}
}
protected abstract fun getViewBinding(parent: ViewGroup): VB
final override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {}
@Suppress("UNCHECKED_CAST")
final override fun onBindViewHolder(
holder: ItemViewHolder,
position: Int,
payloads: MutableList<Any>
) {
if (!isHeader(holder.layoutPosition) && !isFooter(holder.layoutPosition)) {
registerListener(holder, (holder.binding as VB))
registerItemListener(holder)
getItemByLayoutPosition(holder.layoutPosition)?.let { item ->
convert(holder, holder.binding, item, payloads)
}
}
}
private fun registerItemListener(holder: ItemViewHolder) {
if (itemClickListener != null) {
holder.itemView.setOnClickListener {
getItemByLayoutPosition(holder.layoutPosition)?.let {
itemClickListener?.invoke(holder, it)
}
}
}
if (itemLongClickListener != null) {
holder.itemView.onLongClick {
getItemByLayoutPosition(holder.layoutPosition)?.let {
itemLongClickListener?.invoke(holder, it)
}
}
}
}
override fun onViewAttachedToWindow(holder: ItemViewHolder) {
super.onViewAttachedToWindow(holder)
if (!isHeader(holder.layoutPosition) && !isFooter(holder.layoutPosition)) {
addAnimation(holder)
}
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
val manager = recyclerView.layoutManager
if (manager is GridLayoutManager) {
manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return getSpanSize(getItemViewType(position), position)
}
}
}
}
private fun isHeader(position: Int) = position < getHeaderCount()
private fun isFooter(position: Int) = position >= getActualItemCount() + getHeaderCount()
private fun getActualPosition(position: Int) = position - getHeaderCount()
private fun addAnimation(holder: ItemViewHolder) {
itemAnimation?.let {
if (it.itemAnimEnabled) {
if (!it.itemAnimFirstOnly || holder.layoutPosition > it.itemAnimStartPosition) {
startAnimation(holder, it)
it.itemAnimStartPosition = holder.layoutPosition
}
}
}
}
protected open fun startAnimation(holder: ItemViewHolder, item: ItemAnimation) {
item.itemAnimation?.let {
for (anim in it.getAnimators(holder.itemView)) {
anim.setDuration(item.itemAnimDuration).start()
anim.interpolator = item.itemAnimInterpolator
}
}
}
/**
* 如果使用了事件回调,回调里不要直接使用item,会出现不更新的问题,
* 使用getItem(holder.layoutPosition)来获取item
*/
abstract fun convert(
holder: ItemViewHolder,
binding: VB,
item: ITEM,
payloads: MutableList<Any>
)
/**
* 注册事件
*/
abstract fun registerListener(holder: ItemViewHolder, binding: VB)
companion object {
private const val TYPE_HEADER_VIEW = Int.MIN_VALUE
const val TYPE_FOOTER_VIEW = Int.MAX_VALUE - 999
private val handler by lazy { buildMainHandler() }
}
}
@@ -0,0 +1,18 @@
package io.legato.kazusa.base.adapter.animations
import android.animation.Animator
import android.animation.ObjectAnimator
import android.view.View
class AlphaInAnimation @JvmOverloads constructor(private val mFrom: Float = DEFAULT_ALPHA_FROM) :
BaseAnimation {
override fun getAnimators(view: View): Array<Animator> =
arrayOf(ObjectAnimator.ofFloat(view, "alpha", mFrom, 1f))
companion object {
private const val DEFAULT_ALPHA_FROM = 0f
}
}
@@ -0,0 +1,13 @@
package io.legato.kazusa.base.adapter.animations
import android.animation.Animator
import android.view.View
/**
* adapter item 动画
*/
interface BaseAnimation {
fun getAnimators(view: View): Array<Animator>
}
@@ -0,0 +1,21 @@
package io.legato.kazusa.base.adapter.animations
import android.animation.Animator
import android.animation.ObjectAnimator
import android.view.View
class ScaleInAnimation @JvmOverloads constructor(private val mFrom: Float = DEFAULT_SCALE_FROM) :
BaseAnimation {
override fun getAnimators(view: View): Array<Animator> {
val scaleX = ObjectAnimator.ofFloat(view, "scaleX", mFrom, 1f)
val scaleY = ObjectAnimator.ofFloat(view, "scaleY", mFrom, 1f)
return arrayOf(scaleX, scaleY)
}
companion object {
private const val DEFAULT_SCALE_FROM = .5f
}
}
@@ -0,0 +1,12 @@
package io.legato.kazusa.base.adapter.animations
import android.animation.Animator
import android.animation.ObjectAnimator
import android.view.View
class SlideInBottomAnimation : BaseAnimation {
override fun getAnimators(view: View): Array<Animator> =
arrayOf(ObjectAnimator.ofFloat(view, "translationY", view.measuredHeight.toFloat(), 0f))
}
@@ -0,0 +1,13 @@
package io.legato.kazusa.base.adapter.animations
import android.animation.Animator
import android.animation.ObjectAnimator
import android.view.View
class SlideInLeftAnimation : BaseAnimation {
override fun getAnimators(view: View): Array<Animator> =
arrayOf(ObjectAnimator.ofFloat(view, "translationX", -view.rootView.width.toFloat(), 0f))
}
@@ -0,0 +1,13 @@
package io.legato.kazusa.base.adapter.animations
import android.animation.Animator
import android.animation.ObjectAnimator
import android.view.View
class SlideInRightAnimation : BaseAnimation {
override fun getAnimators(view: View): Array<Animator> =
arrayOf(ObjectAnimator.ofFloat(view, "translationX", view.rootView.width.toFloat(), 0f))
}