Commit 66562843 authored by wanglei's avatar wanglei

大文件获取功能

parents 37094dd6 d8cf82d1
......@@ -50,4 +50,6 @@ dependencies {
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
implementation(libs.glide)
}
\ No newline at end of file
......@@ -2,6 +2,19 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<!-- https://developer.android.com/about/versions/14/changes/partial-photo-video-access?hl=zh-cn -->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
<uses-permission
android:name="android.permission.READ_MEDIA_IMAGES"
tools:ignore="SelectedPhotoAccess" />
<uses-permission
android:name="android.permission.READ_MEDIA_VIDEO"
tools:ignore="SelectedPhotoAccess" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
......
......@@ -5,9 +5,8 @@ import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import com.example.hfilemanagermaster.R
import com.example.hfilemanagermaster.databinding.FragmentFilesBinding
import com.example.hfilemanagermaster.databinding.FragmentManagerBinding
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
......@@ -16,14 +15,13 @@ private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
* Use the [FilesFragment.newInstance] factory method to
* Use the [ManagerFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class FilesFragment : Fragment() {
// TODO: Rename and change types of parameters
class ManagerFragment : Fragment() {
private lateinit var binding: FragmentManagerBinding
private var param1: String? = null
private var param2: String? = null
private lateinit var binding: FragmentFilesBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
......@@ -31,27 +29,31 @@ class FilesFragment : Fragment() {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
(activity as OverviewActivity).setStatusBarColor(R.color.white)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_files, container, false)
binding = FragmentFilesBinding.bind(view)
val view = inflater.inflate(R.layout.fragment_manager, container, false)
binding = FragmentManagerBinding.bind(view.rootView)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.ivImage.setOnClickListener {
findNavController().navigate(R.id.imageActivity)
}
binding.ivVideo.setOnClickListener {
findNavController().navigate(R.id.videoActivity)
}
binding.tvUse.text = "25%"
binding.tvFree.text = "75GB"
binding.cardView1.setOnClickListener { }
binding.cardView2.setOnClickListener { }
binding.cardView3.setOnClickListener { }
binding.ivFile.setOnClickListener { }
binding.ivWord.setOnClickListener { }
binding.ivExcel.setOnClickListener { }
binding.ivPdf.setOnClickListener { }
binding.ivPpt.setOnClickListener { }
}
companion object {
......@@ -61,12 +63,12 @@ class FilesFragment : Fragment() {
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment FilesFragment.
* @return A new instance of fragment ManageFragment.
*/
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(param1: String, param2: String) =
FilesFragment().apply {
ManagerFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
......
......@@ -13,14 +13,22 @@ import androidx.core.view.WindowInsetsCompat
import androidx.navigation.findNavController
import com.example.hfilemanagermaster.R
import com.example.hfilemanagermaster.databinding.ActivityOverviewBinding
import com.zxhy.hfilemanagermaster.permission.IntentLauncher
import com.zxhy.hfilemanagermaster.permission.PermissionLauncher
class OverviewActivity : AppCompatActivity() {
private lateinit var binding: ActivityOverviewBinding
lateinit var permissionLauncher: PermissionLauncher
lateinit var intentLauncher: IntentLauncher
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
permissionLauncher = PermissionLauncher(this)
intentLauncher = IntentLauncher(this)
binding = ActivityOverviewBinding.inflate(layoutInflater)
setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
......
package com.zxhy.hfilemanagermaster.data
import android.net.Uri
const val MediaDataC_TYPE_IMAGE = 101
const val MediaDataC_TYPE_VIDEO = 102
data class MediaDataC(
val name: String = "File name",
val size: String = "20MB",
val time: String = "January 5, 2024",
val type: Int = MediaDataC_TYPE_IMAGE,
val uri: Uri = Uri.EMPTY,
val path: String = "",
var select: Boolean = false,
)
package com.zxhy.hfilemanagermaster.files
import android.Manifest
import android.os.Bundle
import android.view.Display
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.example.hfilemanagermaster.R
import com.example.hfilemanagermaster.databinding.FragmentFilesBinding
import com.zxhy.hfilemanagermaster.OverviewActivity
import com.zxhy.hfilemanagermaster.knife.getRecentPhoto
import com.zxhy.hfilemanagermaster.knife.testPhoto
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.security.Permission
import java.security.Permissions
/**
* A simple [Fragment] subclass.
* Use the [FilesFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class FilesFragment : Fragment() {
private lateinit var binding: FragmentFilesBinding
private lateinit var imageAdapter: ImageAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
}
(activity as OverviewActivity).setStatusBarColor(R.color.white)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_files, container, false)
binding = FragmentFilesBinding.bind(view)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
imageAdapter = ImageAdapter()
binding.ivImage.setOnClickListener {
findNavController().navigate(R.id.imageActivity)
}
binding.ivVideo.setOnClickListener {
findNavController().navigate(R.id.videoActivity)
}
lifecycleScope.launch(Dispatchers.IO) {
val list = requireContext().testPhoto()
launch(Dispatchers.Main) {
binding.rv.adapter = imageAdapter
imageAdapter.setData(list)
}
}
}
companion object {
@JvmStatic
fun newInstance(param1: String, param2: String) =
FilesFragment().apply {
arguments = Bundle().apply {
}
}
}
}
\ No newline at end of file
package com.zxhy.hfilemanagermaster.files
import android.annotation.SuppressLint
import android.net.Uri
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.hfilemanagermaster.R
import com.example.hfilemanagermaster.databinding.ItemImageBinding
class ImageAdapter : RecyclerView.Adapter<ImageAdapter.ImageViewHolder>() {
private val imageList = arrayListOf<Uri>()
class ImageViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val binding = ItemImageBinding.bind(view)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
val root = R.layout.item_image.inflate(parent)
return ImageViewHolder(root)
}
override fun getItemCount(): Int {
return imageList.size
}
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
// val context = holder.binding.root.context
val data = imageList[position]
holder.binding.iv.setImageURI(data)
}
/**
* 解析xml布局
*
* @param parent 父布局
* @param attachToRoot 是否依附到父布局
*/
fun Int.inflate(parent: ViewGroup, attachToRoot: Boolean = false): View {
return LayoutInflater.from(parent.context).inflate(this, parent, attachToRoot)
}
@SuppressLint("NotifyDataSetChanged")
fun setData(data: List<Uri>) {
imageList.clear()
imageList.addAll(data)
notifyDataSetChanged()
}
}
package com.zxhy.hfilemanagermaster.glide
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.module.AppGlideModule
/**
* 说明:使用Glide的注解,用于生产出GlideApp,该文件需要处于包的根目录
* Created by code_nil on 2017/12/29.
*/
@GlideModule
class GlideAppModule : AppGlideModule() {}
\ No newline at end of file
package com.zxhy.hfilemanagermaster.glide
import android.content.Context
import android.net.Uri
import android.widget.ImageView
import com.bumptech.glide.Glide
import java.net.URI
fun loadIntoImageView(context: Context, uri: String, imageView: ImageView) {
Glide.with(context)
.load(uri)
.into(imageView)
}
fun loadIntoImageView(context: Context, uri: Uri, imageView: ImageView) {
Glide.with(context)
.load(uri)
.into(imageView)
}
\ No newline at end of file
package com.zxhy.hfilemanagermaster.knife
import android.content.ContentResolver
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
/**
* Uri获取File
*/
/**
* 根据Uri获取File路径
*/
fun getFilePathByUri(context: Context, uri: Uri): String? {
var path: String? = null
// 以 file:// 开头的
if (ContentResolver.SCHEME_FILE == uri.scheme) {
path = uri.path
return path
}
// 以 content:// 开头的,比如 content://media/extenral/images/media/17766
if (ContentResolver.SCHEME_CONTENT == uri.scheme) {
val cursor = context.contentResolver.query(
uri,
arrayOf(MediaStore.Images.Media.DATA),
null,
null,
null
)
if (cursor != null) {
if (cursor.moveToFirst()) {
val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
if (columnIndex > -1) {
path = cursor.getString(columnIndex)
}
}
cursor.close()
}
return path
}
// 4.4及之后的 是以 content:// 开头的,比如 content://com.android.providers.media.documents/document/image%3A235700
if (ContentResolver.SCHEME_CONTENT == uri.scheme) {
if (DocumentsContract.isDocumentUri(context, uri)) {
if (isExternalStorageDocument(uri)) {
// ExternalStorageProvider
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val type = split[0]
if ("primary".equals(type, ignoreCase = true)) {
path = Environment.getExternalStorageDirectory().toString() + "/" + split[1]
return path
}
} else if (isDownloadsDocument(uri)) {
// DownloadsProvider
val id = DocumentsContract.getDocumentId(uri)
val contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id)
)
path = getDataColumn(context, contentUri, null, null)
return path
} else if (isMediaDocument(uri)) {
// MediaProvider
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val type = split[0]
var contentUri: Uri? = null
when (type) {
"image" -> {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
"video" -> {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
}
"audio" -> {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}
}
val selection = "_id=?"
val selectionArgs = arrayOf(split[1])
path = getDataColumn(context, contentUri, selection, selectionArgs)
return path
}
}
}
return null
}
/**
* 通过 MediaStore Uri获取值,或者其他基于 ContentProviders 的 Uri
*/
fun getDataColumn(
context: Context, uri: Uri?, selection: String?, selectionArgs: Array<String>?
): String? {
var cursor: Cursor? = null
val column = "_data"
val projection = arrayOf(column)
try {
cursor = context.contentResolver.query(
uri!!, projection, selection, selectionArgs, null
)
if (cursor != null && cursor.moveToFirst()) {
val columnIndex = cursor.getColumnIndexOrThrow(column)
return cursor.getString(columnIndex)
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
cursor?.close()
}
return null
}
fun isExternalStorageDocument(uri: Uri): Boolean {
return "com.android.externalstorage.documents" == uri.authority
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
fun isMediaDocument(uri: Uri): Boolean {
return "com.android.providers.media.documents" == uri.authority
}
// Whether the Uri authority is DownloadsProvider.
fun isDownloadsDocument(uri: Uri): Boolean {
return "com.android.providers.downloads.documents" == uri.authority
}
package com.zxhy.hfilemanagermaster.knife
import android.annotation.SuppressLint
import android.content.ContentResolver
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.provider.MediaStore
import android.text.format.Formatter
import com.zxhy.hfilemanagermaster.data.MediaDataC
import com.zxhy.hfilemanagermaster.data.MediaDataC_TYPE_VIDEO
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/**
* MediaStore访问最新媒体
*/
fun Context.testPhoto(): ArrayList<Uri> {
val list = arrayListOf<Uri>()
var cursor: Cursor? = null
// 查询照片的Uri和字段
val uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME)
try {
// 执行查询
val contentResolver: ContentResolver = contentResolver
cursor = contentResolver.query(uri, projection, null, null, null)
// 遍历结果
if (cursor != null && cursor.moveToFirst()) {
do {
// 获取照片的ID和名称
val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID))
val name =
cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME))
// 根据ID构建照片的Uri
val photoUri = ContentUris.withAppendedId(uri, id)
// println(photoUri)
list.add(photoUri)
// 在此处进行照片的操作,例如显示、复制、删除等
// ...
if (list.size == 10) {
break
}
} while (cursor.moveToNext())
} else {
println("无数据")
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
cursor?.close()
}
return list
}
@SuppressLint("SimpleDateFormat")
fun Context.largeVideo(): ArrayList<MediaDataC> {
val list = arrayListOf<MediaDataC>()
var cursor: Cursor? = null
// 查询视频的Uri和字段
val uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(
MediaStore.Video.Media._ID,
MediaStore.Video.Media.DISPLAY_NAME,
MediaStore.Video.Media.SIZE,
)
try {
// 执行查询
val contentResolver: ContentResolver = contentResolver
cursor = contentResolver.query(uri, projection, null, null, null)
// 遍历结果
if (cursor != null && cursor.moveToFirst()) {
do {
// 获取视频的ID
val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID))
//名称
val name =
cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME))
//大小
val size =
cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE))
val sizeS = Formatter.formatFileSize(this, size)
// 根据ID构建视频的Uri
val videoUri = ContentUris.withAppendedId(uri, id)
//时间
val filePath = getFilePathByUri(this, videoUri) ?: ""
val time = File(filePath).lastModified()
// val timeS =
// SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time)
val timeE = SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH).format(time)
list.add(
MediaDataC(
name = name,
size = sizeS,
time = timeE,
MediaDataC_TYPE_VIDEO,
uri = videoUri,
path = filePath
)
)
// 在此处进行视频的操作,例如显示、复制、删除等
// ...
if (list.size == 10) {
break
}
} while (cursor.moveToNext())
} else {
println("无数据")
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
cursor?.close()
}
return list
}
//无效
fun getMediaCreateTime(videoPath: String) {
val retriever = MediaMetadataRetriever()
try {
retriever.setDataSource(videoPath)
var creationTime =
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE) ?: "0"
// 如果需要将UNIX时间戳转换为可读的日期格式
val date = Date(creationTime.toLong() * 1000)
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH)
creationTime = sdf.format(date)
// 输出或使用创建时间
println(creationTime)
} catch (e: Exception) {
e.printStackTrace()
} finally {
retriever.release()
}
}
\ No newline at end of file
package com.zxhy.hfilemanagermaster.knife
import android.annotation.SuppressLint
import android.app.Application
import android.database.Cursor
import android.provider.MediaStore
import java.lang.Exception
/**
* 最近图片
* 无效
*/
@SuppressLint("Recycle")
fun Application.getRecentPhoto() {
val list = arrayListOf<String>()
var cursor: Cursor? = null
val currentTime = System.currentTimeMillis() / 1000 - 60
try {
cursor = this.applicationContext.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA),
MediaStore.Images.Media.DATE_ADDED + " >= ?", arrayOf("$currentTime"),
MediaStore.Images.Media.DATE_ADDED + " DESC"
)
if ((cursor?.count ?: 0) < 1) {
cursor?.close()
}
if (cursor?.moveToFirst() == true) {
val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA))
list.add(path)
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
cursor?.close()
}
}
......@@ -6,12 +6,17 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.navigation.findNavController
import androidx.lifecycle.lifecycleScope
import com.example.hfilemanagermaster.R
import com.example.hfilemanagermaster.databinding.ActivityLargeFileBinding
import com.zxhy.hfilemanagermaster.knife.largeVideo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class LargeFileActivity : AppCompatActivity() {
private lateinit var binding: ActivityLargeFileBinding
private lateinit var mediaSelectAdapter: MediaSelectAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
......@@ -26,5 +31,25 @@ class LargeFileActivity : AppCompatActivity() {
binding.ivArrow.setOnClickListener {
finish()
}
mediaSelectAdapter = MediaSelectAdapter {
binding.ivAll.isSelected = it
}
binding.rv.adapter = mediaSelectAdapter
binding.tvDelete.setOnClickListener {
}
binding.ivAll.setOnClickListener { view ->
view.isSelected = mediaSelectAdapter.setToggleSelect()
}
loadData()
}
private fun loadData() {
lifecycleScope.launch(Dispatchers.IO) {
val list = this@LargeFileActivity.largeVideo()
lifecycleScope.launch(Dispatchers.Main) {
mediaSelectAdapter.setData(list)
}
}
}
}
\ No newline at end of file
......@@ -27,22 +27,13 @@ class LargeFileFragment : Fragment() {
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_large_file, container, false)
binding = FragmentLargeFileBinding.bind(view)
return view
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment LargeFileFragment.
*/
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(param1: String, param2: String) =
LargeFileFragment().apply {
......
package com.zxhy.hfilemanagermaster.largefile
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.hfilemanagermaster.R
import com.example.hfilemanagermaster.databinding.ItemMediaSelectBinding
import com.zxhy.hfilemanagermaster.data.MediaDataC
import com.zxhy.hfilemanagermaster.glide.loadIntoImageView
class MediaSelectAdapter(
val selectAction: ((flag: Boolean) -> Unit)? = null
) : RecyclerView.Adapter<MediaSelectAdapter.MediaSelectViewHolder>() {
private val mediaList = arrayListOf<MediaDataC>()
class MediaSelectViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val binding = ItemMediaSelectBinding.bind(view)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaSelectViewHolder {
val root = R.layout.item_media_select.inflate(parent)
return MediaSelectViewHolder(root)
}
override fun getItemCount(): Int {
return mediaList.size
}
override fun onBindViewHolder(holder: MediaSelectViewHolder, position: Int) {
val context = holder.binding.root.context
val data = mediaList[position]
holder.binding.apply {
loadIntoImageView(context, data.uri, iv)
tvName.text = data.name
tvSize.text = data.size
ivSelector.isSelected = data.select
flSelector.setOnClickListener {
data.select = !data.select
notifyItemChanged(position, "单条刷新")
}
}
if (data == mediaList.last()) {
selectAction?.invoke(mediaList.all { it.select })
}
}
override fun onBindViewHolder(
holder: MediaSelectViewHolder,
position: Int,
payloads: MutableList<Any>
) {
val context = holder.binding.root.context
val data = mediaList[position]
//判断是做局部刷新还是单条刷新
if (payloads.isEmpty()) {
holder.binding.apply {
loadIntoImageView(context, data.uri, iv)
tvName.text = data.name
tvSize.text = data.size
ivSelector.isSelected = data.select
flSelector.setOnClickListener {
data.select = !data.select
notifyItemChanged(position, "单条刷新")
}
if (data == mediaList.last()) {
selectAction?.invoke(mediaList.all { it.select })
}
}
} else {// 单条刷新
holder.binding.apply {
ivSelector.isSelected = data.select
}
super.onBindViewHolder(holder, position, payloads)
selectAction?.invoke(mediaList.all { it.select })
}
}
@SuppressLint("NotifyDataSetChanged")
fun setData(data: List<MediaDataC>) {
mediaList.clear()
mediaList.addAll(data)
notifyDataSetChanged()
}
/**
* 解析xml布局
*
* @param parent 父布局
* @param attachToRoot 是否依附到父布局
*/
fun Int.inflate(parent: ViewGroup, attachToRoot: Boolean = false): View {
return LayoutInflater.from(parent.context).inflate(this, parent, attachToRoot)
}
@SuppressLint("NotifyDataSetChanged")
fun setToggleSelect(): Boolean {
val flag = mediaList.all { it.select }
mediaList.forEach { it.select = !flag }
notifyDataSetChanged()
return !flag
}
}
\ No newline at end of file
package com.zxhy.hfilemanagermaster.manager
import android.Manifest
import android.annotation.SuppressLint
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
......@@ -11,6 +13,9 @@ import com.example.hfilemanagermaster.R
import com.example.hfilemanagermaster.databinding.FragmentManagerBinding
import com.zxhy.hfilemanagermaster.OverviewActivity
import com.zxhy.hfilemanagermaster.knife.getMountInfoList
import com.zxhy.hfilemanagermaster.permission.IntentLauncher
import com.zxhy.hfilemanagermaster.permission.PermissionLauncher
import com.zxhy.hfilemanagermaster.permission.requestPermission
import java.math.BigDecimal
/**
......@@ -23,8 +28,7 @@ class ManagerFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
}
arguments?.let {}
}
override fun onCreateView(
......@@ -45,32 +49,52 @@ class ManagerFragment : Fragment() {
val percentF = BigDecimal(disk.used)
.divide(BigDecimal(disk.total), 2, BigDecimal.ROUND_HALF_UP)
// binding.indicatorView.setPercent(percentF.toFloat())
binding.indicatorView.setPercent(0.3f)
binding.indicatorView.setPercent(percentF.toFloat())
val percent = percentF.multiply(BigDecimal(100)).toInt()
binding.tvUse.text = "$percent%"
(requireActivity() as OverviewActivity).showManager()
binding.cardView1.setOnClickListener { }
binding.cardView2.setOnClickListener { }
binding.cardView3.setOnClickListener { }
binding.ivFile.setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val flag = requestPermission(
requireContext(),
(requireActivity() as OverviewActivity).permissionLauncher,
(requireActivity() as OverviewActivity).intentLauncher,
arrayOf(Manifest.permission.READ_MEDIA_IMAGES), "设置允许访问媒体图片权限"
) {
findNavController().navigate(R.id.filesFragment)
}
if (flag) findNavController().navigate(R.id.filesFragment)
}
}
binding.ivWord.setOnClickListener { }
binding.ivExcel.setOnClickListener { }
binding.ivPdf.setOnClickListener { }
binding.ivPpt.setOnClickListener { }
binding.cardView1.setOnClickListener {
findNavController().navigate(R.id.dupPictureActivity)
// findNavController().navigate(R.id.dupPictureActivity)
}
binding.cardView2.setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val flag = requestPermission(
requireContext(),
(requireActivity() as OverviewActivity).permissionLauncher,
(requireActivity() as OverviewActivity).intentLauncher,
arrayOf(
Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_VIDEO
), "设置允许访问媒体图片视频权限"
) {
findNavController().navigate(R.id.largeFileActivity)
}
if (flag) findNavController().navigate(R.id.largeFileActivity)
}
}
binding.cardView3.setOnClickListener {
findNavController().navigate(R.id.emptyFileActivity)
// findNavController().navigate(R.id.emptyFileActivity)
}
}
......@@ -83,4 +107,5 @@ class ManagerFragment : Fragment() {
}
}
}
}
\ No newline at end of file
package com.zxhy.hfilemanagermaster.permission
import android.content.Intent
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.ActivityResultCaller
import androidx.activity.result.contract.ActivityResultContracts
/**
* 对应Intent的处理
*/
class IntentLauncher(activityResultCaller: ActivityResultCaller) {
private var activityResultCallback: ActivityResultCallback<ActivityResult>? = null
private val intentLauncher =
activityResultCaller.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult: ActivityResult ->
activityResultCallback?.onActivityResult(activityResult)
}
/**
* it.resultCode == Activity.RESULT_OK
*/
fun launch(
intent: Intent,
activityResultCallback: ActivityResultCallback<ActivityResult>? = null
) {
this.activityResultCallback = activityResultCallback
intentLauncher.launch(intent)
}
}
\ No newline at end of file
package com.zxhy.hfilemanagermaster.permission
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.Settings
import androidx.core.content.ContextCompat
/**
* 获取媒体图片
*/
fun requestPermission(
context: Context,
permissionLauncher: PermissionLauncher,
intentLauncher: IntentLauncher,
permission: Array<String>,
tip: String,
agreeAction: (() -> Unit)? = null
): Boolean {
var flag = false
val request: () -> Unit = {
val permissionFlag =
permission.all {
ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
}
if (permissionFlag) {
flag = true
} else {
permissionLauncher.launch(permission) { callback ->
val isGrantAll = callback.values.all { it }
if (!isGrantAll) {
context.alert(tip) {
positiveButton("跳转") { dialog ->
goToPermissionSettings(context, intentLauncher)
dialog.dismiss()
}
negativeButton("取消") { dialog ->
dialog.dismiss()
}
//禁止取消
isCancel(false)
}
} else {
agreeAction?.invoke()
}
}
}
// Toast.makeText(context, "不会等待回调", Toast.LENGTH_SHORT).show()
}
if (permission.contains(Manifest.permission.WRITE_EXTERNAL_STORAGE)
&& permission.contains(Manifest.permission.READ_EXTERNAL_STORAGE)
) {
val managerFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Environment.isExternalStorageManager()
} else {
false
}
if (managerFlag) {
return true
} else {
request()
}
} else {
request()
}
return flag
}
//权限设置界面
private fun goToPermissionSettings(context: Context, intentLauncher: IntentLauncher) {
val intent = appSettingsIntent(context.packageName)
intentLauncher.launch(intent)
}
/**
* 应用设置页面意图
*/
fun appSettingsIntent(packageName: String): Intent {
return Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", packageName, null))
}
package com.zxhy.hfilemanagermaster.permission
import android.app.ActivityManager
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.content.ContextCompat
//检查是否有所有权限
fun havePermission(context: Context, permissions: Array<String>): Boolean {
var havePermission = true
permissions.forEach {
if (ContextCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED) {
havePermission = false
}
}
return havePermission
}
//检查拒绝的权限
fun denyPermission(context: Context, permissions: Array<String>): ArrayList<String> {
val denys = arrayListOf<String>()
permissions.forEach {
if (ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_DENIED) {
denys.add(it)
}
}
return denys
}
package com.zxhy.hfilemanagermaster.permission
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.ActivityResultCaller
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.FragmentActivity
/**
* @param activityResultCaller [ComponentActivity] 实现该接口
*
*/
class PermissionLauncher(activityResultCaller: ActivityResultCaller) {
/**
* [ActivityResultContracts.RequestMultiplePermissions] 权限协议回调
* ActivityResultContract<String[], java.util.Map<String, Boolean>>
* String[] 表示输入权限
* Map<String, Boolean> 表示权限回调的结果
* 可以参考范型实现 [ResultLauncher]
*/
private var permissionCallback: ActivityResultCallback<Map<String, Boolean>>? = null
//必须在Activity或者Fragment显示之前注册
//说实话有点傻逼,进过测试,可以直接在Activity的onCreate中初始化
private val permissionLauncher =
activityResultCaller.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result: Map<String, Boolean> ->
permissionCallback?.onActivityResult(result)
}
fun launch(
permissionArray: Array<String>,
permissionCallback: ActivityResultCallback<Map<String, Boolean>>?
) {
this.permissionCallback = permissionCallback
permissionLauncher.launch(permissionArray)
}
}
......@@ -30,7 +30,9 @@ class IndicatorView : View {
private var scale = 1f//指针图像缩放比例
private var rotationDegrees = 0f // 旋转的角度
private var indicatorY = 0.95f//绘制指针画布Y移动的距离
private var indicatorX = 0f//绘制指针画布X移动的修补
private var rotationDegreesOffset = 0f//指针旋转误差修补
// 画原始刻度的画笔
private var arcPaint: Paint = Paint()
......@@ -91,29 +93,84 @@ class IndicatorView : View {
super.onDraw(canvas)
Log.e("IndicatorView", "radius=$radius")
// Log.e("IndicatorView", "radius=$radius")
val left = hMargin + strokeWidth / 2
val top = topMargin + strokeWidth / 2
val right = radius * 2 + left
val bottom = radius * 2 + top
Log.e("IndicatorView", "left=$left top=$top right=$right bottom=$bottom")
// Log.e("IndicatorView", "left=$left top=$top right=$right bottom=$bottom")
//绘制半圆
val arcRect = RectF(left, top, right, bottom)
canvas.drawArc(arcRect, -180f, sweepAngle, false, arcPaint)
canvas.translate((measuredWidth / 2).toFloat() + 50, measuredHeight - 250f)
canvas.translate(
(measuredWidth / 2).toFloat() - (bgBitmap.width / 2) * indicatorX,
measuredHeight.toFloat() * indicatorY
)
// 绘制旋转后的图片
// val srcRect = Rect(0, 0, 100, 100)
// val dstRect = Rect(0, 0, 100, 100)
//绘制指针
canvas.rotate(rotationDegrees)
canvas.save()
canvas.drawBitmap(bgBitmap, 0f, -bgBitmap.height.toFloat(), Paint())
}
//绘制百分比
fun setPercent(percent: Float) {
sweepAngle = 180f * percent
rotationDegrees = 180f * percent-80f
rotationDegrees = 180f * percent - 90f
// sweepAngle = 180f
// rotationDegrees = 180f - 90f
//针对指针角度进行误差调整
when (sweepAngle) {
in 0f..29f -> {
indicatorY = 0.95f
indicatorX = 0f
rotationDegreesOffset = -8f
}
in 30f..59f -> {
indicatorY = 0.98f
indicatorX = 0f
rotationDegreesOffset = -5f
}
in 60f..89f -> {
indicatorY = 0.95f
indicatorX = 1f
rotationDegreesOffset = 0f
rotationDegrees += rotationDegreesOffset
}
in 90f..99f -> {
indicatorY = 0.95f
indicatorX = 1f
rotationDegreesOffset = 0f
}
in 100f..119f->{
indicatorY = 0.95f
indicatorX = 1f
rotationDegreesOffset = 2f
}
in 120f..149f -> {
indicatorY = 0.85f
indicatorX = 1.05f
rotationDegreesOffset = 8f
}
in 150f..180f -> {
indicatorY = 0.85f
indicatorX = 1.08f
rotationDegreesOffset = 8f
}
}
rotationDegrees += rotationDegreesOffset
// Log.e("IndicatorView", "rotationDegrees=$rotationDegrees")
invalidate()
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:innerRadius="7dp"
android:shape="ring"
android:thickness="1dp"
android:useLevel="false">
<solid android:color="#999999" />
<size
android:width="18dp"
android:height="18dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@mipmap/dd_7865211111" android:state_selected="true" />
<item android:drawable="@mipmap/dd_78652" android:state_selected="false" />
</selector>
\ No newline at end of file
......@@ -51,16 +51,17 @@
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText,RtlHardcoded" />
<ImageView
android:id="@+id/iv_all"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginRight="5dp"
android:src="@mipmap/dd_78652"
android:src="@drawable/bg_media_selector"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toLeftOf="@id/tv_all"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlHardcoded" />
tools:ignore="ContentDescription,RtlHardcoded" />
</androidx.constraintlayout.widget.ConstraintLayout>
......@@ -111,12 +112,14 @@
android:id="@+id/rv"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginHorizontal="20dp"
android:layout_marginHorizontal="12dp"
android:layout_marginVertical="16dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toTopOf="@id/tv_delete"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_file" />
app:layout_constraintTop_toBottomOf="@id/tv_file"
tools:listitem="@layout/item_media_select" />
<TextView
......
......@@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.zxhy.hfilemanagermaster.FilesFragment">
tools:context="com.zxhy.hfilemanagermaster.files.FilesFragment">
<TextView
......@@ -190,6 +190,17 @@
app:cardCornerRadius="8dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_recent" />
app:layout_constraintTop_toBottomOf="@id/tv_recent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_marginHorizontal="6dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_image" />
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="6dp"
android:layout_marginVertical="12dp"
app:cardCornerRadius="8dp">
<ImageView
android:id="@+id/iv"
android:layout_width="80dp"
android:layout_height="80dp"
tools:ignore="ContentDescription" />
</androidx.cardview.widget.CardView>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:layout_marginVertical="8dp">
<ImageView
android:id="@+id/iv"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginLeft="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription,RtlHardcoded" />
<TextView
android:id="@+id/tv_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:paddingEnd="5dp"
android:text="File name"
android:textColor="#333333"
android:textSize="14sp"
app:layout_constraintBottom_toTopOf="@id/tv_size"
app:layout_constraintLeft_toRightOf="@id/iv"
app:layout_constraintRight_toLeftOf="@id/fl_selector"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText,MissingConstraints,RtlHardcoded,RtlSymmetry" />
<TextView
android:id="@+id/tv_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="20MB"
android:textColor="#999999"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="@id/tv_name"
app:layout_constraintTop_toBottomOf="@id/tv_name"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:text="January 5, 2024"
android:textColor="#999999"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/tv_size"
app:layout_constraintTop_toBottomOf="@id/tv_name"
tools:ignore="HardcodedText,RtlHardcoded" />
<FrameLayout
android:id="@+id/fl_selector"
android:layout_width="36dp"
android:layout_height="36dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/iv_selector"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_gravity="center"
android:src="@drawable/bg_media_selector"
tools:ignore="ContentDescription,RtlHardcoded" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
......@@ -37,7 +37,7 @@
</fragment>
<fragment
android:id="@+id/filesFragment"
android:name="com.zxhy.hfilemanagermaster.FilesFragment"
android:name="com.zxhy.hfilemanagermaster.files.FilesFragment"
android:label="fragment_files"
tools:layout="@layout/fragment_files" >
<action
......
......@@ -11,6 +11,7 @@ activity = "1.8.0"
constraintlayout = "2.1.4"
navigationFragmentKtx = "2.7.7"
navigationUiKtx = "2.7.7"
glide = "4.16.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
......@@ -24,6 +25,9 @@ androidx-constraintlayout = { group = "androidx.constraintlayout", name = "const
androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" }
androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" }
#第三方
glide = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment