常见的Android工具
提示:本系列文章处于动态更新当中; 基于我个人的使用习惯, 每一个文件可以单独拿出使用, 没有特别提出的前提下, 默认不依赖第三方框架, 即将object的所有代码复制到Android Studio中即可运行.
文章目录
1、字符串工具.
import androidx.annotation.StringRes
import java.util.IllegalFormatException
object StringUtil {
// 返回字符串是否为空或者长度为0
fun isEmpty(s: CharSequence?): Boolean = s.isNullOrEmpty()
// 返回字符串是否为空或者为空白字符
fun isTrimEmpty(s: CharSequence?): Boolean = s.isNullOrBlank()
// 比较两个字符串是否相同
fun equals(s1: CharSequence?, s2: CharSequence?): Boolean {
if (s1 == null || s2 == null) return false
if (s1 == s2) return true
if (s1.length != s2.length) return false
return when {
s1 is String && s2 is String -> s1 == s2
else -> s1.toString() == s2.toString()
}
}
// 忽略大小写的前提下比较两个字符串是否相同
fun equalsIgnoreCase(s1: String?, s2: String?): Boolean = s1.equals(s2, ignoreCase = true)
// 将null转换为空字符
fun nullToLength0(s: String?): String = s.orEmpty()
// 返回字符串的长度
fun length(s: CharSequence?): Int = s?.length ?: 0
// 将首字母大写
fun upperFirstLetter(s: String?): String {
if (s.isNullOrEmpty()) return ""
return s.replaceFirstChar {
if (it.isLowerCase()) it.titlecase()
else it.toString()
}
}
// 将首字母小写
fun lowerFirstLetter(s: String?): String {
if (s.isNullOrEmpty()) return ""
return s.replaceFirstChar {
it.lowercase()
}
}
// 反转
fun reverse(s: String?): String = s?.reversed() ?: ""
// 转为半角
fun toDBC(s: String?): String {
if (s.isNullOrEmpty()) return ""
val chars = s.toCharArray()
for (i in chars.indices) {
when {
chars[i] == 12288.toChar() -> chars[i] = ' '
chars[i] in 65281.toChar()..65374.toChar() -> chars[i] = (chars[i] - 65248).toChar()
}
}
return String(chars)
}
// 转为全角
fun toSBC(s: String?): String {
if (s.isNullOrEmpty()) return ""
val chars = s.toCharArray()
for (i in chars.indices) {
when {
chars[i] == ' ' -> chars[i] = 12288.toChar()
chars[i] in '!'..'~' -> chars[i] = (chars[i] + 65248).toChar()
}
}
return String(chars)
}
// 格式化
fun format(str: String?, vararg args: Any?): String {
return str?.let {
if (args.isNotEmpty()) {
try {
it.format(*args)
} catch (e: IllegalFormatException) {
e.printStackTrace()
it
}
} else {
it
}
} ?: ""
}
}
2、尺寸转换及测量工具.
import android.content.res.Resources
import android.os.Build
import android.util.DisplayMetrics
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
// 尺寸转换和测量工具类
object SizeUtil {
// dp转px
fun dp2px(dpValue: Float): Int {
val scale = Resources.getSystem().displayMetrics.density
return (dpValue * scale + 0.50F).toInt()
}
// px转dp
fun px2dp(pxValue: Float): Int {
val scale = Resources.getSystem().displayMetrics.density
return (pxValue / scale + 0.50F).toInt()
}
// sp转px
fun sp2px(spValue: Float): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
spValue,
Resources.getSystem().displayMetrics
).toInt()
}
// px转sp
fun px2sp(pxValue: Float): Int {
val metrics = Resources.getSystem().displayMetrics
return (pxValue / metrics.scaledDensityCompat + 0.50F).toInt()
}
private val DisplayMetrics.scaledDensityCompat: Float
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
computeScaledDensity()
} else {
@Suppress("DEPRECATION")
scaledDensity
}
@RequiresApi(Build.VERSION_CODES.S)
private fun DisplayMetrics.computeScaledDensity(): Float {
return if (Resources.getSystem().configuration.fontScale != 1.0f) {
density * Resources.getSystem().configuration.fontScale
} else {
density
}
}
// 任意单位转换为px值
fun applyDimension(value: Float, unit: Int): Float {
val metrics = Resources.getSystem().displayMetrics
return when (unit) {
TypedValue.COMPLEX_UNIT_PX -> value
TypedValue.COMPLEX_UNIT_DIP -> value * metrics.density
TypedValue.COMPLEX_UNIT_SP -> value * metrics.scaledDensity
TypedValue.COMPLEX_UNIT_PT -> value * metrics.xdpi * (1.0f / 72)
TypedValue.COMPLEX_UNIT_IN -> value * metrics.xdpi
TypedValue.COMPLEX_UNIT_MM -> value * metrics.xdpi * (1.0f / 25.4f)
else -> 0f
}
}
// 异步方式强制获取视图尺寸
interface OnGetSizeListener {
fun onGetSize(view: View)
}
fun forceGetViewSize(view: View, listener: OnGetSizeListener?) {
view.post {
listener?.onGetSize(view)
}
}
// 获取视图的测量宽高
private fun measureView(view: View): IntArray {
val lp = view.layoutParams ?: ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
val widthSpec = ViewGroup.getChildMeasureSpec(0, 0, lp.width)
val heightSpec = if (lp.height > 0) {
View.MeasureSpec.makeMeasureSpec(lp.height, View.MeasureSpec.EXACTLY)
} else {
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
}
view.measure(widthSpec, heightSpec)
return intArrayOf(view.measuredWidth, view.measuredHeight)
}
fun getMeasuredWidth(view: View): Int = measureView(view)[0]
fun getMeasuredHeight(view: View): Int = measureView(view)[1]
}
3、亮度工具.
import android.app.Application
import android.provider.Settings
import android.view.Window
// 亮度工具
object BrightnessUtil {
// 判断自动亮度模式是否启用, true表示已启用
fun isAutoBrightnessEnabled(application: Application): Boolean {
return try {
val mode = Settings.System.getInt(
application.contentResolver,
Settings.System.SCREEN_BRIGHTNESS_MODE
)
mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC
} catch (e: Settings.SettingNotFoundException) {
e.printStackTrace()
false
}
}
// 启动或禁用自动亮度模式
// 需要获取亮度权限
fun setAutoBrightnessEnabled(enabled: Boolean, application: Application): Boolean {
return Settings.System.putInt(
application.contentResolver,
Settings.System.SCREEN_BRIGHTNESS_MODE,
if (enabled) Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC
else Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL
)
}
// 获取当前屏幕的亮度, 范围在0-255
fun getBrightness(application: Application): Int {
return try {
Settings.System.getInt(
application.contentResolver,
Settings.System.SCREEN_BRIGHTNESS
)
} catch (e: Settings.SettingNotFoundException) {
e.printStackTrace()
0
}
}
// 设置当前屏幕的亮度 0-255
// 需要获取亮度权限
fun setBrightness(brightness: Int, application: Application): Boolean {
val resolver = application.contentResolver
val result = Settings.System.putInt(resolver, Settings.System.SCREEN_BRIGHTNESS, brightness)
resolver.notifyChange(Settings.System.getUriFor("screen_brightness"), null)
return result
}
// 获取当前窗口的亮度
fun getWindowBrightness(application: Application, window: Window): Int {
val lp = window.attributes
val brightness = lp.screenBrightness
return if (brightness < 0) getBrightness(application) else (brightness * 255).toInt()
}
// 设置窗口的亮度 0-255
fun setWindowBrightness(brightness: Int, window: Window) {
val lp = window.attributes
lp.screenBrightness = brightness / 255f
window.attributes = lp
}
}
4、状态栏工具.
import android.app.Activity
import android.content.Context
import android.content.res.Resources
import android.os.Build
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.view.Window
import android.view.WindowManager
import androidx.annotation.ColorInt
import android.graphics.Color
// 状态栏工具
object StatusBarUtil {
// 状态常量
private const val TAG_STATUS_BAR = "TAG_STATUS_BAR"
private const val TAG_OFFSET = "TAG_OFFSET"
private const val KEY_OFFSET = -123
// 获取状态栏高度
fun getStatusBarHeight(): Int {
val resources = Resources.getSystem()
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
return resources.getDimensionPixelSize(resourceId)
}
// 检查当前状态栏是否可见
fun isStatusBarVisible(activity: Activity): Boolean {
val flags = activity.window.attributes.flags
return flags and WindowManager.LayoutParams.FLAG_FULLSCREEN == 0
}
// 设置状态栏的可见性
fun setStatusBarVisibility(activity: Activity, isVisible: Boolean): Unit {
setStatusBarVisibility(activity.window, isVisible)
}
fun setStatusBarVisibility(window: Window, isVisible: Boolean): Unit {
if (isVisible) {
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
showStatusBarView(window)
addMarginTopEqualStatusBarHeight(window)
} else {
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
hideStatusBarView(window)
subtractMarginTopEqualStatusBarHeight(window)
}
}
private fun showStatusBarView(window: Window) {
val decorView = window.decorView as ViewGroup
val fakeStatusBarView = decorView.findViewWithTag<View>(TAG_STATUS_BAR)
?: return
fakeStatusBarView.visibility = View.VISIBLE
}
private fun addMarginTopEqualStatusBarHeight(window: Window) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return
val withTag = window.decorView.findViewWithTag<View>(TAG_OFFSET) ?: return
addMarginTopEqualStatusBarHeight(withTag)
}
// 为视图添加上边距等于状态栏高度
fun addMarginTopEqualStatusBarHeight(view: View) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return
view.tag = TAG_OFFSET
val haveSetOffset = view.getTag(KEY_OFFSET)
if (haveSetOffset != null && haveSetOffset as Boolean) return
val layoutParams = view.layoutParams as MarginLayoutParams
layoutParams.setMargins(
layoutParams.leftMargin,
layoutParams.topMargin + this.getStatusBarHeight(),
layoutParams.rightMargin,
layoutParams.bottomMargin
)
view.setTag(KEY_OFFSET, true)
}
private fun hideStatusBarView(window: Window) {
val decorView = window.decorView as ViewGroup
val fakeStatusBarView = decorView.findViewWithTag<View>(TAG_STATUS_BAR)
?: return
fakeStatusBarView.visibility = View.GONE
}
private fun subtractMarginTopEqualStatusBarHeight(window: Window) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return
val withTag = window.decorView.findViewWithTag<View>(TAG_OFFSET) ?: return
subtractMarginTopEqualStatusBarHeight(withTag)
}
// 从视图减去上边距等于状态栏高度
fun subtractMarginTopEqualStatusBarHeight(view: View) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return
val haveSetOffset = view.getTag(KEY_OFFSET) as Boolean
if (haveSetOffset == null || !haveSetOffset) return
val layoutParams = view.layoutParams as MarginLayoutParams
layoutParams.setMargins(
layoutParams.leftMargin,
layoutParams.topMargin - getStatusBarHeight(),
layoutParams.rightMargin,
layoutParams.bottomMargin
)
view.setTag(KEY_OFFSET, false)
}
// 检查状态栏是否为浅色模式
fun isStatusBarLightMode(activity: Activity): Boolean = isStatusBarLightMode(activity.window)
fun isStatusBarLightMode(window: Window): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val decorView = window.decorView
val vis = decorView.systemUiVisibility
return vis and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR != 0
}
return false
}
// 设置状态栏是否为浅色模式
fun setStatusBarLightMode(activity: Activity, isLightMode: Boolean): Unit {
setStatusBarLightMode(activity.window, isLightMode)
}
fun setStatusBarLightMode(window: Window, isLightMode: Boolean): Unit {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val decorView = window.decorView
var vis = decorView.systemUiVisibility
vis = if (isLightMode) {
vis or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
} else {
vis and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
}
decorView.systemUiVisibility = vis
}
}
// 设置状态栏颜色
fun setStatusBarColor(activity: Activity, @ColorInt color: Int): View? {
return setStatusBarColor(activity, color, false) // 第三个参数表示是否添加到decorView
}
fun setStatusBarColor(activity: Activity, @ColorInt color: Int, isDecor: Boolean): View? {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return null
transparentStatusBar(activity)
return applyStatusBarColor(activity, color, isDecor)
}
fun setStatusBarColor(window: Window, @ColorInt color: Int): View? {
return setStatusBarColor(window, color, false)
}
fun setStatusBarColor(window: Window, @ColorInt color: Int, isDecor: Boolean): View? {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return null
transparentStatusBar(window)
return applyStatusBarColor(window, color, isDecor)
}
private fun applyStatusBarColor(activity: Activity, color: Int, isDecor: Boolean): View? {
return applyStatusBarColor(activity.window, color, isDecor)
}
private fun applyStatusBarColor(window: Window, color: Int, isDecor: Boolean): View? {
val parent = if (isDecor) {
window.decorView as ViewGroup
} else {
window.findViewById<ViewGroup>(android.R.id.content)
}
var fakeStatusBarView = parent.findViewWithTag<View>(TAG_STATUS_BAR)
if (fakeStatusBarView != null) {
if (fakeStatusBarView.visibility == View.GONE) {
fakeStatusBarView.visibility = View.VISIBLE
}
fakeStatusBarView.setBackgroundColor(color)
} else {
fakeStatusBarView = createStatusBarView(window.context, color)
parent.addView(fakeStatusBarView)
}
return fakeStatusBarView
}
private fun createStatusBarView(context: Context, color: Int): View {
return View(context).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
getStatusBarHeight()
)
setBackgroundColor(color)
tag = TAG_STATUS_BAR
}
}
// 透明化状态栏
fun transparentStatusBar(activity: Activity) {
transparentStatusBar(activity.window)
}
fun transparentStatusBar(window: Window) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
val option = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
val vis = window.decorView.systemUiVisibility
window.decorView.systemUiVisibility = option or vis
window.statusBarColor = Color.TRANSPARENT
} else {
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
}
}
}
5、应用栏工具.
import android.app.Application
import android.util.TypedValue
// 应用栏工具
object ActionBarUtil {
// 获取应用栏高度
fun getActionBarHeight(application: Application): Int {
val tv = TypedValue()
return if (application.theme.resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
TypedValue.complexToDimensionPixelSize(
tv.data,
application.resources.displayMetrics
)
} else {
0
}
}
}
6、通知栏工具.
import android.Manifest
import android.annotation.SuppressLint
import android.app.Application
import android.os.Build
import androidx.annotation.RequiresPermission
// 通知栏工具
object NotificationBarUtil {
// 设置通知栏可见性
// 需要权限:
// <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
@RequiresPermission(Manifest.permission.EXPAND_STATUS_BAR)
fun setNotificationBarVisibility(isVisible: Boolean, application: Application) {
val methodName = if (isVisible) {
if (Build.VERSION.SDK_INT <= 16) "expand" else "expandNotificationsPanel"
} else {
if (Build.VERSION.SDK_INT <= 16) "collapse" else "collapsePanels"
}
invokePanels(methodName, application)
}
private fun invokePanels(methodName: String, application: Application) {
try {
@SuppressLint("WrongConstant")
val service = application.getSystemService("statusbar")
@SuppressLint("PrivateApi")
val statusBarManager = Class.forName("android.app.StatusBarManager")
val expand = statusBarManager.getMethod(methodName)
expand.invoke(service)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
7、导航栏工具.
import android.app.Activity
import android.content.res.Resources
import android.graphics.Color
import android.os.Build
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import androidx.annotation.ColorInt
import androidx.annotation.RequiresApi
// 导航栏工具
object NavigationBarUtil {
// 获取导航栏高度
fun getNavBarHeight(): Int {
val res = Resources.getSystem()
val resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android")
return if (resourceId != 0) {
res.getDimensionPixelSize(resourceId)
} else {
0
}
}
// 设置导航栏可见性
fun setNavBarVisibility(activity: Activity, isVisible: Boolean) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return
setNavBarVisibility(activity.window, isVisible)
}
fun setNavBarVisibility(window: Window, isVisible: Boolean) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return
val decorView = window.decorView as ViewGroup
for (i in 0 until decorView.childCount) {
val child = decorView.getChildAt(i)
if (child.id != View.NO_ID) {
val resourceEntryName = getResNameById(child.id)
if ("navigationBarBackground" == resourceEntryName) {
child.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
}
}
}
val uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
if (isVisible) {
decorView.systemUiVisibility = decorView.systemUiVisibility and uiOptions.inv()
} else {
decorView.systemUiVisibility = decorView.systemUiVisibility or uiOptions
}
}
private fun getResNameById(id: Int): String {
return try {
Resources.getSystem().getResourceEntryName(id)
} catch (ignore: Exception) {
""
}
}
// 获取导航栏颜色
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun getNavBarColor(activity: Activity): Int = getNavBarColor(activity.window)
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun getNavBarColor(window: Window): Int = window.navigationBarColor
// 设置导航栏颜色
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun setNavBarColor(activity: Activity, @ColorInt color: Int) {
setNavBarColor(activity.window, color)
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun setNavBarColor(window: Window, @ColorInt color: Int) {
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.navigationBarColor = color
}
// 透明化导航栏
fun transparentNavBar(activity: Activity) {
transparentNavBar(activity.window)
}
fun transparentNavBar(window: Window) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.navigationBarColor = Color.TRANSPARENT
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (window.attributes.flags and WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION == 0) {
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
}
}
val decorView = window.decorView
val vis = decorView.systemUiVisibility
val option = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
decorView.systemUiVisibility = vis or option
}
}
8、点击工具.
import android.graphics.Rect
import android.util.Log
import android.view.MotionEvent
import android.view.TouchDelegate
import android.view.View
import androidx.annotation.NonNull
// 点击工具
object ClickUtil {
// 常量定义
///////////////////////////////////////////////////////////////////////////
private const val PRESSED_VIEW_SCALE_TAG = -1
private const val PRESSED_VIEW_SCALE_DEFAULT_VALUE = -0.06f
private const val PRESSED_VIEW_ALPHA_TAG = -2
private const val PRESSED_VIEW_ALPHA_SRC_TAG = -3
private const val PRESSED_VIEW_ALPHA_DEFAULT_VALUE = 0.8f
private const val PRESSED_BG_ALPHA_STYLE = 4
private const val PRESSED_BG_ALPHA_DEFAULT_VALUE = 0.9f
private const val PRESSED_BG_DARK_STYLE = 5
private const val PRESSED_BG_DARK_DEFAULT_VALUE = 0.9f
private const val DEBOUNCING_DEFAULT_VALUE = 1000L
private const val TIP_DURATION = 2000L
// 为视图添加点击后缩放效果
fun applyPressedViewScale(view: View?, scaleFactor: Float) {
view ?: return
view.setTag(PRESSED_VIEW_SCALE_TAG, scaleFactor)
view.isClickable = true
view.setOnTouchListener(OnUtilsTouchListener.getInstance())
}
private class OnUtilsTouchListener : View.OnTouchListener {
override fun onTouch(v: View, event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
processScale(v, true)
processAlpha(v, true)
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
processScale(v, false)
processAlpha(v, false)
}
}
return false
}
private fun processScale(view: View, isDown: Boolean) {
(view.getTag(PRESSED_VIEW_SCALE_TAG) as? Float ?: return)
val value = if (isDown) 1 + view.getTag(PRESSED_VIEW_SCALE_TAG) as Float else 1f
view.animate()
.scaleX(value)
.scaleY(value)
.setDuration(200)
.start()
}
private fun processAlpha(view: View, isDown: Boolean) {
val tag = view.getTag(if (isDown) PRESSED_VIEW_ALPHA_TAG else PRESSED_VIEW_ALPHA_SRC_TAG)
(tag as? Float)?.let { view.alpha = it }
}
companion object {
fun getInstance() = InstanceHolder.INSTANCE
private object InstanceHolder {
val INSTANCE = OnUtilsTouchListener()
}
}
}
// 为视图添加点击后透明度效果
fun applyPressedViewAlpha(view: View?, alpha: Float) {
view ?: return
view.setTag(PRESSED_VIEW_ALPHA_TAG, alpha)
view.setTag(PRESSED_VIEW_ALPHA_SRC_TAG, view.alpha)
view.isClickable = true
view.setOnTouchListener(OnUtilsTouchListener.getInstance())
}
// 扩展可点击的区域
fun expandClickArea(@NonNull view: View,
expandSizeTop: Int,
expandSizeLeft: Int,
expandSizeRight: Int,
expandSizeBottom: Int): Unit {
val parentView = view.parent as? View ?: run {
Log.e("ClickUtils", "expandClickArea must have parent view.")
return
}
parentView.post {
Rect().apply {
view.getHitRect(this)
top -= expandSizeTop
bottom += expandSizeBottom
left -= expandSizeLeft
right += expandSizeRight
parentView.touchDelegate = TouchDelegate(this, view)
}
}
}
// 双击回调
interface OnDoubleClickListener {
fun onDoubleClick(view: View)
fun onFirstClick(view: View)
}
// 双击间隔时间阈值(毫秒)
private const val DOUBLE_CLICK_INTERVAL = 200L
// 记录上次点击时间
private var lastClickTime = 0L
fun setDoubleClickListener(view: View, listener: OnDoubleClickListener) {
view.setOnClickListener {
val currentTime = System.currentTimeMillis()
val timeDiff = currentTime - lastClickTime
if (timeDiff < DOUBLE_CLICK_INTERVAL) {
// 在阈值时间内再次点击,视为双击
listener.onDoubleClick(view)
lastClickTime = 0 // 重置时间
} else {
// 第一次点击
listener.onFirstClick(view)
lastClickTime = currentTime
}
}
}
}
9、触摸工具.
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import android.view.ViewConfiguration
import androidx.annotation.IntDef
// 触摸工具
object TouchUtil {
// 方向常量
const val UNKNOWN = 0
const val LEFT = 1
const val UP = 2
const val RIGHT = 4
const val DOWN = 8
@IntDef(LEFT, UP, RIGHT, DOWN, UNKNOWN)
@Retention(AnnotationRetention.SOURCE)
annotation class Direction
// 为视图添加触摸监听器
fun setOnTouchListener(v: View?, listener: OnTouchUtilsListener?) {
if (v == null || listener == null) return
v.setOnTouchListener(listener)
}
// 触摸监听器基类
abstract class OnTouchUtilsListener : View.OnTouchListener {
companion object {
private const val STATE_DOWN = 0
private const val STATE_MOVE = 1
private const val STATE_STOP = 2
private const val MIN_TAP_TIME = 1000L // 区分点击和长按的时间阈值(毫秒)
}
private var touchSlop = 0 // 触摸滑动的最小距离阈值
private var downX = -1 // 按下时的X坐标
private var downY = -1 // 按下时的Y坐标
private var lastX = -1 // 上次事件的X坐标
private var lastY = -1 // 上次事件的Y坐标
private var state = STATE_DOWN // 当前状态
private var direction = UNKNOWN // 滑动方向
private var velocityTracker: VelocityTracker? = null // 速度跟踪器
private var maximumFlingVelocity = 0 // 最大投掷速度
private var minimumFlingVelocity = 0 // 最小投掷速度
init {
resetTouch(-1, -1)
}
/**
* 重置触摸状态
* @param x X坐标
* @param y Y坐标
*/
private fun resetTouch(x: Int, y: Int) {
downX = x
downY = y
lastX = x
lastY = y
state = STATE_DOWN
direction = UNKNOWN
velocityTracker?.clear()
}
/**
* 按下事件回调
* @param view 触发事件的视图
* @param x 当前X坐标
* @param y 当前Y坐标
* @param event 原始事件
* @return 是否消费事件
*/
abstract fun onDown(view: View, x: Int, y: Int, event: MotionEvent): Boolean
/**
* 移动事件回调
* @param view 触发事件的视图
* @param direction 移动方向
* @param x 当前X坐标
* @param y 当前Y坐标
* @param dx 与上次事件的X偏移量
* @param dy 与上次事件的Y偏移量
* @param totalX 与按下点的X总偏移量
* @param totalY 与按下点的Y总偏移量
* @param event 原始事件
* @return 是否消费事件
*/
abstract fun onMove(
view: View,
@Direction direction: Int,
x: Int,
y: Int,
dx: Int,
dy: Int,
totalX: Int,
totalY: Int,
event: MotionEvent
): Boolean
/**
* 抬起或取消事件回调
* @param view 触发事件的视图
* @param direction 移动方向
* @param x 当前X坐标
* @param y 当前Y坐标
* @param totalX 与按下点的X总偏移量
* @param totalY 与按下点的Y总偏移量
* @param vx X轴速度
* @param vy Y轴速度
* @param event 原始事件
* @return 是否消费事件
*/
abstract fun onStop(
view: View,
@Direction direction: Int,
x: Int,
y: Int,
totalX: Int,
totalY: Int,
vx: Int,
vy: Int,
event: MotionEvent
): Boolean
/**
* 处理触摸事件
* @param v 触发事件的视图
* @param event 触摸事件
* @return 是否消费事件
*/
override fun onTouch(v: View, event: MotionEvent): Boolean {
// 初始化触摸参数
if (touchSlop == 0) {
touchSlop = ViewConfiguration.get(v.context).scaledTouchSlop
}
if (maximumFlingVelocity == 0) {
maximumFlingVelocity = ViewConfiguration.get(v.context).scaledMaximumFlingVelocity
}
if (minimumFlingVelocity == 0) {
minimumFlingVelocity = ViewConfiguration.get(v.context).scaledMinimumFlingVelocity
}
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain()
}
velocityTracker?.addMovement(event)
return when (event.action) {
MotionEvent.ACTION_DOWN -> onUtilsDown(v, event)
MotionEvent.ACTION_MOVE -> onUtilsMove(v, event)
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> onUtilsStop(v, event)
else -> false
}
}
/**
* 处理按下事件
* @param view 触发事件的视图
* @param event 触摸事件
* @return 是否消费事件
*/
fun onUtilsDown(view: View, event: MotionEvent): Boolean {
val x = event.rawX.toInt()
val y = event.rawY.toInt()
resetTouch(x, y)
view.isPressed = true
return onDown(view, x, y, event)
}
/**
* 处理移动事件
* @param view 触发事件的视图
* @param event 触摸事件
* @return 是否消费事件
*/
fun onUtilsMove(view: View, event: MotionEvent): Boolean {
val x = event.rawX.toInt()
val y = event.rawY.toInt()
// 如果没有收到按下事件,则重置状态
if (downX == -1) {
resetTouch(x, y)
view.isPressed = true
}
// 如果还未进入移动状态,检查是否达到滑动阈值
if (state != STATE_MOVE) {
if (abs(x - lastX) < touchSlop && abs(y - lastY) < touchSlop) {
return true
}
state = STATE_MOVE
// 确定滑动方向
}
direction = if (abs(x - lastX) >= abs(y - lastY)) {
if (x - lastX < 0) LEFT else RIGHT
} else {
if (y - lastY < 0) UP else DOWN
}
// 计算偏移量并回调
val consumeMove = onMove(
view, direction, x, y,
x - lastX, y - lastY,
x - downX, y - downY,
event
)
lastX = x
lastY = y
return consumeMove
}
/**
* 处理抬起或取消事件
* @param view 触发事件的视图
* @param event 触摸事件
* @return 是否消费事件
*/
fun onUtilsStop(view: View, event: MotionEvent): Boolean {
val x = event.rawX.toInt()
val y = event.rawY.toInt()
var vx = 0
var vy = 0
// 计算速度
velocityTracker?.run {
computeCurrentVelocity(1000, maximumFlingVelocity.toFloat())
vx = xVelocity.toInt()
vy = yVelocity.toInt()
recycle()
velocityTracker = null
}
// 过滤过小的速度
if (abs(vx) < minimumFlingVelocity) vx = 0
if (abs(vy) < minimumFlingVelocity) vy = 0
view.isPressed = false
val consumeStop = onStop(
view, direction, x, y,
x - downX, y - downY,
vx, vy, event
)
// 处理点击和长按
if (event.action == MotionEvent.ACTION_UP) {
when (state) {
STATE_DOWN -> {
if (event.eventTime - event.downTime <= MIN_TAP_TIME) {
view.performClick()
} else {
view.performLongClick()
}
}
}
}
resetTouch(-1, -1)
return consumeStop
}
private fun abs(value: Int) = kotlin.math.abs(value)
}
/*
TouchUtil.setOnTouchListener(view, object : TouchUtil.OnTouchUtilsListener() {
override fun onDown(view: View, x: Int, y: Int, event: MotionEvent): Boolean {
// 处理按下事件
return true
}
override fun onMove(
view: View,
direction: Int,
x: Int,
y: Int,
dx: Int,
dy: Int,
totalX: Int,
totalY: Int,
event: MotionEvent
): Boolean {
// 处理移动事件
when (direction) {
LEFT -> {
}
UP -> {
}
RIGHT -> {
}
DOWN -> {
}
}
return true
}
override fun onStop(
view: View,
direction: Int,
x: Int,
y: Int,
totalX: Int,
totalY: Int,
vx: Int,
vy: Int,
event: MotionEvent
): Boolean {
return true
}
})
*/
}
10、音量工具.
import android.app.Application
import android.content.Context
import android.media.AudioManager
import android.os.Build
// 音量工具
object VolumeUtil {
var application: Application? = null
/**
* 获取指定音频流的当前音量
* @param streamType 音频流类型,可选值:
* - AudioManager.STREAM_VOICE_CALL 通话
* - AudioManager.STREAM_SYSTEM 系统声音
* - AudioManager.STREAM_RING 铃声
* - AudioManager.STREAM_MUSIC 媒体音量
* - AudioManager.STREAM_ALARM 闹钟
* - AudioManager.STREAM_NOTIFICATION 通知
* - AudioManager.STREAM_DTMF DTMF音
* - AudioManager.STREAM_ACCESSIBILITY 辅助功能
* @return 当前音量值
*/
fun getVolume(streamType: Int): Int {
val am = application?.getSystemService(Context.AUDIO_SERVICE) as AudioManager
return am.getStreamVolume(streamType)
}
/**
* 设置指定音频流的音量
* 注意:
* - 如果设置的音量大于最大音量,会自动设置为最大音量
* - 如果设置的音量小于0,会自动设置为最小音量
* @param streamType 音频流类型,可选值同上
* @param volume 要设置的音量值
* @param flags 标志位,可选值:
* - AudioManager.FLAG_SHOW_UI 显示系统音量UI
* - AudioManager.FLAG_ALLOW_RINGER_MODES 允许改变铃声模式
* - AudioManager.FLAG_PLAY_SOUND 调整音量时播放声音
* - AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE 调整音量时不播放声音和振动
* - AudioManager.FLAG_VIBRATE 调整音量时振动
*/
// 这个方法内部不是在主线程执行
fun setVolume(streamType: Int, volume: Int, flags: Int) {
val am = application?.getSystemService(Context.AUDIO_SERVICE) as AudioManager
try {
am.setStreamVolume(streamType, volume, flags)
} catch (ignore: SecurityException) {
// 处理没有权限的情况
}
}
/**
* 获取指定音频流的最大音量
* @param streamType 音频流类型,可选值同上
* @return 最大音量值
*/
fun getMaxVolume(streamType: Int): Int {
val am = application?.getSystemService(Context.AUDIO_SERVICE) as AudioManager
return am.getStreamMaxVolume(streamType)
}
/**
* 获取指定音频流的最小音量
* 注意:仅支持 Android P(API 28) 及以上版本
* @param streamType 音频流类型,可选值同上
* @return 最小音量值,Android P以下版本返回0
*/
fun getMinVolume(streamType: Int): Int {
val am = application?.getSystemService(Context.AUDIO_SERVICE) as AudioManager
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
am.getStreamMinVolume(streamType)
} else {
0
}
}
}
11、语言工具.
import android.content.res.Configuration
import android.os.Build
import java.util.Locale
// 语言工具
object LanguageUtil {
private const val KEY_LOCALE = "KEY_LOCALE"
private const val VALUE_FOLLOW_SYSTEM = "VALUE_FOLLOW_SYSTEM"
// 获取语言
fun getLocal(configuration: Configuration): Locale {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
configuration.locales[0]
} else {
configuration.locale
}
}
// 设置语言
fun setLocal(configuration: Configuration, locale: Locale) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLocale(locale)
} else {
configuration.locale = locale
}
}
}
12、ToastUtil.
import android.animation.ValueAnimator
import android.app.Activity
import android.content.Context
import android.graphics.Color
import android.graphics.Outline
import android.os.Handler
import android.os.Looper
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import android.view.animation.AccelerateInterpolator
import android.view.animation.LinearInterpolator
import android.widget.FrameLayout
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.view.setMargins
import java.lang.ref.WeakReference
/*
这里的Toast是在content节点下添加的, 这就表示添加必须在setContentView之后, 否则会出现遮挡问题
实测(decorView as ViewGroup).addView也行, 也不会出现遮挡问题
*/
object ToastUtil {
private class ShadowHandler(context: Context) : Handler(Looper.myLooper()!!) {
private val mContexts: WeakReference<Context> = WeakReference(context)
fun processMessage(delay: Long, callback: () -> Unit) {
if (this.mContexts.get() != null) {
this.postDelayed(
callback,
delay
)
}
}
}
// 基本Toast, 瞬间弹出提示, 渐隐消失, 不遮挡点击
fun Activity.toast1(message: String = "", delay: Long = 3000L, callback: (() -> Unit)? = null): Unit {
// 当前窗的根视图, 包含系统级装饰
val decorView = this.window.decorView
// 用户自定义内容的根容器
val contentView = decorView.findViewById<ViewGroup>(android.R.id.content)
val width = this.resources.displayMetrics.widthPixels
val height = this.resources.displayMetrics.heightPixels
val shadowView = FrameLayout(this).apply {
setBackgroundColor(Color.argb(255 - 51, 0, 0, 0))
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply {
gravity = Gravity.CENTER
setMargins(20)
}
clipToOutline = true
outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View?, outline: Outline?) {
view?.let {
outline?.setRoundRect(
0, 0, it.width, it.height, it.width * 0.5F
)
}
}
}
}
val text: AppCompatTextView = AppCompatTextView(this).apply {
text = message
gravity = Gravity.CENTER
setTextColor(textColor)
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
setPadding((width * 0.033).toInt(), (height * 0.007).toInt(), (width * 0.033).toInt(), (height * 0.007).toInt())
}
shadowView.addView(text)
contentView.addView(shadowView)
val handler = ShadowHandler(this)
val animator = ValueAnimator.ofFloat(1.00F, 1.00F, 0.00F).apply {
duration = delay
repeatCount = 0
interpolator = AccelerateInterpolator()
addUpdateListener {
shadowView.alpha = it.animatedValue as Float
}
}
animator.start()
handler.processMessage(delay) {
animator.cancel()
contentView.removeView(shadowView)
callback?.invoke()
}
}
// 改良Toast, 短时放大弹出提示, 渐隐消失, 不遮挡点击
fun Activity.toast2(message: String = "", delay: Long = 3000L, callback: (() -> Unit)? = null): Unit {
// 当前窗的根视图, 包含系统级装饰
val decorView = this.window.decorView
// 用户自定义内容的根容器
val contentView = decorView.findViewById<ViewGroup>(android.R.id.content)
val width = this.resources.displayMetrics.widthPixels
val height = this.resources.displayMetrics.heightPixels
val shadowView = FrameLayout(this).apply {
setBackgroundColor(Color.argb(255 - 51, 0, 0, 0))
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply {
gravity = Gravity.CENTER
setMargins(20)
}
clipToOutline = true
outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View?, outline: Outline?) {
view?.let {
outline?.setRoundRect(
0, 0, it.width, it.height, it.width * 0.5F
)
}
}
}
}
val text: AppCompatTextView = AppCompatTextView(this).apply {
text = message
gravity = Gravity.CENTER
setTextColor(textColor)
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
setPadding((width * 0.033).toInt(), (height * 0.007).toInt(), (width * 0.033).toInt(), (height * 0.007).toInt())
}
shadowView.addView(text)
contentView.addView(shadowView)
val handler = ShadowHandler(this)
val scaleAnimator = ValueAnimator.ofFloat(0.00F, 1.00F).apply {
duration = delay / 23
repeatCount = 0
interpolator = LinearInterpolator()
addUpdateListener {
shadowView.scaleX = it.animatedValue as Float
shadowView.scaleY = it.animatedValue as Float
}
}
val animator = ValueAnimator.ofFloat(1.00F, 1.00F, 0.00F).apply {
duration = delay
repeatCount = 0
interpolator = AccelerateInterpolator()
addUpdateListener {
shadowView.alpha = it.animatedValue as Float
}
}
scaleAnimator.start()
animator.start()
handler.processMessage(delay) {
scaleAnimator.cancel()
animator.cancel()
contentView.removeView(shadowView)
callback?.invoke()
}
}
// 从屏幕上方划入, 从上方消失, 不遮挡点击
fun Activity.toast3(message: String = "", delay: Long = 3000L, callback: (() -> Unit)? = null): Unit {
// 当前窗的根视图, 包含系统级装饰
val decorView = this.window.decorView
// 用户自定义内容的根容器
val contentView = decorView.findViewById<ViewGroup>(android.R.id.content)
val width = this.resources.displayMetrics.widthPixels
val height = this.resources.displayMetrics.heightPixels
val shadowView = FrameLayout(this).apply {
setBackgroundColor(Color.argb(255 - 51, 0, 0, 0))
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply {
gravity = Gravity.TOP
}
}
val text: AppCompatTextView = AppCompatTextView(this).apply {
text = message
gravity = Gravity.CENTER
setTextColor(textColor)
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
setPadding((width * 0.033).toInt(), (height * 0.007).toInt(), (width * 0.033).toInt(), (height * 0.007).toInt())
}
shadowView.addView(text)
contentView.addView(shadowView)
val handler = ShadowHandler(this)
val animator = ValueAnimator.ofFloat(-height * 0.50F, 0.00F, 0.00F, 0.00F, 0.00F, 0.00F, -height * 0.50F).apply {
duration = delay
repeatCount = 0
interpolator = LinearInterpolator()
addUpdateListener {
shadowView.translationY = it.animatedValue as Float
}
}
animator.start()
handler.processMessage(delay) {
animator.cancel()
contentView.removeView(shadowView)
callback?.invoke()
}
}
// 从屏幕下方划入, 从下方消失, 不遮挡点击
fun Activity.toast4(message: String = "", delay: Long = 3000L, callback: (() -> Unit)? = null): Unit {
// 当前窗的根视图, 包含系统级装饰
val decorView = this.window.decorView
// 用户自定义内容的根容器
val contentView = decorView.findViewById<ViewGroup>(android.R.id.content)
val width = this.resources.displayMetrics.widthPixels
val height = this.resources.displayMetrics.heightPixels
val shadowView = FrameLayout(this).apply {
setBackgroundColor(Color.argb(255 - 51, 0, 0, 0))
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply {
gravity = Gravity.BOTTOM
}
}
val text: AppCompatTextView = AppCompatTextView(this).apply {
text = message
gravity = Gravity.CENTER
setTextColor(textColor)
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
setPadding((width * 0.033).toInt(), (height * 0.007).toInt(), (width * 0.033).toInt(), (height * 0.007).toInt())
}
shadowView.addView(text)
contentView.addView(shadowView)
val handler = ShadowHandler(this)
val animator = ValueAnimator.ofFloat(height * 0.50F, 0.00F, 0.00F, 0.00F, 0.00F, 0.00F, height * 0.50F).apply {
duration = delay
repeatCount = 0
interpolator = LinearInterpolator()
addUpdateListener {
shadowView.translationY = it.animatedValue as Float
}
}
animator.start()
handler.processMessage(delay) {
animator.cancel()
contentView.removeView(shadowView)
callback?.invoke()
}
}
}
13、ShadowCoverUtil.
import android.animation.ValueAnimator
import android.app.Activity
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.Outline
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.Handler
import android.os.Looper
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import android.view.animation.LinearInterpolator
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.appcompat.widget.AppCompatImageView
import androidx.appcompat.widget.AppCompatTextView
import java.lang.ref.WeakReference
import kotlin.math.cos
import kotlin.math.sin
object ShadowCoverUtil {
private class ShadowHandler(context: Context) : Handler(Looper.myLooper()!!) {
private val mContexts: WeakReference<Context> = WeakReference(context)
fun processMessage(delay: Long, callback: () -> Unit) {
if (this.mContexts.get() != null) {
this.postDelayed(
callback,
delay
)
}
}
}
fun Activity.shadowCover1(delay: Long = 1333L, callback: (() -> Unit)? = null): Unit {
// 当前窗的根视图, 包含系统级装饰
val decorView = this.window.decorView
// 用户自定义内容的根容器
val contentView = decorView.findViewById<ViewGroup>(android.R.id.content)
val shadowView = FrameLayout(this).apply {
setBackgroundColor(Color.argb(50, 0, 0, 0))
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
addView(AppCompatTextView(context).apply {
text = "加载中..."
setLines(1)
setTextColor(Color.WHITE)
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply {
gravity = Gravity.CENTER
}
})
}
contentView.addView(shadowView)
shadowView.bringToFront()
val handler = ShadowHandler(this)
handler.processMessage(delay, {
contentView.removeView(shadowView)
callback?.invoke()
})
}
class DotLoadingDrawable(
private val context: Context,
private val radius: Float = 32.0F, // 这个半径是大圆的
private val dotColor: Int = Color.BLUE,
private val dotCount: Int = 8,
private val dotRadius: Float = 4.0F,
private val maxScale: Float = 1.5f
) : Drawable() {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = dotColor
style = Paint.Style.FILL
}
private val dotPositions = mutableListOf<PointF>()
private var progress = 0f
private val animator = ValueAnimator.ofFloat(0f, 1f).apply {
duration = 1000L
repeatCount = ValueAnimator.INFINITE
interpolator = LinearInterpolator()
addUpdateListener { anim ->
progress = anim.animatedValue as Float
invalidateSelf() // 触发重绘
}
}
init {
// 初始化圆点位置(圆形分布)
val radius = this.radius
for (i in 0 until dotCount) {
val angle = Math.toRadians(i * (360f / dotCount).toDouble())
dotPositions.add(
PointF(
(radius * cos(angle)).toFloat(),
(radius * sin(angle)).toFloat()
)
)
}
animator.start()
}
override fun draw(canvas: Canvas) {
val centerX = bounds.exactCenterX()
val centerY = bounds.exactCenterY()
// 绘制动态圆点
dotPositions.forEachIndexed { index, point ->
val dotProgress = (progress + index.toFloat() / dotCount) % 1f
val scale = 0.5f + (maxScale - 0.5f) * sin(dotProgress * Math.PI).toFloat()
canvas.save()
canvas.translate(centerX + point.x, centerY + point.y)
canvas.scale(scale, scale)
canvas.drawCircle(0f, 0f, dotRadius, paint)
canvas.restore()
}
}
override fun setAlpha(alpha: Int) { paint.alpha = alpha }
override fun setColorFilter(filter: ColorFilter?) { paint.colorFilter = filter }
override fun getOpacity() = PixelFormat.TRANSLUCENT
override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
invalidateSelf()
}
fun start() = animator.start()
fun stop() = animator.cancel()
private val Float.dp: Float get() = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, this, context.resources.displayMetrics
)
}
fun Activity.shadowCover2(delay: Long = 3000L, callback: (() -> Unit)? = null): Unit {
// 当前窗的根视图, 包含系统级装饰
val decorView = this.window.decorView
// 用户自定义内容的根容器
val contentView = decorView.findViewById<ViewGroup>(android.R.id.content)
val width = this.resources.displayMetrics.widthPixels
val height = this.resources.displayMetrics.heightPixels
val background = FrameLayout(this).apply {
setBackgroundColor(Color.argb(0, 0, 0, 0))
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
setOnClickListener {}
}
val shadowView = FrameLayout(this).apply {
setBackgroundColor(Color.argb(160, 0, 0, 0))
layoutParams = FrameLayout.LayoutParams(
(width * 0.35).toInt(),
(width * 0.35).toInt(),
).apply {
gravity = Gravity.CENTER
// setPadding((width * 0.07).toInt(), (width * 0.07).toInt(), (width * 0.07).toInt(), (width * 0.07).toInt())
}
clipToOutline = true
outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View?, outline: Outline?) {
view?.let {
outline?.setRoundRect(
0, 0, it.width, it.height, it.width * 0.17F
)
}
}
}
}
val dotView: AppCompatImageView = AppCompatImageView(this).apply {
scaleType = ImageView.ScaleType.FIT_XY
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
setBackgroundDrawable(DotLoadingDrawable(
context = this@shadowCover2,
radius = width * 0.10F,
dotColor = Color.parseColor("#FFFFFFFF"),
dotCount = 16,
dotRadius = width * 0.003F,
maxScale = 2.3f
))
}
shadowView.addView(dotView)
background.addView(shadowView)
contentView.addView(background)
val handler = ShadowHandler(this)
handler.processMessage(delay) {
contentView.removeView(background)
callback?.invoke()
}
}
}
0、占位.

被折叠的 条评论
为什么被折叠?



