前言
微信聊天框中点击 + 号,会有一个popwindow 提示:你可能要发送的图片(前提是有新的图片入了系统相册),可以通过查询 媒体库获取第一张图片,也可以通过Loader实现,当然他们本质上是一样的。
实现
1,先创建 获取媒体数据库的实体类:
/**
* 遍历本地图片
*/
@Parcelize
class ImageItem( var name:String ?= null , var path:String ?= null, var size:Long =0, var width:Int = 0, var height:Int = 0, var mineType:String ?= null, var addTime:Long = 0) : Parcelable{
}
2,创建ViewModel 包括 查询数据库 注册ContentObserver
/**
* @author dhl
* ViewModel 查询最近保存的图片
* 需要 application 实例,查询数据库
*/
class LatestImagePickerViewModel(application: Application) : BaseViewModel(application) {
companion object {
const val TAG = "LatestImagePicker"
}
private val imageType = arrayOf("image/png", "image/jpeg")
/**
* 判断图片的路径 是否截屏
*/
private val screenShoot = arrayOf(
"screenshot", "screen_shot", "screen-sh", "screen shot",
"screencapture", "screen_capture ", "screen-capture", "screen capture",
"screencap", "screen_cap", "screen-cap", "screen cap"
)
/**
* 至少需要高宽,时间
*/
private val imageProjection = arrayOf( //查询图片需要的数据列
MediaStore.Images.Media.BUCKET_DISPLAY_NAME, //图片的显示名称 aaa.jpg
MediaStore.Images.Media.DATA, //图片的真实路径 /storage/emulated/0/pp/downloader/wallpaper/aaa.jpg
MediaStore.Images.Media.SIZE, //图片的大小,long型 132492
MediaStore.Images.Media.WIDTH, //图片的宽度,int型 1920
MediaStore.Images.Media.HEIGHT, //图片的高度,int型 1080
MediaStore.Images.Media.MIME_TYPE, //图片的类型 image/jpeg
MediaStore.Images.Media.DATE_ADDED //图片被添加的时间,long型 1450518608
)
private val _lvImageData = MutableLiveData<ImageItem>()
val lvMediaData: LiveData<ImageItem>
get() = _lvImageData
private val _lvDataChanged = MutableLiveData<Boolean>()
private val _imageIsScreenShotData = MutableLiveData<Boolean>()
val imageIsScreenShotData :LiveData<Boolean>
get() = _imageIsScreenShotData
val lvDataChanged: LiveData<Boolean>
get() = _lvDataChanged
private var contentObserver: ContentObserver? = null
private fun registerContentObserver() {
if (contentObserver == null) {
contentObserver = getApplication<Application>().contentResolver.registerObserver(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
) {
_lvDataChanged.value = true
}
}
}
fun getLatestImage(bucketId: String? = null) {
launchDataLoad {
val imageItems = queryImages(bucketId)
_lvImageData.postValue(imageItems)
registerContentObserver()
val imagePath = imageItems.path!!.toLowerCase()
screenShoot.forEach {
if(imagePath!!.contains(it) && (System.currentTimeMillis()/1000-imageItems.addTime < 1)) {
_imageIsScreenShotData.postValue(true)
return@forEach
}
}
}
}
/**
* 只获取普通图片,不获取Gif
*/
@WorkerThread
suspend fun queryImages(bucketId: String?):ImageItem {
val imageItem = ImageItem()
withContext(Dispatchers.IO) {
val uri = MediaStore.Files.getContentUri("external")
val sortOrder = MediaStore.Images.Media._ID + " DESC limit 1 "
var selection = (MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) +
" AND " + MediaStore.Images.Media.MIME_TYPE + "=?" +
" or " + MediaStore.Images.Media.MIME_TYPE + "=?"
val data = getApplication<Application>().contentResolver.query(
uri,
imageProjection,
selection,
imageType,
sortOrder
)
if (data!!.moveToFirst()) {
//查询数据
val imageId: String =
data.getString(data.getColumnIndexOrThrow(imageProjection[0]))
val imagePath: String =
data.getString(data.getColumnIndexOrThrow(imageProjection[1]))
val imageSize: Long =
data.getLong(data.getColumnIndexOrThrow(imageProjection[2]))
val imageWidth: Int =
data.getInt(data.getColumnIndexOrThrow(imageProjection[3]))
val imageHeight: Int =
data.getInt(data.getColumnIndexOrThrow(imageProjection[4]))
val imageMimeType: String =
data.getString(data.getColumnIndexOrThrow(imageProjection[5]))
val imageAddTime: Long =
data.getLong(data.getColumnIndexOrThrow(imageProjection[6]))
imageItem.path = imagePath
imageItem.addTime = imageAddTime
}
}
return imageItem
}
override fun onCleared() {
contentObserver?.let {
getApplication<Application>().contentResolver.unregisterContentObserver(it)
}
}
}
BaseModel 实体类 :
/**
* @author dhl
* 封装协程
* 获取最新图片的VM
*/
open class BaseViewModel(application: Application) : AndroidViewModel(application) {
/**
* 取消协程
*/
private val viewModelJob = SupervisorJob()
private val exceptionHandler = CoroutineExceptionHandler { _, t ->
t.printStackTrace()
}
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob + exceptionHandler)
private val _lvError = MutableLiveData<Exception>()
open val lvError: LiveData<Exception>
get() = _lvError
fun launchDataLoad(block: suspend (scope: CoroutineScope) -> Unit): Job {
return uiScope.launch {
try {
block(this)
} catch (error: Exception) {
handleException(error)
} finally {
}
}
}
private fun handleException(error: Exception) {
error.printStackTrace()
if (error !is CancellationException) {
_lvError.value = error
}
}
public override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}
3,监听媒体数据的变化
/**
* 监听媒体数据的变化
*/
fun ContentResolver.registerObserver(
uri: Uri,
observer: (selfChange: Boolean) -> Unit
): ContentObserver {
val contentObserver = object : ContentObserver(Handler()) {
override fun onChange(selfChange: Boolean) {
observer(selfChange)
}
}
// notifyForDescendants true 华为手机上 传值 false 会有延迟
registerContentObserver(uri, true, contentObserver)
return contentObserver
}
实战
UI调用 :
class MainActivity : AppCompatActivity() {
companion object{
const val TAG = "MainActivity"
}
var permissions = Manifest.permission.READ_EXTERNAL_STORAGE
var permissionArray = arrayOf(permissions)
var btn: Button? = null
var editText: EditText? = null
var currentImageItem: ImageItem? = null
var imageView: ImageView? = null
lateinit var viewModel: LatestImagePickerViewModel
@RequiresApi(Build.VERSION_CODES.M)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory(this.application)
).get(LatestImagePickerViewModel::class.java)
val checkPermission = this?.let { ActivityCompat.checkSelfPermission(it, permissions) }
if (checkPermission == PackageManager.PERMISSION_GRANTED) {
viewModel.getLatestImage(null)
} else {
requestPermissions(permissionArray, 0)
}
btn = findViewById(R.id.photo_btn)
imageView = findViewById(R.id.image)
editText = findViewById(R.id.et_view)
btn?.setOnClickListener {
showInput(editText!!)
currentImageItem?.let {
if((System.currentTimeMillis()/1000 - 12) <it.addTime) { // 当前时间和 图片的时间差小于 8S 才展示
if (!TextUtils.isEmpty(it.path)) {
LatestImagePop(btn!!.context, it.path!!).showPop(btn!!)
}
currentImageItem = null
}
}
}
viewModel.lvMediaData.observe(this, Observer { data ->
currentImageItem = data
Glide.with(this).load(currentImageItem!!.path).into(imageView)
})
viewModel.lvDataChanged.observe(this, Observer {
viewModel.getLatestImage(null)
})
viewModel.imageIsScreenShotData.observe(this, Observer {
Toast.makeText(this,"你截屏了!",Toast.LENGTH_LONG).show()
})
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
0 ->
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
viewModel.getLatestImage(null)
}
}
}
fun showInput(et: EditText) {
et.requestFocus()
val imm: InputMethodManager =
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(et, InputMethodManager.SHOW_IMPLICIT)
}
}
首次进入页面,直接查询数据,图片的addTime 与当前的系统时间对比,我这里小于设置12s,就认为是你可能要发送的图片,当媒体库发生变化的时候,触发监听,通知UI。
总结
以上就是你可能要发送的图片功能,有两个细节,查询本地媒体库一定是查询 addTime 字段而不是dataToken 字段,每次媒体库发生了变化就去更新数据而不是点击+号再去查询数据库。 上面的功能也附带了截屏的逻辑,仅供参考。