Last active
July 2, 2022 02:59
-
-
Save koinzhang/6a0c92872af4f9ac892b749faa8152f0 to your computer and use it in GitHub Desktop.
[Utils] Android Utils Code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context | |
import android.graphics.* | |
import android.graphics.Bitmap.CompressFormat | |
import android.graphics.drawable.Drawable | |
import android.renderscript.Allocation | |
import android.renderscript.Element | |
import android.renderscript.RenderScript | |
import android.renderscript.ScriptIntrinsicBlur | |
import android.text.TextUtils | |
import android.util.Log | |
import java.io.* | |
import java.util.* | |
object BitmapUtils { | |
private val randomColor: Int | |
private get() { | |
val random = Random() | |
return -0x1000000 or random.nextInt(0x00ffffff) | |
} | |
/** | |
* @方法描述 将RGB字节数组转换成Bitmap, | |
*/ | |
fun rgb2SafeBitmap(data: ByteArray, width: Int, height: Int): Bitmap? { | |
val colors = convertByteToColor(data) ?: return null //取RGB值转换为int数组 | |
var bmp = Bitmap.createBitmap( | |
colors, 0, width, width, height, | |
Bitmap.Config.RGB_565 | |
) | |
val matrix = Matrix() | |
val wScale = 384.0f / width | |
val hScale = 512.0f / height | |
val realScale = if (wScale > hScale) hScale else wScale | |
matrix.setScale(realScale, realScale) | |
bmp = Bitmap.createBitmap(bmp!!, 0, 0, width, height, matrix, true) | |
return bmp | |
} | |
/** | |
* @方法描述 将RGB字节数组转换成Bitmap, | |
*/ | |
fun rgb2Bitmap(data: ByteArray, width: Int, height: Int): Bitmap? { | |
val colors = convertByteToColor(data) ?: return null //取RGB值转换为int数组 | |
return Bitmap.createBitmap( | |
colors, 0, width, width, height, | |
Bitmap.Config.ARGB_8888 | |
) | |
} | |
// 将一个byte数转成int | |
// 实现这个函数的目的是为了将byte数当成无符号的变量去转化成int | |
private fun convertByteToInt(data: Byte): Int { | |
return data.toInt() and 0x0F | |
} | |
// 将纯RGB数据数组转化成int像素数组 | |
fun convertByteToColor(data: ByteArray): IntArray? { | |
val size = data.size | |
if (size == 0) { | |
return null | |
} | |
var arg = 0 | |
if (size % 3 != 0) { | |
arg = 1 | |
} | |
// 一般RGB字节数组的长度应该是3的倍数, | |
// 不排除有特殊情况,多余的RGB数据用黑色0XFF000000填充 | |
val color = IntArray(size / 3 + arg) | |
var red: Int | |
var green: Int | |
var blue: Int | |
val colorLen = color.size | |
if (arg == 0) { | |
for (i in 0 until colorLen) { | |
red = convertByteToInt(data[i * 3]) | |
green = convertByteToInt(data[i * 3 + 1]) | |
blue = convertByteToInt(data[i * 3 + 2]) | |
// 获取RGB分量值通过按位或生成int的像素值 | |
color[i] = red shl 16 or (green shl 8) or blue or -0x1000000 | |
} | |
} else { | |
for (i in 0 until colorLen - 1) { | |
red = convertByteToInt(data[i * 3]) | |
green = convertByteToInt(data[i * 3 + 1]) | |
blue = convertByteToInt(data[i * 3 + 2]) | |
color[i] = red shl 16 or (green shl 8) or blue or -0x1000000 | |
} | |
color[colorLen - 1] = -0x1000000 | |
} | |
return color | |
} | |
//将bitmap保存到fileName指定的文件中 | |
fun saveBitmap2File(bitmap: Bitmap?, fileName: String, filePath: String) { | |
if (bitmap == null || TextUtils.isEmpty(fileName)) { | |
return | |
} | |
val path = "$filePath/$fileName.jpg" | |
val compressFormat = CompressFormat.JPEG | |
val quality = 100 | |
var outputStream: OutputStream? = null | |
try { | |
outputStream = FileOutputStream(path) | |
bitmap.compress(compressFormat, quality, outputStream) | |
} catch (e: Exception) { | |
e.printStackTrace() | |
} finally { | |
try { | |
outputStream!!.flush() | |
outputStream.close() | |
} catch (e: IOException) { | |
e.printStackTrace() | |
} | |
} | |
} | |
//读取fileName指定的bitmap,并将fileName对应的文件删除 | |
fun getBitmapFromFile(fileName: String, filePath: String): Bitmap? { | |
if (TextUtils.isEmpty(fileName)) { | |
return null | |
} | |
val path = "$filePath/$fileName.jpg" | |
var bitmap: Bitmap? = null | |
try { | |
val fileInputStream = FileInputStream(path) | |
bitmap = BitmapFactory.decodeStream(fileInputStream) | |
fileInputStream.close() | |
val file = File(path) | |
if (file.exists()) { | |
file.delete() | |
} | |
} catch (e: FileNotFoundException) { | |
e.printStackTrace() | |
} catch (e: IOException) { | |
e.printStackTrace() | |
} | |
return bitmap | |
} | |
fun Bitmap2Bytes(bm: Bitmap): ByteArray { | |
val baos = ByteArrayOutputStream() | |
bm.compress(CompressFormat.PNG, 100, baos) | |
return baos.toByteArray() | |
} | |
/** | |
* 按比例缩放图片 | |
* | |
* @param origin 原图 | |
* @param ratio 比例 | |
* @return 新的bitmap | |
*/ | |
fun scaleBitmap(origin: Bitmap?, ratio: Float, x: Int, y: Int): Bitmap? { | |
if (origin == null) { | |
return null | |
} | |
val width = origin.width | |
val height = origin.height | |
val matrix = Matrix() | |
matrix.setScale(ratio, ratio, (width / 2).toFloat(), (height / 2).toFloat()) | |
matrix.postTranslate(x * ratio, y * ratio) | |
val afterBitmap = Bitmap.createBitmap(width, height, origin.config) | |
// 设置画笔,消除锯齿 | |
val paint = Paint() | |
paint.isAntiAlias = true | |
val canvas = Canvas(afterBitmap) | |
canvas.drawBitmap(origin, matrix, paint) | |
//origin.recycle(); | |
return afterBitmap | |
} | |
fun calPictureCenter( | |
origin: Bitmap, | |
userDot: Bitmap?, | |
faceLTX: Int, | |
faceLTY: Int, | |
centerX: Float, | |
centerY: Float, | |
faceW: Int, | |
faceH: Int, | |
scale: Float, | |
circleRadius: Int | |
): CaptureBitmap { | |
var scaleFactor = scale * faceW / (circleRadius * 2) | |
val result = floatArrayOf(centerX, centerY) | |
val captureBitmap = CaptureBitmap() | |
captureBitmap.bitmap = origin | |
captureBitmap.pos = result | |
captureBitmap.userDot = userDot | |
captureBitmap.scaleSize = scale | |
//scaleFactor>=1:脸跟框一样大或者比框大 | |
//faceW != faceH:优图返回的人脸长宽不等(目前优图返回的人脸长宽是等长的) | |
if (scaleFactor >= 1 || faceW != faceH) { | |
Log.e("captureFace", "UITrack,scaleFactor invalid(>=1) || faceW != faceH!") | |
return captureBitmap | |
} | |
var viewWidth = faceW / scaleFactor | |
var space = viewWidth - faceW | |
var halfSpace = space / 2 | |
val bitmapWidth = origin.width | |
val bitmapHeight = origin.height | |
if (bitmapWidth < viewWidth || bitmapHeight < viewWidth) { | |
val tmpSize = Math.min(bitmapHeight, bitmapWidth).toFloat() | |
val destSize = Math.min(tmpSize, (2 * circleRadius).toFloat()) | |
scaleFactor = faceW.toFloat() / destSize | |
if (scaleFactor >= 1) { | |
Log.e("captureFace", "UITrack,scaleFactor invalid(>=1)") | |
return captureBitmap | |
} | |
captureBitmap.scaleSize = circleRadius * 2 * scaleFactor / faceW | |
viewWidth = faceW / scaleFactor | |
space = viewWidth - faceW | |
halfSpace = space / 2 | |
Log.e( | |
"captureFace", | |
"UITrack,viewWidth invalid(>bitmapWidth || > bitmapHeight)!fix scaleSize:" + scale + "->" + captureBitmap.scaleSize | |
) | |
} | |
var left = faceLTX - halfSpace | |
var right = faceLTX + faceW + halfSpace | |
var top = faceLTY - halfSpace | |
var bottom = faceLTY + faceH + halfSpace | |
val leftDistance = left - 0 | |
val rightDistance = bitmapWidth - right | |
val topDistance = top - 0 | |
val bottomDistance = bitmapHeight - bottom | |
if (leftDistance < 0) { | |
left = 0f | |
right = right + Math.abs(leftDistance) | |
result[0] += Math.abs(leftDistance) | |
} else if (rightDistance < 0) { | |
right = bitmapWidth.toFloat() | |
left = left - Math.abs(rightDistance) | |
result[0] -= Math.abs(rightDistance) | |
} | |
if (topDistance < 0) { | |
top = 0f | |
bottom = bottom + Math.abs(topDistance) | |
result[1] += Math.abs(topDistance) | |
} else if (bottomDistance < 0) { | |
bottom = bitmapHeight.toFloat() | |
top = top - Math.abs(bottomDistance) | |
result[1] -= Math.abs(bottomDistance) | |
} | |
Log.i( | |
"captureFace", | |
"UITrack,left=" + left + ",right=" + +right + ",top=" + top + ",bottom=" + bottom + ",viewWidth=" + Math.floor( | |
viewWidth.toDouble() | |
) | |
.toInt() + ",viewHeight=" + | |
Math.floor(viewWidth.toDouble()) | |
.toInt() + " ,origin centerX=" + centerX + ",centerY=" + centerY + ",fixed centerX=" + result[0] + ",centerY=" + result[1] + ",origin scaleSize=" + scale + ",fix scaleSize=" + captureBitmap.scaleSize | |
) | |
var bitmap: Bitmap? = null | |
var userDotBitmap: Bitmap? = null | |
try { | |
bitmap = Bitmap.createBitmap( | |
origin, | |
left.toInt(), | |
top.toInt(), | |
Math.floor(viewWidth.toDouble()).toInt(), | |
Math.floor(viewWidth.toDouble()) | |
.toInt() | |
) | |
userDotBitmap = Bitmap.createBitmap( | |
userDot!!, | |
left.toInt(), | |
top.toInt(), | |
Math.floor(viewWidth.toDouble()).toInt(), | |
Math.floor(viewWidth.toDouble()) | |
.toInt() | |
) | |
} catch (e: Exception) { | |
Log.i("captureFace", "UITrack,createBitmap exception:" + Log.getStackTraceString(e)) | |
captureBitmap.scaleSize = scale | |
return captureBitmap | |
} | |
captureBitmap.pos = result | |
captureBitmap.bitmap = bitmap | |
captureBitmap.userDot = userDotBitmap | |
return captureBitmap | |
} | |
/** | |
* 该方法会创建RenderScript,在Android 5系统上RenderScript中的线程不会自动释放,所以这里需要 | |
* 在Android 5的机器上进行释放 | |
* | |
* @param iContext | |
* @param iBitmap | |
* @param radius | |
* @return | |
*/ | |
fun fastBlur(iContext: Context?, iBitmap: Bitmap?, radius: Int): Bitmap? { | |
if (iBitmap == null) { | |
return null | |
} | |
val bitmap = iBitmap.copy(iBitmap.config, true) | |
val rs = RenderScript.create(iContext) ?: return bitmap | |
val input = Allocation.createFromBitmap( | |
rs, iBitmap, Allocation.MipmapControl.MIPMAP_NONE, | |
Allocation.USAGE_SCRIPT | |
) | |
val output = Allocation.createTyped(rs, input.type) | |
val script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)) | |
script.setRadius(radius /* e.g. 3.f */.toFloat()) | |
script.setInput(input) | |
script.forEach(output) | |
output.copyTo(bitmap) | |
// 这里需要销毁RenderScript对象,否则在Android 5系统上会造成线程泄漏 | |
rs.destroy() | |
return bitmap | |
} | |
fun getBitmap(drawable: Drawable): Bitmap { | |
val bitmap = Bitmap.createBitmap( | |
drawable.intrinsicWidth, | |
drawable.intrinsicHeight, | |
if (drawable.opacity != PixelFormat.OPAQUE) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565 | |
) | |
val canvas = Canvas(bitmap) | |
//canvas.setBitmap(bitmap); | |
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight) | |
drawable.draw(canvas) | |
return bitmap | |
} | |
class CaptureBitmap { | |
var pos: FloatArray? = null | |
var scaleSize = 0f | |
var bitmap: Bitmap? = null | |
var userDot: Bitmap? = null | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.os.Handler | |
import android.os.Looper | |
import android.os.Message | |
import java.util.* | |
object CountDownUtils { | |
private val TOKEN = Any() | |
private val LISTENERS: MutableSet<Listener> = HashSet() | |
private val CALLBACK: Handler.Callback = Handler.Callback { msg -> | |
synchronized(CountDownUtils::class.java) { | |
val seconds = msg.what | |
if (seconds <= 0) { | |
for (listener in LISTENERS) { | |
listener.onFinish() | |
} | |
return@Callback true | |
} else { | |
for (listener in LISTENERS) { | |
listener.onTick(seconds) | |
} | |
HANDLER.sendMessageDelayed( | |
android.os.Message.obtain( | |
HANDLER, | |
seconds - 1, | |
TOKEN | |
), 1000 | |
) | |
return@Callback false | |
} | |
} | |
} | |
private val HANDLER = Handler(Looper.getMainLooper(), CALLBACK) | |
@Synchronized | |
fun start(seconds: Int) { | |
HANDLER.removeCallbacksAndMessages(TOKEN) | |
HANDLER.sendMessageDelayed( | |
Message.obtain(HANDLER, seconds - 1, TOKEN), 1000 | |
) | |
} | |
@Synchronized | |
fun cancel() { | |
HANDLER.removeCallbacksAndMessages(TOKEN) | |
} | |
fun addListener(listener: Listener) { | |
HANDLER.post { LISTENERS.add(listener) } | |
} | |
fun removeListener(listener: Listener) { | |
HANDLER.post { LISTENERS.remove(listener) } | |
} | |
interface Listener { | |
fun onTick(seconds: Int) | |
fun onFinish() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.os.Parcelable | |
import android.util.Log | |
import com.google.gson.Gson | |
import com.google.gson.GsonBuilder | |
import com.google.gson.JsonParser | |
import java.lang.reflect.Type | |
/** | |
* @author koinzhang | |
* @date 2021/12/30 | |
* @email koinzhang@gmail.com | |
*/ | |
object GsonUtils { | |
private const val TAG = "GsonUtils" | |
private val gson = GsonBuilder().create() | |
fun <T> String.fromJson(gson: Gson, cls: Type?): T? { | |
var obj: T? = null | |
try { | |
obj = gson.fromJson(this, cls) | |
} catch (e: Exception) { | |
Log.e(TAG, "fromJson $e") | |
} | |
return obj | |
} | |
fun <T> String.fromJson(cls: Type?): T? { | |
var obj: T? = null | |
try { | |
obj = gson.fromJson(this, cls) | |
} catch (e: Exception) { | |
Log.e(TAG, "fromJson $e") | |
} | |
return obj | |
} | |
fun <T> String.fromJson(gson: Gson, clazz: Class<T>?): T? { | |
return try { | |
gson.fromJson(this, clazz) | |
} catch (e: Throwable) { | |
Log.e(TAG, "fromJson $e") | |
null | |
} | |
} | |
fun <T> String.fromJson(clazz: Class<T>?): T { | |
return this.fromJson(gson, clazz)!! | |
} | |
fun Parcelable.toJson(gson: Gson): String? { | |
var json: String? = null | |
try { | |
json = gson.toJson(this) | |
} catch (e: Exception) { | |
Log.e(TAG, "fromJson $e") | |
} | |
return json | |
} | |
fun Parcelable.toJson(): String? { | |
return this.toJson(gson) | |
} | |
fun Parcelable.toJson(gson: Gson, tTYpe: Type?): String? { | |
var json: String? = null | |
try { | |
json = gson.toJson(this, tTYpe) | |
} catch (e: Exception) { | |
Log.e(TAG, "toJson $e") | |
} | |
return json | |
} | |
fun Parcelable.toJson(tTYpe: Type?): String? { | |
return this.toJson(gson, tTYpe) | |
} | |
fun <T> String.listFromJson(gson: Gson, clazz: Class<T>?): List<T>? { | |
var list: ArrayList<T>? = null | |
try { | |
val jsonArray = JsonParser.parseString(this).asJsonArray | |
list = ArrayList() | |
var i = 0 | |
val size = jsonArray.size() | |
while (i < size) { | |
list.add(gson.fromJson(jsonArray[i], clazz)) | |
++i | |
} | |
} catch (e: Exception) { | |
Log.e(TAG, "fromJsonList $e") | |
} | |
return list | |
} | |
fun <T> String.listFromJson(clazz: Class<T>?): List<T>? { | |
return this.listFromJson(gson, clazz) | |
} | |
fun <T> List<T>.listToJson(gson: Gson): String? { | |
var json: String? = null | |
try { | |
json = gson.toJson(this) | |
} catch (e: Exception) { | |
Log.e(TAG, "listToJson $e") | |
} | |
return json | |
} | |
fun <T> List<T>.listToJson(): String? { | |
return this.listToJson(gson) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.text.SimpleDateFormat | |
import java.util.* | |
object TimeUtils { | |
const val UNIT_MILLI_SECONDS: Long = 1000 | |
const val SECONDS_IN_ONE_HOUR = (60 * 60).toLong() | |
private const val defaultPattern = "yyyy-MM-dd HH:mm:ss" | |
private val sDateFormat = SimpleDateFormat(defaultPattern, Locale.CHINA) | |
/** | |
* unix时间[timestamp]戳转换为日期,单位s | |
* 默认形式 yyyy-MM-dd HH:mm:ss | |
*/ | |
fun TimeStamp2Date(timestamp: Long, pattern: String = defaultPattern): String { | |
if (timestamp > 0) { | |
val date = Date(timestamp * 1000) | |
return SimpleDateFormat(pattern, Locale.CHINA) | |
.format(date) | |
} | |
return "" | |
} | |
/** | |
* 获取当前时间字符串 yyyy-MM-dd HH:mm:ss | |
*/ | |
val currentTimeStr: String get() = sDateFormat.format(Date()) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context | |
import android.graphics.Point | |
import android.graphics.Rect | |
import android.os.Build | |
import android.text.TextUtils | |
import android.util.Log | |
import android.view.View | |
import android.view.WindowManager | |
import android.webkit.WebView | |
import android.widget.AbsListView | |
import android.widget.Button | |
import android.widget.ScrollView | |
import android.widget.TextView | |
object ViewUtils { | |
private const val TAG = "ViewUtils" | |
/** | |
* 获取屏幕的高度 | |
*/ | |
private var sWindowHeight = -1 | |
/** | |
* 尝试获取view的文案 | |
*/ | |
fun View.getViewContent(): String { | |
var result = "" | |
try { | |
if (this is TextView) { //TextView是EditText的父类 | |
val content = this.text | |
result = content?.toString() ?: "" | |
} else if (this is Button) { //Button是CompoundButton的父类 | |
val content = this.text | |
result = content?.toString() ?: "" | |
} | |
} catch (e: Exception) { | |
Log.e(TAG, e.message!!) | |
} | |
return result | |
} | |
/** | |
* 获取View的resourceId | |
*/ | |
fun View.getResourcesId(): String { | |
val resourcesId: String = try { | |
this.resources.getResourceEntryName(this.id) //view.getResources().getResourceName(view.getId()) | |
} catch (e: Exception) { | |
"" | |
} | |
return resourcesId | |
} | |
fun View.getActivityName(): String { | |
val activityName: String = try { | |
this.context.javaClass.simpleName | |
} catch (e: Exception) { | |
Log.e(TAG, e.message!!) | |
"" | |
} | |
return activityName | |
} | |
fun View.getTag(view: View): String { | |
try { | |
val tag = this.tag | |
return if (tag is String) { | |
tag | |
} else { | |
"" | |
} | |
} catch (e: Exception) { | |
Log.e(TAG, e.message!!) | |
} | |
return "" | |
} | |
/** | |
* view是否可见 | |
*/ | |
fun View.isViewVisible(): Boolean { | |
val rect = Rect() | |
return this.isShown && this.getGlobalVisibleRect(rect) && this.isVisibleToUser() | |
} | |
/** | |
* 是否在父组件的可视范围内 | |
*/ | |
private fun View.isVisibleToUser(): Boolean { | |
return try { | |
val xyView = IntArray(2) | |
val xyParent = IntArray(2) | |
val viewHeight = this.height.toFloat() | |
val parent = this.getScrollOrListParent() | |
this.getLocationOnScreen(xyView) | |
if (parent == null) { | |
xyParent[1] = 0 | |
} else { | |
parent.getLocationOnScreen(xyParent) | |
} | |
if (xyView[1] + viewHeight / 2.0f > this.getScrollListWindowHeight()) return false else if (xyView[1] + viewHeight / 2.0f < xyParent[1]) return false | |
true | |
} catch (t: Throwable) { | |
false | |
} | |
} | |
/** | |
* 获取它的滚动父组件 | |
*/ | |
private fun View.getScrollOrListParent(): View? { | |
return if (this !is AbsListView && this !is ScrollView && this !is WebView) { | |
try { | |
this.getScrollOrListParent() | |
} catch (e: Throwable) { | |
null | |
} | |
} else { | |
this | |
} | |
} | |
/** | |
* 获取滚动父组件的高度 | |
*/ | |
private fun View.getScrollListWindowHeight(): Float { | |
val xyParent = IntArray(2) | |
val parent = this.getScrollOrListParent() | |
val windowHeight: Float = if (parent == null) { | |
this.context.getDisplayHeight().toFloat() | |
} else { | |
parent.getLocationOnScreen(xyParent) | |
(xyParent[1] + parent.height).toFloat() | |
} | |
return windowHeight | |
} | |
private fun Context.getDisplayHeight(): Int { | |
if (sWindowHeight == -1) { | |
val display = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { | |
this.display | |
} else { | |
val windowManager = this.getSystemService(Context.WINDOW_SERVICE) as WindowManager | |
windowManager.defaultDisplay | |
} | |
val p = Point() | |
display?.getSize(p) | |
sWindowHeight = p.y | |
} | |
return sWindowHeight | |
} | |
/** | |
* 是否textview可见:可见且内容不为空 | |
*/ | |
fun TextView.isTextViewVisible(): Boolean { | |
return this.visibility == View.VISIBLE && !TextUtils.isEmpty(this.getViewContent()) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment