android 高仿微信你可能要发送的图片

2,001 阅读3分钟

前言

微信聊天框中点击 + 号,会有一个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 字段,每次媒体库发生了变化就去更新数据而不是点击+号再去查询数据库。 上面的功能也附带了截屏的逻辑,仅供参考。

源码: github.com/ThirdPrince…