目录
Glide 图片加载
需要添加Glide依赖implementation 'com.github.bumptech.glide:glide:4.13.0'
、annotationProcessor 'com.github.bumptech.glide:compiler:4.13.0'
/**
* Glide 图片加载
* 图片的url,此处采用本地路径加服务器地址的方式拼接,直接采用全部路径
*/
class ImageLoadUtils private constructor(){
companion object {
// 单例模式
val instance: ImageLoadUtils by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { ImageLoadUtils() }
}
/**
* 获取默认的图片
*
* glide 默认使用缓存机制,但是加载Gif 图片时会造成oom,所以要关闭缓存机制
* @return
*/
fun getDefaultOptions(): RequestOptions? {
return getOptions(R.drawable.ic_image_holder, R.drawable.ic_image_error)
}
fun getOptions(@DrawableRes holderRes: Int, @DrawableRes errorRes: Int): RequestOptions? {
return RequestOptions().centerCrop().error(errorRes).placeholder(holderRes)
}
/**
* 加载原图
*/
fun load(context: Context, url: String, iv: ImageView) {
if (objectNull(context, url, iv)) return
Glide.with(context)
.load(url)
.apply(getDefaultOptions())
.into(iv)
}
/**
* 加载圆形的图片
*/
fun loadCircle(context: Context, url: String?, iv: ImageView) {
Glide.with(context)
.load(url)
.apply(RequestOptions.bitmapTransform(CircleCrop()).apply(getDefaultOptions()))
.into(iv)
}
/**
* 加载圆角图片
*/
fun loadCorner(context: Context, url: String, iv: ImageView, size: Int) {
if (objectNull(context, url, iv)) return
Glide.with(context)
.load(url)
.skipMemoryCache(true) //圆角半径
.apply(RequestOptions.bitmapTransform(RoundedCorners(50)))
.into(iv)
}
/**
* 判断图片路径是否为空和是否符合规则
*/
private fun objectNull(context: Context?, path: String, imageView: ImageView?): Boolean {
return context == null || TextUtils.isEmpty(path) || imageView == null
}
}
SharedPreferences 本地缓存
class PreferenceUtil private constructor() {
companion object {
// 单例模式
val instance: PreferenceUtil by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { PreferenceUtil() }
}
private val context: Context = MyApplication.getContext()
val FILE_NAME = "leo_pro"
/**
* 保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法
*
* @param key
* @param obj
*/
fun put(key: String?, obj: Any) {
val sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE)
val editor = sp.edit()
when (obj) {
is String -> {
editor.putString(key, obj)
}
is Int -> {
editor.putInt(key, obj)
}
is Boolean -> {
editor.putBoolean(key, obj)
}
is Float -> {
editor.putFloat(key, obj)
}
is Long -> {
editor.putLong(key, obj)
}
else -> {
editor.putString(key, obj.toString())
}
}
SharedPreferencesCompat.apply(editor)
}
/**
* 得到保存数据的方法,根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值
*
* @param key
* @param defaultObject
* @return
*/
fun get(key: String?, defaultObject: Any?): Any? {
val sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE)
when (defaultObject) {
is String -> {
return sp.getString(key, defaultObject as String?)
}
is Int -> {
return sp.getInt(key, (defaultObject as Int?)!!)
}
is Boolean -> {
return sp.getBoolean(key, (defaultObject as Boolean?)!!)
}
is Float -> {
return sp.getFloat(key, (defaultObject as Float?)!!)
}
is Long -> {
return sp.getLong(key, (defaultObject as Long?)!!)
}
else -> return null
}
}
/**
* 移除某个key值以及对应的值
*
* @param key
*/
fun remove(key: String?) {
val sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE)
val editor = sp.edit()
editor.remove(key)
SharedPreferencesCompat.apply(editor)
}
/**
* 清除所有数据
*/
fun clear() {
val sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE)
val editor = sp.edit()
editor.clear()
SharedPreferencesCompat.apply(editor)
}
/**
* 查询某个key是否已经存在
*
* @param key 查询的值
*/
operator fun contains(key: String?): Boolean {
val sp = context.getSharedPreferences(
FILE_NAME,
Context.MODE_PRIVATE
)
return sp.contains(key)
}
/**
* 返回所有的键值对
*/
fun getAll(): Map<String?, *>? {
val sp = context.getSharedPreferences(
FILE_NAME,
Context.MODE_PRIVATE
)
return sp.all
}
/**
* 创建一个解决SharedPreferencesCompat.apply方法的一个兼容类
*/
private object SharedPreferencesCompat {
private val sApplyMethod = findApplyMethod()
/**
* 反射查找apply的方法
*/
private fun findApplyMethod(): Method? {
try {
val clz: Class<*> = SharedPreferences.Editor::class.java
return clz.getMethod("apply")
} catch (e: NoSuchMethodException) {
}
return null
}
/**
* 如果找到则使用apply执行,否则使用commit
*/
fun apply(editor: SharedPreferences.Editor) {
try {
if (sApplyMethod != null) {
sApplyMethod.invoke(editor)
return
}
} catch (e: IllegalArgumentException) {
} catch (e: IllegalAccessException) {
} catch (e: InvocationTargetException) {
}
editor.commit()
}
}
}
也可以这样封装:
public class PreferenceUtil {
private var sp: SharedPreferences? = null
private var editor: SharedPreferences.Editor? = null
/**
* 初始化工具类
*/
fun init(context: Context, spName: String?) {
sp = context.getSharedPreferences(spName, Context.MODE_PRIVATE)
editor = sp?.edit()
editor?.apply()
}
/**
* SP中写入String类型value
*
* @param key 键
* @param value 值
*/
fun put(key: String?, value: String?) {
editor!!.putString(key, value).apply()
}
/**
* SP中读取String
*
* @param key 键
* @return 存在返回对应值,不存在返回默认值`null`
*/
fun getString(key: String?): String? {
return getString(key, null)
}
/**
* SP中读取String
*
* @param key 键
* @param defaultValue 默认值
* @return 存在返回对应值,不存在返回默认值`defaultValue`
*/
fun getString(key: String?, defaultValue: String?): String? {
return sp!!.getString(key, defaultValue)
}
/**
* SP中写入int类型value
*
* @param key 键
* @param value 值
*/
fun put(key: String?, value: Int) {
editor!!.putInt(key, value).apply()
}
/**
* SP中读取int
*
* @param key 键
* @return 存在返回对应值,不存在返回默认值-1
*/
fun getInt(key: String?): Int {
return getInt(key, -1)
}
/**
* SP中读取int
*
* @param key 键
* @param defaultValue 默认值
* @return 存在返回对应值,不存在返回默认值`defaultValue`
*/
fun getInt(key: String?, defaultValue: Int): Int {
return sp!!.getInt(key, defaultValue)
}
/**
* SP中写入long类型value
*
* @param key 键
* @param value 值
*/
fun put(key: String?, value: Long) {
editor!!.putLong(key, value).apply()
}
/**
* SP中读取long
*
* @param key 键
* @return 存在返回对应值,不存在返回默认值-1
*/
fun getLong(key: String?): Long {
return getLong(key, -1L)
}
/**
* SP中读取long
*
* @param key 键
* @param defaultValue 默认值
* @return 存在返回对应值,不存在返回默认值`defaultValue`
*/
fun getLong(key: String?, defaultValue: Long): Long {
return sp!!.getLong(key, defaultValue)
}
/**
* SP中写入float类型value
*
* @param key 键
* @param value 值
*/
fun put(key: String?, value: Float) {
editor!!.putFloat(key, value).apply()
}
/**
* SP中读取float
*
* @param key 键
* @return 存在返回对应值,不存在返回默认值-1
*/
fun getFloat(key: String?): Float {
return getFloat(key, -1f)
}
/**
* SP中读取float
*
* @param key 键
* @param defaultValue 默认值
* @return 存在返回对应值,不存在返回默认值`defaultValue`
*/
fun getFloat(key: String?, defaultValue: Float): Float {
return sp!!.getFloat(key, defaultValue)
}
/**
* SP中写入boolean类型value
*
* @param key 键
* @param value 值
*/
fun put(key: String?, value: Boolean) {
editor!!.putBoolean(key, value).apply()
}
/**
* SP中读取boolean
*
* @param key 键
* @return 存在返回对应值,不存在返回默认值`false`
*/
fun getBoolean(key: String?): Boolean {
return getBoolean(key, false)
}
/**
* SP中读取boolean
*
* @param key 键
* @param defaultValue 默认值
* @return 存在返回对应值,不存在返回默认值`defaultValue`
*/
fun getBoolean(key: String?, defaultValue: Boolean): Boolean {
return sp!!.getBoolean(key, defaultValue)
}
/**
* SP中写入String集合类型value
*
* @param key 键
* @param values 值
*/
fun put(key: String?, values: Set<String?>?) {
editor!!.putStringSet(key, values).apply()
}
/**
* SP中读取StringSet
*
* @param key 键
* @return 存在返回对应值,不存在返回默认值`null`
*/
fun getStringSet(key: String?): Set<String?>? {
return getStringSet(key, null)
}
/**
* SP中读取StringSet
*
* @param key 键
* @param defaultValue 默认值
* @return 存在返回对应值,不存在返回默认值`defaultValue`
*/
fun getStringSet(key: String?, defaultValue: Set<String?>?): Set<String?>? {
return sp!!.getStringSet(key, defaultValue)
}
/**
* SP中获取所有键值对
*
* @return Map对象
*/
fun getAll(): Map<String?, *>? {
return sp!!.all
}
/**
* SP中移除该key
*
* @param key 键
*/
fun remove(key: String?) {
editor!!.remove(key).apply()
}
/**
* SP中是否存在该key
*
* @param key 键
* @return `true`: 存在<br></br>`false`: 不存在
*/
operator fun contains(key: String?): Boolean {
return sp!!.contains(key)
}
/**
* SP中清除所有数据
*/
fun clear() {
editor!!.clear().apply()
}
}
Toast & Snackbar 消息展示
Toast
在Android 12 及以上的Android版本Toast
将会被限制使用,当应用处于前台时,应首选Snackbar
,但Toast
很多Application低版本的Android还在使用,这里封装类也加上Toast
。
/**
* 防止重复点击toast,一直显示未隐藏
*/
class ShowMessage private constructor(){
private val context: Context = MyApplication.getContext()
// 之前显示的内容
private var oldMsg: String? = null
// Toast对象
private var toast: Toast? = null
// Toast对象
private var snackbar: Snackbar? = null
// 第一次时间
private var oneTime: Long = 0
// 第二次时间
private var twoTime: Long = 0
companion object {
// 单例模式
val instance: ShowMessage by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { ShowMessage() }
}
/**
* 显示Toast
* @param message
*/
fun showToast(message: String) {
if (toast == null) {
toast = Toast.makeText(context, message, Toast.LENGTH_SHORT)
toast!!.show()
oneTime = System.currentTimeMillis()
} else {
twoTime = System.currentTimeMillis()
if (message == oldMsg) {
if (twoTime - oneTime > Toast.LENGTH_SHORT) {
toast!!.show()
}
} else {
oldMsg = message
toast!!.setText(message)
toast!!.show()
}
}
oneTime = twoTime
}
/**
* 显示Snackbar
* @param view
* @param message
*/
fun showSnackbar(view : View, message: String) {
if (snackbar == null) {
snackbar = Snackbar.make(view, message, Snackbar.LENGTH_SHORT)
snackbar!!.show()
oneTime = System.currentTimeMillis()
} else {
twoTime = System.currentTimeMillis()
if (message == oldMsg) {
if (twoTime - oneTime > Toast.LENGTH_SHORT) {
snackbar!!.show()
}
} else {
oldMsg = message
snackbar!!.setText(message)
snackbar!!.show()
}
}
oneTime = twoTime
}
}
Log日志打印
Android自带的Log
日志打印超出一定长度时会自动截断日志内容,后面的那一段日志内容不会打印出来,在此也做一个封装,如果没打印完日志,则换行继续打印。
打包发布APK时,将isPrintLog
设置为false
,日志不会再打印,这样就不用经常添加、删除Log打印日志的代码啦!
/**
* 日志打印
* */
class LogUtils private constructor() {
private var isPrintLog = true
private val LOG_MAXLENGTH = 4000
private val MIDDLE_DIVIDER = "----------- 换行 ------------\n"
private val TOP_DIVIDER = "┌────────────────────────────────────────────────────────"
private val BOTTOM_DIVIDER = "└────────────────────────────────────────────────────────"
private val TAG = LogUtils::class.java.getSimpleName();
companion object {
// 单例模式
val instance: LogUtils by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { LogUtils() }
}
/**
* 控制是否打印日志
*/
fun isPrintLog(pring : Boolean) {
this.isPrintLog = pring
}
/**
* 使用默认的Tag打印日志
*
* msg : String 需要打印的消息
*/
fun v(msg: String) {
v(TAG, msg)
}
/**
* 使用自定义的tag打印日志
*
* tag :String 打印日志使用的标签
* msg : String 需要打印的消息
*/
fun v(tag: String, msg: String) {
if (isPrintLog) {
if (msg == null) {
Log.v(tag, "null")
return
}
var strLength = msg.length
var start = 0
var end = LOG_MAXLENGTH
if (strLength > end) {
val sbf = StringBuffer()
sbf.append("\n" + TOP_DIVIDER).append(msg)
val trueMsg = sbf.toString()
strLength = trueMsg.length
while (strLength > end) {
if (start == 0) {
Log.v(tag, trueMsg.substring(start, end))
start = end
end += LOG_MAXLENGTH
} else {
end -= MIDDLE_DIVIDER.length
Log.v(tag, MIDDLE_DIVIDER + trueMsg.substring(start, end))
start = end
end += LOG_MAXLENGTH
}
}
Log.v(tag, trueMsg.substring(start, strLength))
Log.v(tag, "\n" + BOTTOM_DIVIDER)
} else {
Log.v(tag, msg)
}
}
}
}
Json & 对象互转
依赖Goole的gson库,需导入 implementation 'com.google.code.gson:gson:2.9.0'
或其它版本。
/**
* Gson与对象互转
*
* 需导入 implementation 'com.google.code.gson:gson:2.9.0'
*/
class GsonUtil private constructor() {
companion object{
// 单例模式
val instance : GsonUtil by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { GsonUtil() }
}
/**
* 返回一个JSON对象
*/
private fun getGsonObject(): Gson {
return GsonBuilder().serializeNulls().create()
}
/**
* 对象转Gson字符串
*
* @param obj
* @return
*/
fun <T : Any?> ser(obj: T): String? {
return getGsonObject().toJson(obj)
}
/**
* Gson字符串转可序列化对象
*
* @param obj gson字符串
* @param type 接口
* @return
*/
fun <T : Any?> deser(obj: String?, type: Type?): T? {
return try {
getGsonObject().fromJson(obj, type)
} catch (e: Exception) {
null
}
}
/**
* Gson字符串转可序列化对象
*
* @param obj json字符串
* @param clazz 转化的序列化对象
* @return
*/
fun <T : Any?> deserBequiet(obj: String?, clazz: Class<T>?): T? {
return try {
getGsonObject().fromJson(obj, clazz)
} catch (e: Exception) {
null
}
}
}
屏幕单位转换
/**
* 屏幕单位转换
*/
class DisplayUtils private constructor(){
companion object{
// 单例模式
val instant: DisplayUtils by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { DisplayUtils() }
}
/**
* 获取屏幕的高度
*/
fun getWindowHeight(activity: Activity): Int{
val dm : DisplayMetrics = DisplayMetrics();
activity.windowManager.defaultDisplay.getMetrics(dm)
return dm.heightPixels;
}
/**
* 获取屏幕的宽度
*/
fun getWindowWidth(activity: Activity): Int{
val dm : DisplayMetrics = DisplayMetrics();
activity.windowManager.defaultDisplay.getMetrics(dm)
return dm.widthPixels;
}
/**
* dp单位转换成px单位
*/
fun dpToPx(context: Context,dp : Float): Int{
val scale = context.getResources().getDisplayMetrics().density;
return (dp * scale + 0.5f).toInt();
}
/**
* dp单位转换为px单位
*/
fun dpToPx(context: Context,dp : Int): Int{
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dp.toFloat(),context.getResources().getDisplayMetrics()).toInt();
}
/**
* px单位转换为dip单位
*/
fun pxToDip(context: Context,px : Float): Int{
val scale = context.getResources().getDisplayMetrics().density
return (px / scale + 0.5f).toInt();
}
/**
* dip单位转换为px单位
*/
fun dipToPx(context: Context,dip : Float): Int{
val scale =context.getResources().getDisplayMetrics().density
return (dip * scale + 0.5f).toInt();
}
/**
* sp单位转换为px单位
*/
fun spToPx(context: Context,sp : Float): Int{
val scale = context.getResources().getDisplayMetrics().scaledDensity
return (sp * scale + 0.5f).toInt();
}
/**
* px单位转换为sp单位
*/
fun pxToSp(context: Context,px : Float): Int{
val scale = context.getResources().getDisplayMetrics().scaledDensity
return (px / scale + 0.5f).toInt();
}
}
检查权限 & 权限申请
权限申请单个可以正常申请权限,如果有多个权限需要同时申请,需要结合Activity
类或ComponentActivity
的onRequestPermissionsResult
方法同时使用方可,执意将其封装在Permissions
类里只会造成不必要的麻烦。
当然,权限申请也有第三方的框架,例如RxPermissions
(使用方法参考:RxPermissions)、easypermissions
(使用方法参考:easypermissions),
/**
* 权限处理
*/
class Permissions private constructor() {
private val REQUEST_EXTERNAL_STORAGE = 1;
/** 检查权限、申请权限、尝试监听权限申请状态 */
companion object {
// 单例模式
val instance: Permissions by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { Permissions() }
}
/**
* 检查多个权限
*
* return 返回未授权的权限
*/
fun checkMultiplePermissions(activity: Activity, permissions: Array<String>): String{
permissions.forEach {
if (!checkSinglePermission(activity, it.toString())){
return it
}
}
return ""
}
/**
* 检查一个权限
*/
fun checkSinglePermission(activity: Activity, permission: String): Boolean {
return ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED;
}
/**
* 请求一个权限
*/
fun requestSinglePermission(activity: Activity, permission: String){
if (!checkSinglePermission(activity,permission)){
val t = 1
ActivityCompat.requestPermissions(activity, arrayOf(permission),REQUEST_EXTERNAL_STORAGE)
}
}
}
检查是否开启某个系统功能
class CheckSystemFunctionState private constructor() {
companion object {
// 单例模式
val instant: CheckSystemFunctionState by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { CheckSystemFunctionState() }
}
/**
* 检查蓝牙是否开启
*
* 需要android.permission.BLUETOOTH权限
*/
fun checkBluetoothOpenState(): Boolean {
val adapter: BluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// 是否支持蓝牙模块
if (adapter != null) {
// 蓝牙是否打开
if (adapter.isEnabled) {
return true;
}
}
return false;
}
/**
* 检查NFC是否开启
*/
fun checkNFCOpenState(context: Context): Boolean {
val nfcManager = context!!.getSystemService(Context.NFC_SERVICE) as NfcManager;
val nfcAdapter = nfcManager.defaultAdapter;
if (nfcAdapter != null && nfcAdapter.isEnabled) {
return true;
}
return false;
}
/**
* 检查定位是否开启
*/
fun checkLocationOpenState(context: Context): Boolean {
val locationManager = context!!.getSystemService(Context.LOCATION_SERVICE) as LocationManager;
// 通过GPS卫星定位,定位精度高
val gps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
// 通过手机网络定位
val network = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
if (gps || network) {
return true;
}
return false;
}
/**
* 检查网络是否可用
*
* type: 1 检查网络 2 检查WiFi 3 检查移动网络
*/
fun checkNetWorkConnect(context: Context, type: Int): Boolean {
val manager: ConnectivityManager =
context!!.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager;
when (type) {
1 -> {
val networkinfo = manager.activeNetworkInfo;
if (networkinfo != null)
return networkinfo.isAvailable;
};
2 -> {
val networkInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
return networkInfo.isAvailable;
}
3 -> {
val networkInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
return networkInfo.isAvailable;
}
}
return false;
}
}
防抖
防抖(防止短时间多次点击)使用RxBinding
,需引入:
// rxbinding防抖
implementation 'com.jakewharton.rxbinding4:rxbinding:4.0.0'
public class AntiShake {
private static AntiShake instance;
public static AntiShake getInstance() {
if (instance == null) {
synchronized (AntiShake.class) {
if (instance == null) {
instance = new AntiShake();
}
}
}
return instance;
}
/**
* 防抖---防止短时间多次点击,一秒钟之内只取第一个事件
*
* @param view
* @param consumer 防抖后执行的操作
*/
public void setThrottleFirst(View view, Consumer<Unit> consumer) {
// 监听View点击事件
RxView.clicks(view)
// 一秒钟之内只取第一个事件,防抖操作
.throttleFirst(1, TimeUnit.SECONDS)
.subscribe(consumer);
}
/**
* 防抖---防止短时间多次点击,一秒钟之内只取最后一个事件
*
* @param editText
* @param consumer 防抖后执行的操作
*/
public void setDebounce(EditText editText, Consumer<CharSequence> consumer) {
// 监听文本改变事件
RxTextView.textChanges(editText)
// 一秒钟之内只取最后一个文本改变事件,防抖操作
.debounce(1, TimeUnit.SECONDS)
.subscribe(consumer);
}
}
获取当前时间
class TimeUtil private constructor() {
companion object {
private var timeUtil: TimeUtil? = null
val instance: TimeUtil?
get() {
if (timeUtil == null) {
synchronized(TimeUtil::class.java) {
if (timeUtil == null) {
timeUtil = TimeUtil()
}
}
}
return timeUtil
}
}
/**
* 获取当前日期
*/
fun getDate() : String{
return getDateFormatInfo().split(" ")[0]
}
/**
* 获取当前星期几
*/
fun getWeek() : String{
return getDateFormatInfo().split(" ")[1]
}
/**
* 获取当前时间
*/
fun getTime() : String{
return getDateFormatInfo().split(" ")[2]
}
/**
* 获取时间信息
*/
fun getDateFormatInfo() : String{
// yyyy-MM-dd 年月日 EEEE 星期 HH:mm:ss 时分秒,大写H代表24小时制,小写为12小时制
return DateFormat.format("yyyy-MM-dd EEEE HH:mm:ss", System.currentTimeMillis()).toString()
}
}