效果演示:
列表采用一个recyclerview实现,定义一个公共父实体类,定义基础属性,各个item类型的实体类拥有自己的属性,继承公共父实体类。然后adapter采用多itemType,多viewHolder处理。
Adapter代码:
class MutipleAdapter(val context: Context): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var itemList = CopyOnWriteArrayList<AdapterItem>()
fun appendDatas(datas: List<AdapterItem>): MutipleAdapter {
this.itemList.addAll(datas)
notifyDataSetChanged()
return this
}
fun appendData(data: AdapterItem): MutipleAdapter {
this.itemList.add(data)
return this
}
fun clearData() {
itemList.clear()
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == ITEM_TYPE_FILE) {
ViewHolderFile(ItemFileBinding.inflate(LayoutInflater.from(parent.context), parent, false))
} else if (viewType == ITEM_TYPE_CONTACT) {
ViewHolderContact(ItemContactBinding.inflate(LayoutInflater.from(parent.context), parent, false))
} else if (viewType == ITEM_TYPE_App) {
ViewHolderApp(ItemAppBinding.inflate(LayoutInflater.from(parent.context), parent, false))
} else {
ViewHolderHeader(ItemHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
}
override fun getItemCount() = itemList.size
override fun getItemViewType(position: Int): Int {
return itemList[position].itemType
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
var itemData = itemList[position]
if (holder is ViewHolderHeader) {
holder.bind(itemData)
} else if (holder is ViewHolderFile) {
holder.bind(itemData)
} else if (holder is ViewHolderContact) {
holder.bind(itemData)
} else if (holder is ViewHolderApp) {
holder.bind(itemData)
}
}
inner class ViewHolderHeader(val binding: ItemHeaderBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: AdapterItem) {
binding.tvHeaderTitle.text = item.headerTitle
}
}
inner class ViewHolderFile(val binding: ItemFileBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: AdapterItem) {
if (item is FileBean) {
binding.tvFileName.text = item.fileName
binding.tvFileInfo.text = "" + (item.fileSize/1000) + "KB"
setFileIcon(binding.ivIcon, item.fileName)
}
}
}
inner class ViewHolderContact(val binding: ItemContactBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: AdapterItem) {
if (item is ContactBean) {
binding.tvName.text = item.name
binding.tvNumber.text = item.number
binding.tvCall.setOnClickListener {
callPhone(item.number)
}
}
}
}
inner class ViewHolderApp(val binding: ItemAppBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: AdapterItem) {
if (item is AppBean) {
binding.tvAppName.text = item.name
item.intent?.let {
binding.ivIcon.background = context.packageManager.getActivityIcon(it)
}
}
}
}
/**
* 根据文件后缀类型设置对应类型图标
*/
private fun setFileIcon(imageView: ImageView, fileName: String) {
if (fileName.endsWith(".jpg") || fileName.endsWith(".mp4")) {
imageView.background = context.resources.getDrawable(R.drawable.category_file_icon_pic_phone)
} else {
var drawableId = 0
if (fileName.endsWith(".txt") || fileName.endsWith(".pdf")) {
drawableId = R.drawable.category_file_icon_doc_phone
} else if (fileName.endsWith(".zip")) {
drawableId = R.drawable.category_file_icon_zip_phone
} else if (fileName.endsWith(".mp3")) {
drawableId = R.drawable.category_file_icon_music_phone
} else if (fileName.endsWith(".apk")) {
drawableId = R.drawable.category_file_icon_apk_phone
} else {
drawableId = R.drawable.ic_local_file
}
imageView.background = context.resources.getDrawable(drawableId)
}
}
/**
* 拨打电话
*/
fun callPhone(phoneNumber: String) {
var intent = Intent(Intent.ACTION_CALL)
var data = Uri.parse("tel:$phoneNumber")
intent.data = data
context.startActivity(intent)
}
}
各个实体类代码:
open class AdapterItem(var itemType: Int = 0, val headerTitle: String = "")
class AppBean(val pkg: String, val icon: Int, val name: String, val intent: Intent?): AdapterItem() {
init {
itemType = ItemType.ITEM_TYPE_App
}
}
class ContactBean(val name: String, val number: String): AdapterItem() {
init {
itemType = ItemType.ITEM_TYPE_CONTACT
}
}
data class FileBean(val fileName: String, val path: String, val fileSize: Int): AdapterItem() {
init {
itemType = ITEM_TYPE_FILE
}
}
获取本地应用:
object SearchAppProvider {
fun searchInstallApps(context: Context): List<AppBean>? {
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
checkInstallAppsBeforeL(context)
} else {
checkInstallAppsAfterL(context)
}
}
private fun checkInstallAppsBeforeL(context: Context): List<AppBean> {
val apps: MutableList<AppBean> = ArrayList()
val pm = context.packageManager
try {
val packageInfos = pm.getInstalledPackages(0)
for (i in packageInfos.indices) {
val pkgInfo = packageInfos[i]
val AppBean = pkgInfo.applicationInfo
if (TextUtils.equals(context.packageName, pkgInfo.packageName)) continue
val intent = getLaunchIntent(pm, pkgInfo.packageName)
intent!!.flags =
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED or Intent.FLAG_ACTIVITY_CLEAR_TOP
val app = AppBean(
pkgInfo.packageName,
AppBean.icon,
AppBean.loadLabel(pm).toString(),
intent
)
apps.add(app)
}
} catch (e: Exception) {
//
}
return apps
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private fun checkInstallAppsAfterL(context: Context): List<AppBean>? {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null
val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
?: return null
val apps: MutableList<AppBean> = ArrayList()
try {
val activityInfos = launcherApps.getActivityList(null, Process.myUserHandle())
for (activityInfo in activityInfos) {
val AppBean = activityInfo.applicationInfo
val intent = Intent(Intent.ACTION_MAIN)
intent.addCategory(Intent.CATEGORY_LAUNCHER)
intent.setPackage(AppBean.packageName)
intent.component = activityInfo.componentName
intent.flags =
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED or Intent.FLAG_ACTIVITY_CLEAR_TOP
val app = AppBean(
AppBean.packageName,
AppBean.icon,
activityInfo.label.toString(),
intent
)
apps.add(app)
}
} catch (e: Exception) {
}
return apps
}
private fun getLaunchIntent(pm: PackageManager, pkg: String): Intent? {
var intent = pm.getLaunchIntentForPackage(pkg)
return if (intent != null) {
intent
} else {
intent = Intent(Intent.ACTION_MAIN)
intent.addCategory(Intent.CATEGORY_LAUNCHER)
intent.setPackage(pkg)
val apps = pm.queryIntentActivities(intent, 0)
if (apps == null || apps.isEmpty()) {
return null
}
val ri = apps.iterator().next() ?: return null
intent.component = ComponentName(pkg, ri.activityInfo.name)
intent
}
}
}
模糊查询联系人:
object SearchContactProvider {
@SuppressLint("Range")
fun readContacts(context: Context) {
//ContactsContract.CommonDataKinds.Phone 联系人表
var cursor: Cursor? = context.contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null, null, null, null)
cursor?.let {
while (it.moveToNext()) {
//读取通讯录的姓名
var name = it.getString(it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
//读取通讯录的号码
var number = cursor.getString(it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
Log.i("minfo", "$name--$number")
}
}
}
/**
* 模糊查询联系人
*/
@SuppressLint("Range")
fun searchContact(context: Context, key: String): List<ContactBean> {
//ContactsContract.CommonDataKinds.Phone 联系人表
var list = ArrayList<ContactBean>()
val projection = arrayOf(
ContactsContract.PhoneLookup.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER
)
val selection = StringBuilder()
selection.append(ContactsContract.Contacts.DISPLAY_NAME)
selection.append(" LIKE '%$key%' ")
var cursor: Cursor? = context.contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
projection, selection.toString(), null, null)
cursor?.let {
while (it.moveToNext()) {
//读取通讯录的姓名
var name = it.getString(it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
//读取通讯录的号码
var number = cursor.getString(it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
Log.i("minfo", "$name--$number")
list.add(ContactBean(name, number))
}
it.close()
}
return list
}
}
模糊查询本地文件:
/**
* 使用contentResolver查询本地各种文件
*/
object SearchFileProvider {
private const val MAX_FILE_COUNT = 20
/**
* 模糊查询本地文件
*/
suspend fun searchLocalFile(context: Context, key: String): List<FileBean> {
var list = ArrayList<FileBean>()
val volumeName = "external"
val columns = arrayOf(MediaStore.Files.FileColumns.DATA)
val selection = MediaStore.Files.FileColumns.DATA + " LIKE '%$key%.mp3' OR " +
MediaStore.Files.FileColumns.DATA + " LIKE '%$key%.json' OR " +
MediaStore.Files.FileColumns.DATA + " LIKE '%$key%.log' OR " +
MediaStore.Files.FileColumns.DATA + " LIKE '%$key%.apk' OR " +
MediaStore.Files.FileColumns.DATA + " LIKE '%$key%.mp4' OR " +
MediaStore.Files.FileColumns.DATA + " LIKE '%$key%.pdf' OR " +
MediaStore.Files.FileColumns.DATA + " LIKE '%$key%.txt' OR " +
MediaStore.Files.FileColumns.DATA + " LIKE '%$key%.jpg' OR " +
MediaStore.Files.FileColumns.DATA + " LIKE '%$key%.zip'"
var cursor: Cursor? = null
try {
cursor = context.contentResolver.query(
MediaStore.Files.getContentUri(volumeName),
columns,
selection,
null,
null
)
if (cursor != null) {
while (cursor.moveToNext()) {
if (list.size < MAX_FILE_COUNT) {
val absolutePath = cursor.getString(0)
File(absolutePath).apply {
if (exists() && !TextUtils.isEmpty(name) && name.contains(".")) {
if (!TextUtils.isEmpty(name)) {
var bean = FileBean(name, path, readBytes().size)
list.add(bean)
}
}
}
} else {
return list
}
}
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
} finally {
try {
if (cursor != null) {
cursor.close()
cursor = null
}
} catch (e: java.lang.Exception) {
}
}
return list
}
}
异步加载各类数据,再使用async/await同步使用数据结果
/**
* 搜索各类数据
*/
private fun loadData(key: String) {
adapter.clearData()
//搜索本地App
val localAppsDeferred = GlobalScope.async(Dispatchers.IO) {
SearchAppProvider.searchInstallApps(applicationContext)
}
//搜索联系人
val contactsDeferred = GlobalScope.async(Dispatchers.IO) {
SearchContactProvider.searchContact(applicationContext, key)
}
//搜索本地文件
val localFilesDeferred = GlobalScope.async(Dispatchers.IO) {
SearchFileProvider.searchLocalFile(applicationContext, key)
}
GlobalScope.launch {
// 通过 await 获取异步任务的结果
val localApps = localAppsDeferred.await()
val contacts = contactsDeferred.await()
val localFiles = localFilesDeferred.await()
withContext(Dispatchers.Main) {
adapter.appendData(AdapterItem(0, "本机应用"))
adapter.appendDatas(localApps!!.take(10))
adapter.appendData(AdapterItem(0, "联系人"))
.appendDatas(contacts)
adapter.appendData(AdapterItem(0, "文件管理")).appendDatas(localFiles) //先添加内容的header,再添加内容
}
}
}
然后,需要能够访问文件,别忘了加上6.0访问权限,获取本地文件、读取联系人访问的权限。并且注意,在targetsdk 29及以下,可以访问所有问题,高于29,则只能够访问到图片,视频,音乐这样的多媒体文件。
requestPermissions(arrayOf(Manifest.permission.READ_CONTACTS,
Manifest.permission.CALL_PHONE, Manifest.permission.WRITE_EXTERNAL_STORAGE), this)