Android工具(持续更新ing)

常见的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、占位.

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值