pull/42/head
kunfei 5 years ago
parent d9e0506c10
commit 03f3a3c3fa
  1. 8
      app/src/main/java/io/legado/app/ui/config/ConfigFragment.kt
  2. 89
      app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt
  3. 283
      app/src/main/java/io/legado/app/ui/filechooser/FilePickerIcon.java
  4. 174
      app/src/main/java/io/legado/app/ui/filechooser/adapter/FileAdapter.kt
  5. 63
      app/src/main/java/io/legado/app/ui/filechooser/adapter/PathAdapter.kt
  6. 17
      app/src/main/java/io/legado/app/ui/filechooser/entity/FileItem.kt
  7. 54
      app/src/main/java/io/legado/app/ui/filechooser/entity/JavaBean.kt
  8. 617
      app/src/main/java/io/legado/app/ui/filechooser/utils/ConvertUtils.java
  9. 226
      app/src/main/java/io/legado/app/ui/filechooser/utils/DateUtils.java
  10. 669
      app/src/main/java/io/legado/app/ui/filechooser/utils/FileUtils.kt
  11. 71
      app/src/main/java/io/legado/app/ui/filechooser/utils/ScreenUtils.java
  12. 161
      app/src/main/java/io/legado/app/ui/filechooser/utils/StorageUtils.java
  13. 9
      app/src/main/res/layout/dialog_file_chooser.xml
  14. 22
      app/src/main/res/layout/item_file_filepicker.xml
  15. 22
      app/src/main/res/layout/item_path_filepicker.xml
  16. 1
      app/src/main/res/values/strings.xml

@ -13,6 +13,7 @@ import io.legado.app.R
import io.legado.app.help.BookHelp
import io.legado.app.lib.theme.ATH
import io.legado.app.receiver.SharedReceiverActivity
import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.utils.LogUtils
import io.legado.app.utils.getPrefString
import io.legado.app.utils.putPrefBoolean
@ -48,6 +49,13 @@ class ConfigFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChange
super.onPause()
}
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
when (preference?.key) {
"downloadPath" -> fragmentManager?.let { FileChooserDialog.show(it) }
}
return super.onPreferenceTreeClick(preference)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
when (key) {
"downloadPath" -> BookHelp.upDownloadPath()

@ -1,18 +1,33 @@
package io.legado.app.ui.filechooser
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R
import io.legado.app.ui.filechooser.adapter.FileAdapter
import io.legado.app.ui.filechooser.adapter.PathAdapter
import io.legado.app.utils.FileUtils
import io.legado.app.utils.gone
import io.legado.app.utils.visible
import kotlinx.android.synthetic.main.dialog_file_chooser.*
class FileChooserDialog : DialogFragment() {
class FileChooserDialog : DialogFragment(),
FileAdapter.CallBack,
PathAdapter.CallBack {
companion object {
const val tag = "FileChooserDialog"
const val DIRECTORY = 0
const val FILE = 1
fun show(manager: FragmentManager) {
val fragment =
@ -21,6 +36,17 @@ class FileChooserDialog : DialogFragment() {
}
}
private var initPath = FileUtils.getSdCardPath()
private var mode: Int = FILE
private lateinit var fileAdapter: FileAdapter
private lateinit var pathAdapter: PathAdapter
override fun onStart() {
super.onStart()
val dm = DisplayMetrics()
activity?.windowManager?.defaultDisplay?.getMetrics(dm)
dialog?.window?.setLayout((dm.widthPixels * 0.9).toInt(), (dm.heightPixels * 0.8).toInt())
}
override fun onCreateView(
inflater: LayoutInflater,
@ -30,4 +56,65 @@ class FileChooserDialog : DialogFragment() {
return inflater.inflate(R.layout.dialog_file_chooser, container, true)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tool_bar.title = "文件选择"
fileAdapter = FileAdapter(requireContext(), this)
pathAdapter = PathAdapter(requireContext(), this)
rv_file.addItemDecoration(DividerItemDecoration(activity, LinearLayout.VERTICAL))
rv_file.layoutManager = LinearLayoutManager(activity)
rv_file.adapter = fileAdapter
rv_path.layoutManager = LinearLayoutManager(activity, RecyclerView.HORIZONTAL, false)
rv_path.adapter = pathAdapter
refreshCurrentDirPath(initPath)
}
override fun onFileClick(position: Int) {
val fileItem = fileAdapter.getItem(position)
if (fileItem?.isDirectory == true) {
refreshCurrentDirPath(fileItem.path)
} else {
fileItem?.path?.let { path ->
if (mode != DIRECTORY) {
(parentFragment as? CallBack)?.onFilePicked(path)
(activity as? CallBack)?.onFilePicked(path)
dismiss()
}
}
}
}
override fun onPathClick(position: Int) {
refreshCurrentDirPath(pathAdapter.getPath(position))
}
private fun refreshCurrentDirPath(currentPath: String) {
if (currentPath == "/") {
pathAdapter.updatePath("/")
} else {
pathAdapter.updatePath(currentPath)
}
fileAdapter.loadData(currentPath)
var adapterCount = fileAdapter.itemCount
if (fileAdapter.isShowHomeDir) {
adapterCount--
}
if (fileAdapter.isShowUpDir) {
adapterCount--
}
if (adapterCount < 1) {
tv_empty.visible()
tv_empty.setText(R.string.empty)
} else {
tv_empty.gone()
}
}
interface CallBack {
fun onFilePicked(currentPath: String)
}
}

@ -0,0 +1,283 @@
package io.legado.app.ui.filechooser;
/**
* Generated by https://github.com/gzu-liyujiang/Image2ByteVar
*
* @author 李玉江[QQ:1023694760]
* @since 2017/01/04 06:03
*/
public class FilePickerIcon {
public static byte[] getFILE() {
return FILE;
}
public static byte[] getFOLDER() {
return FOLDER;
}
public static byte[] getHOME() {
return HOME;
}
public static byte[] getUPDIR() {
return UPDIR;
}
public static byte[] getARROW() {
return ARROW;
}
// fixed: 17-1-7 "static final" arrays should be "private"
private static final byte[] FILE = {
-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 42,
0, 0, 0, 40, 8, 6, 0, 0, 0, -120, 11, 104, 80, 0, 0, 0, 6, 98, 75, 71,
68, 0, -1, 0, -1, 0, -1, -96, -67, -89, -109, 0, 0, 0, 9, 112, 72, 89, 115, 0,
0, 14, -60, 0, 0, 14, -60, 1, -107, 43, 14, 27, 0, 0, 1, -73, 73, 68, 65, 84,
88, -123, -19, -106, 63, 75, 3, 49, 24, -121, 127, -74, 69, -37, 110, -30, -94, -109, -120, -126,
-125, -101, 56, 72, 63, -125, -97, -64, -63, 77, 28, 69, 20, 81, 68, 113, 83, 113, -12, 91,
-120, -96, -117, -120, -85, 31, 65, 29, 28, -100, -124, -94, -101, 46, -83, -105, 92, 114, 113, -24,
31, -38, -110, 59, -34, -9, 114, 87, 69, -14, 64, 41, -92, 73, -34, -89, -55, -17, 114, 1,
60, 30, -113, -25, 79, 50, 66, -19, 120, -7, 16, 26, 0, -112, 82, -61, 24, 3, 41, 35,
0, -128, 20, 10, 74, 69, -48, 58, -126, 82, -90, -37, -90, -75, -127, 82, 81, 119, 124, 16,
40, -100, 109, 79, -109, -21, 13, 82, -94, 118, -108, 82, 99, -91, 54, -58, 25, 2, 0, 120,
-85, 7, -72, -71, 127, -57, -18, -58, 12, -102, -51, 87, 115, 113, 56, -105, 74, -74, 64, -19,
104, -116, 73, 51, 63, 0, -96, 88, 104, -107, 57, -34, -102, -59, -6, -63, 75, -86, -119, -56,
-94, -99, -83, -26, -96, 123, -122, 20, -37, -107, 78, -9, -25, -79, -74, -13, -52, -106, 37, -117,
114, -23, 72, 42, 109, -96, -93, 8, 66, 0, -97, 95, -83, -49, -55, -34, 2, 86, 55, 31,
89, -78, -12, -116, 10, -59, 18, -20, 48, 90, -86, -96, -47, -48, -72, -66, -5, 64, -40, -98,
-94, 90, -27, -81, 15, 89, -76, -9, 9, -114, 99, 80, 18, 0, -90, 38, -127, -38, -46, 4,
-76, 78, -97, 113, -128, 33, 42, -124, 78, -4, -35, 38, -87, -62, -42, -9, -14, -30, 120, 127,
-69, 6, -82, 110, -21, -44, -46, 0, -72, 103, 77, 12, 73, -110, 125, 109, -55, -1, 53, 17,
86, 70, 109, 66, 113, -72, 72, -39, 32, -89, -38, 53, 99, -82, 100, -14, 48, -39, -74, 57,
107, -100, -49, -47, -84, -77, 24, 7, 121, 69, -125, -64, 126, -114, 82, -91, -66, 3, 106, 37,
59, -12, 21, 85, -46, -87, 80, -91, -20, 52, -100, 46, -38, -108, -87, 111, 104, -103, -64, 58,
71, -121, -15, -48, -60, -63, -54, -24, -80, -14, 104, 35, -73, -37, 83, -42, -112, 69, 5, -15,
-10, -108, 23, -12, 3, 63, -92, -65, 63, 43, 101, -5, -10, 7, 14, -111, 112, -66, -108, -28,
-111, 71, 27, -1, 47, -93, -65, 77, 38, -9, 81, 27, 46, 121, -76, -63, 18, 29, 86, 30,
109, -80, 68, -113, -50, -97, -14, -14, -16, 120, 60, -98, 1, 126, 0, -110, 81, -78, -5, 36,
19, -64, 3, 0, 0, 0, 0, 73, 69, 78, 68, -82, 66, 96, -126};
private static final byte[] FOLDER = {
-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 42,
0, 0, 0, 40, 8, 6, 0, 0, 0, -120, 11, 104, 80, 0, 0, 0, 6, 98, 75, 71,
68, 0, -1, 0, -1, 0, -1, -96, -67, -89, -109, 0, 0, 0, 9, 112, 72, 89, 115, 0,
0, 14, -60, 0, 0, 14, -60, 1, -107, 43, 14, 27, 0, 0, 2, -102, 73, 68, 65, 84,
88, -123, -19, -105, 61, -113, 19, 49, 16, -122, 95, 111, -110, 75, -124, -124, -60, -49, 65, 20,
-48, 34, 81, 80, 32, 81, 83, 80, -94, 52, 72, 8, 78, 66, -94, 64, 20, -108, 124, 20,
39, -47, 64, -51, -11, -7, 87, -96, 11, -38, -75, 61, 99, 15, -59, 110, 54, 118, -42, -5,
113, -71, 77, 117, -5, 54, -55, -50, -38, -42, -77, -81, 61, 99, 27, -104, 52, 105, -46, -92,
73, -73, 66, 106, 104, -61, -51, 57, -92, -21, -3, -29, 79, -61, -57, 58, 70, -13, 33, -115,
54, -25, -112, 71, -17, 127, 0, 0, -106, -38, -105, 65, 93, 64, -56, 0, 108, 33, 76, -72,
-28, -113, -14, -20, -77, 59, 25, 108, 54, 4, -14, -63, -6, 59, -112, 123, -84, -4, -84, -114,
-117, 39, 96, -74, -17, -2, -12, -59, 91, 92, -66, -103, 117, -70, 126, 19, 117, 58, -80, 57,
-121, 60, 92, 127, 5, 0, -84, -106, -53, -3, 11, 93, -108, -96, 0, 96, 52, -124, 9, 96,
6, 116, -127, -33, -65, -66, -44, -51, 56, -117, -121, -73, 62, 3, -7, 56, -74, 123, 126, -11,
-83, -24, 100, -23, -99, -6, -43, -67, -5, -119, -96, -119, -66, 80, 1, -128, 49, 0, -128, -25,
31, -98, -108, 65, 103, -93, 46, -62, -36, 24, -58, -27, -37, -6, -65, -39, -66, -108, -41, 63,
-13, 86, -40, 65, 107, 84, -7, 43, 64, -103, 70, 92, 108, 17, -45, 82, -94, -115, -47, 77,
-40, -22, 35, -108, 53, 32, 34, 56, 34, 112, -49, -14, 30, 4, 90, 66, -2, -119, 99, -34,
2, -69, -23, -33, -119, -10, -32, 18, -66, -37, -63, 114, 16, 99, -122, 2, -64, 87, 127, 97,
-56, -61, -53, 24, -96, -121, -14, -107, 35, 103, 11, -120, -91, 116, 27, -25, -45, -15, 96, 9,
80, -95, 97, -56, -61, 114, 127, 14, -10, 102, 125, 27, 36, -128, 86, -56, -70, 34, -52, 50,
-128, 109, -78, 77, 40, 118, -82, -73, 77, -65, -93, 98, 26, -128, -111, -62, 41, 62, -101, 67,
116, -111, 110, -41, 34, -53, 2, -26, 22, -9, 3, 29, 53, -11, -111, -109, -39, -94, -4, -83,
0, 85, -74, -120, -41, -25, 72, -22, 4, -27, -86, -58, -119, 45, -102, -119, 3, 52, -36, -124,
61, 40, 65, 81, -58, 55, -5, -117, 99, -80, 115, -89, 115, 52, 9, 93, 65, -90, -36, 76,
65, 94, 87, -67, -96, 74, -52, -2, 52, -46, 54, -91, -95, -109, -119, 108, 87, -13, -59, 126,
-9, 74, 40, -109, 27, 58, 106, 124, 6, -79, 40, -117, -7, 33, 100, 0, 23, -71, -72, -37,
-1, -85, 105, 31, -61, 77, 96, -24, -44, -109, 1, 40, -19, 70, 50, 113, -126, -75, -39, -22,
-26, 53, -85, -61, 113, 89, 127, 8, 23, 78, 119, 80, 55, -5, -36, -12, 117, 34, -11, 23,
-4, 78, 80, -14, 10, 112, 22, -30, 92, -5, -6, 28, 2, 25, -70, -103, 112, -46, -75, -19,
98, 67, 65, 57, 60, -110, -11, 13, 118, 4, -92, -9, 2, 79, 4, -94, 17, 118, 38, 97,
6, -104, 64, -108, -34, -103, -124, -35, -63, 115, -20, -68, -46, 58, 124, 2, 0, 56, 91, -18,
118, -123, -74, -48, 122, 88, -78, 117, -126, 90, 95, 102, -80, 49, 5, -88, 26, 80, -101, -46,
33, 83, -24, -42, 126, 53, 116, 42, 97, -104, -22, 2, -97, 111, 115, 108, -13, 17, 64, 1,
-64, 85, 103, 71, 50, 84, 1, -106, 110, 88, 106, 95, 10, 93, -5, -67, -81, 62, -44, 22,
26, 84, 1, -33, -67, -77, -24, 5, -19, -67, -116, 93, -84, 87, 2, 32, -70, 66, -104, -32,
-112, -53, 94, -63, -113, 112, 1, 125, 119, -15, -17, -92, -73, -40, 73, -109, 38, 77, -70, -19,
-6, 15, -2, -54, -98, -96, -19, -118, -95, -10, 0, 0, 0, 0, 73, 69, 78, 68, -82, 66,
96, -126};
private static final byte[] HOME = {
-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 42,
0, 0, 0, 40, 8, 4, 0, 0, 0, 34, 2, -96, -37, 0, 0, 0, 1, 115, 82, 71,
66, 0, -82, -50, 28, -23, 0, 0, 0, 2, 98, 75, 71, 68, 0, -1, -121, -113, -52, -65,
0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 46, 35, 0, 0, 46, 35, 1, 120, -91, 63,
118, 0, 0, 0, 7, 116, 73, 77, 69, 7, -37, 8, 4, 10, 36, 16, -22, -9, -18, -50,
0, 0, 2, -84, 73, 68, 65, 84, 72, -57, -19, -106, 91, 72, 20, 97, 20, -57, 127, -77,
-18, -106, -41, 54, -14, -106, -23, -102, 4, 21, 97, 5, 5, 61, 70, 74, -76, -122, -94, -11,
18, -108, -92, 121, 41, 16, -124, 96, -95, 48, -118, -108, -94, 18, -70, 96, 33, 68, -81, -127,
15, 25, 68, 15, 21, 89, 80, 42, 25, 42, -91, 97, -106, -103, -122, 107, 41, -19, -86, -37,
-22, 122, -39, 77, -73, -103, -81, 7, -105, 84, -36, -85, -26, -125, -44, -127, -31, 59, -52, -103,
-7, 113, -50, -103, -17, -4, -65, -127, -1, -74, 44, 76, -102, 113, -29, 22, -119, 50, -3, -15,
84, 75, -111, -87, -38, -81, -89, 84, 24, -120, 1, -96, -106, -102, -65, 83, -66, -118, 59, -119,
39, 54, 97, 69, -31, -109, 115, -14, 8, 15, 124, -107, -17, 27, 42, 113, 123, 125, 81, 46,
102, 28, 8, -84, -68, 116, 78, -26, 80, 77, 5, -79, 104, -48, -48, 70, -39, 124, -88, -38,
103, 37, -107, -70, -94, 28, -6, 105, 7, 34, 89, 67, -86, -90, -74, 106, 82, -51, 65, 125,
-110, 13, 27, -99, 43, 2, -17, -87, -60, -51, -124, -30, 99, -104, -88, -63, -12, 20, -93, -90,
96, 111, -80, -106, 20, 117, -3, -35, -97, -65, 54, -13, 29, 21, 8, -9, -3, -14, -122, -68,
17, 127, 50, 15, 51, -49, 49, 85, -109, 73, -79, 51, -81, 94, -103, 96, 21, 123, -126, 66,
86, 10, 64, 4, 12, -107, -72, -70, -50, -112, -57, 0, 47, -24, 123, 66, 46, 50, 80, -19,
40, 121, -59, 20, -31, -20, 102, 0, 121, 1, -48, -14, -72, 83, -7, 12, 81, -121, -79, -106,
67, 76, -71, -18, 94, -73, 85, 54, 34, 19, -122, 115, -102, 23, 16, -12, 114, 92, 73, 1,
22, 26, -24, 110, -26, 0, -114, 89, 17, -61, -32, -61, 22, 4, 2, -127, -30, -31, 125, -9,
-48, -117, -79, 103, -13, -79, -48, 68, -57, 123, -46, 25, -101, 19, -109, 57, -38, -41, -40, -127,
64, 16, -127, 70, -113, -34, 63, 104, 105, -52, -7, 2, -84, -76, -48, -42, 69, 26, -42, 121,
113, 59, 89, 93, -35, -67, 8, -126, -39, -87, 81, -35, 103, -69, 111, -24, -71, -24, 11, -123,
12, -45, -58, -37, -81, -20, -61, -20, -74, 18, 11, -23, -19, -125, 102, 20, -76, 36, 107, 121,
76, -68, 119, -24, -103, -88, 75, -123, -116, -16, -111, 38, 51, 122, -66, 121, -4, -116, 95, 68,
-42, 59, -5, 8, -126, 24, 54, -24, 120, 68, -124, 103, -24, -23, -88, -14, -29, -40, -24, -28,
-75, 85, -92, -47, -27, 117, 48, -102, -27, -20, 86, 121, 28, 5, 29, 107, 119, 112, 111, -10,
24, 5, -51, -72, 17, -122, -32, 107, -39, -110, -99, 30, -22, -58, -108, -3, -76, -6, 20, -93,
-49, -78, -59, -102, 17, -50, 40, -126, -47, -115, 66, 59, 94, -29, 78, 80, -86, -40, 74, -108,
20, -119, 34, 50, -88, -13, 83, 58, -81, -112, 73, 47, 70, -116, 52, -104, -34, 120, 86, 41,
-119, 4, 10, 1, 24, -26, -106, 71, 88, 10, 41, 0, 60, -93, -47, 31, -107, 18, -124, -123,
-106, -19, -30, 7, 31, 122, -68, 64, 83, 19, 75, -75, -12, 51, 108, -101, -127, -6, -40, -4,
97, -92, -110, -20, 67, 18, -109, -40, -58, 106, -113, -86, -66, 96, 83, -36, 15, -2, 34, -96,
-110, 88, 8, 84, -8, 60, -37, 60, 67, -43, -18, -127, 2, 1, 26, -74, 120, -124, 70, 11,
20, -108, 64, -113, 104, -119, 88, -99, -24, 112, -105, -71, 112, 117, -44, 22, 88, -90, 32, 8,
-27, 48, 33, 76, 119, 78, -52, 89, 101, 20, -100, -12, 49, 21, 24, 84, 97, 12, 59, 19,
46, -44, -36, 75, 65, 70, 70, 118, -83, -2, 67, 101, -21, 80, -123, -65, -69, -64, -79, -68,
127, -48, -106, 4, -6, -113, -37, 111, 38, -57, 11, 112, 71, 102, 113, -50, 0, 0, 0, 0,
73, 69, 78, 68, -82, 66, 96, -126};
private static final byte[] UPDIR = {
-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 42,
0, 0, 0, 40, 8, 6, 0, 0, 0, -120, 11, 104, 80, 0, 0, 0, 6, 98, 75, 71,
68, 0, -1, 0, -1, 0, -1, -96, -67, -89, -109, 0, 0, 0, 9, 112, 72, 89, 115, 0,
0, 14, -60, 0, 0, 14, -60, 1, -107, 43, 14, 27, 0, 0, 2, -111, 73, 68, 65, 84,
88, -123, -19, -106, -51, 74, -21, 64, 20, -57, -1, -87, -47, 7, -15, 41, 92, 118, -47, 82,
10, -59, 110, 20, -95, 32, 77, -101, 44, -92, -37, -66, 80, -23, 3, 8, -126, -76, -48, -115,
-120, 43, 117, -19, -109, -44, -113, -50, -57, -103, 115, 87, 103, 76, 106, -117, 77, 19, -17, -107,
75, -2, 48, -52, -112, 100, 38, -65, 57, -25, -52, 57, 3, 84, -86, 84, -87, -46, -1, -83,
36, 73, 56, 73, 18, -2, -87, -11, -61, 50, 22, -119, -29, -104, 47, 47, 47, 97, -116, -127,
-75, -106, 39, -109, 73, 80, -58, -70, 105, 21, 94, 48, -114, 99, 78, -110, 4, 68, 4, -91,
20, -116, 49, 120, 121, 121, -63, 120, 60, 46, 21, -74, 86, 100, -14, 112, 56, -28, -85, -85,
43, 0, -128, -75, 22, -50, 57, 48, 51, -114, -113, -113, -47, -21, -11, 74, 13, -125, -67, 65,
-121, -61, 33, -113, 70, 35, 4, 65, 0, 99, -116, 111, -42, 90, 16, 17, 58, -99, 14, 46,
46, 46, 74, -125, -35, 11, 84, 44, -23, -100, -61, -57, -57, 7, 86, -85, 21, -116, 49, 32,
34, 111, 89, 107, 45, 90, -83, 22, -50, -50, -50, 74, -127, -51, 125, -104, 6, -125, 1, -57,
113, -20, -83, -89, -108, -126, -75, 22, 0, -32, -100, -13, 45, 8, 2, 48, 51, -102, -51, 38,
-100, 115, 124, 125, 125, 93, 40, 102, 115, -127, 14, 6, 3, -114, -94, -56, -69, 87, 107, 13,
34, 2, 0, -1, 44, 8, -78, 60, 68, -124, 70, -93, 1, 34, -30, -101, -101, -101, -67, 97,
119, 6, -115, -94, -120, 123, -67, -98, -121, 19, 43, 10, -88, -12, -58, 24, 56, -25, -4, 55,
-78, -127, 122, -67, 14, 34, -30, -37, -37, -37, -67, 96, 119, -102, 20, 69, 17, 119, -69, 93,
15, 98, -83, -59, -63, -63, 1, 0, 32, 12, -61, 12, -88, -124, -61, -37, -37, 27, -34, -33,
-33, -3, -122, 68, 15, 15, 15, -104, -49, -25, -71, 97, -65, -99, -48, -17, -9, -71, -35, 110,
-5, 83, 45, 58, 58, 58, -14, -112, 34, -79, -30, -21, -21, 43, -106, -53, 37, -76, -42, 0,
0, -26, -49, -13, 20, -122, 33, -18, -17, -17, -79, 88, 44, 114, -63, 126, -21, 122, 102, -58,
108, 54, -13, 22, 19, 32, 0, 56, 61, 61, -3, -14, 76, 107, -115, -43, 106, 5, 34, -62,
-45, -45, 83, -26, -35, -6, -72, 84, -48, -23, 116, -70, 113, -25, -25, -25, -25, -20, -100, -53,
-4, 60, 29, -109, 114, -6, 103, -77, 89, 41, 21, 106, -17, 90, 47, -112, 2, 8, -64, 3,
-118, -85, -45, -33, 20, -43, -34, -96, -52, -100, 57, -35, -64, 103, -116, -82, 3, -1, 83, 80,
-87, 62, 2, -72, -98, 79, 5, -74, 44, -107, 2, 42, 112, 0, 50, -15, -7, 43, 64, -103,
121, 35, -24, -81, 118, -67, -28, -41, -76, -53, -91, -43, -21, 117, -106, 119, -23, 94, -58, -113,
-113, -113, 59, 101, -123, -62, -96, -101, -54, -87, 88, -14, -28, -28, 4, -121, -121, -121, 25, 48,
-71, 35, 40, -91, -16, -4, -4, -68, -13, -1, 10, -71, 62, 125, -75, 91, 7, -83, -43, 106,
30, 74, 42, -104, -28, 89, -83, 53, -76, -42, -71, 98, -72, -112, 69, -91, 68, 110, -69, -96,
-92, 123, -7, 70, 41, 5, -91, 84, -18, 10, 85, 10, 104, 58, 70, -45, 125, -6, -80, 73,
47, 22, -51, -85, 66, -82, 87, 74, 1, -128, -17, 55, -127, 109, 2, -1, -85, -96, 98, -47,
-76, 91, -73, 1, -82, -113, 1, -8, 107, -30, -113, -125, -34, -35, -35, 5, 68, -28, 19, -27,
-74, -12, 83, 102, -46, -81, 84, -87, 82, -91, 95, -88, 63, 49, -122, -88, 68, 127, -55, -90,
73, 0, 0, 0, 0, 73, 69, 78, 68, -82, 66, 96, -126};
private static final byte[] ARROW = {-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 48,
0, 0, 0, 117, 8, 3, 0, 0, 0, 63, 73, -110, 106, 0, 0, 2, -9, 80, 76, 84,
69, -46, -46, -46, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -65, -65, -65, 0,
0, 0, -52, -52, -52, -49, -49, -49, -47, -47, -47, -47, -47, -47, -48, -48, -48, -54, -54, -54,
-48, -48, -48, -48, -48, -48, -47, -47, -47, -47, -47, -47, -49, -49, -49, -86, -86, -86, -48, -48,
-48, -48, -48, -48, -47, -47, -47, -52, -52, -52, -49, -49, -49, -48, -48, -48, -48, -48, -48, -1,
-1, -1, -49, -49, -49, -48, -48, -48, -48, -48, -48, -51, -51, -51, -50, -50, -50, -48, -48, -48,
-48, -48, -48, -50, -50, -50, -48, -48, -48, -48, -48, -48, -48, -48, -48, -52, -52, -52, -48, -48,
-48, -48, -48, -48, -48, -48, -48, -46, -46, -46, -47, -47, -47, -52, -52, -52, -48, -48, -48, -48,
-48, -48, -1, -1, -1, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48,
-48, -48, -48, -41, -41, -41, -48, -48, -48, -49, -49, -49, -43, -43, -43, -47, -47, -47, -49, -49,
-49, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -49, -49, -49, -47,
-47, -47, -49, -49, -49, -47, -47, -47, -48, -48, -48, -48, -48, -48, -48, -48, -48, -49, -49, -49,
-47, -47, -47, -44, -44, -44, -48, -48, -48, -48, -48, -48, -1, -1, -1, -46, -46, -46, -48, -48,
-48, -49, -49, -49, -47, -47, -47, -49, -49, -49, -35, -35, -35, -49, -49, -49, -49, -49, -49, -49,
-49, -49, -47, -47, -47, -40, -40, -40, -49, -49, -49, -48, -48, -48, -48, -48, -48, -47, -47, -47,
-47, -47, -47, -47, -47, -47, -48, -48, -48, -60, -60, -60, -53, -53, -53, -48, -48, -48, -46, -46,
-46, -65, -65, -65, -48, -48, -48, -54, -54, -54, -45, -45, -45, -47, -47, -47, -49, -49, -49, -48,
-48, -48, -49, -49, -49, -51, -51, -51, -48, -48, -48, -48, -48, -48, -46, -46, -46, -47, -47, -47,
-47, -47, -47, -37, -37, -37, -47, -47, -47, -49, -49, -49, -50, -50, -50, -48, -48, -48, -128, -128,
-128, -48, -48, -48, -48, -48, -48, -50, -50, -50, -48, -48, -48, -48, -48, -48, -43, -43, -43, -47,
-47, -47, -48, -48, -48, -48, -48, -48, -48, -48, -48, -43, -43, -43, -47, -47, -47, -48, -48, -48,
-50, -50, -50, -49, -49, -49, -48, -48, -48, -48, -48, -48, -33, -33, -33, -48, -48, -48, -52, -52,
-52, -51, -51, -51, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -52,
-52, -52, -48, -48, -48, -46, -46, -46, -48, -48, -48, -37, -37, -37, -29, -29, -29, -48, -48, -48,
-48, -48, -48, -47, -47, -47, -48, -48, -48, -49, -49, -49, -46, -46, -46, -48, -48, -48, -49, -49,
-49, -47, -47, -47, -58, -58, -58, -49, -49, -49, -47, -47, -47, -48, -48, -48, -50, -50, -50, -47,
-47, -47, -50, -50, -50, -48, -48, -48, -48, -48, -48, -50, -50, -50, -48, -48, -48, -48, -48, -48,
-48, -48, -48, -55, -55, -55, -45, -45, -45, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48,
-48, -46, -46, -46, -47, -47, -47, -48, -48, -48, -47, -47, -47, -51, -51, -51, -47, -47, -47, -51,
-51, -51, -48, -48, -48, -47, -47, -47, -48, -48, -48, -65, -65, -65, -48, -48, -48, -50, -50, -50,
-48, -48, -48, -47, -47, -47, -49, -49, -49, -47, -47, -47, -47, -47, -47, -48, -48, -48, -48, -48,
-48, -50, -50, -50, -47, -47, -47, -49, -49, -49, -49, -49, -49, -47, -47, -47, -48, -48, -48, -47,
-47, -47, -49, -49, -49, -39, -39, -39, -50, -50, -50, -47, -47, -47, -48, -48, -48, -42, -42, -42,
-47, -47, -47, -46, -46, -46, -47, -47, -47, -47, -47, -47, -48, -48, -48, -49, -49, -49, -49, -49,
-49, -47, -47, -47, -48, -48, -48, -47, -47, -47, -48, -48, -48, -50, -50, -50, -47, -47, -47, -48,
-48, -48, -47, -47, -47, -49, -49, -49, -48, -48, -48, -49, -49, -49, -50, -50, -50, -47, -47, -47,
-56, -56, -56, -48, -48, -48, -48, -48, -48, -40, -40, -40, -45, -45, -45, -47, -47, -47, -47, -47,
-47, -49, -49, -49, -48, -48, -48, -43, -43, -43, -52, -52, -52, -49, -49, -49, -47, -47, -47, -47,
-47, -47, -50, -50, -50, -49, -49, -49, -47, -47, -47, -49, -49, -49, -48, -48, -48, -49, -49, -49,
-115, 52, -27, 40, 0, 0, 0, -3, 116, 82, 78, 83, 34, -37, -1, -7, 120, 4, 0, 5,
118, -9, -41, 38, 24, -46, -2, -100, 78, 69, 3, -98, -54, 28, 40, -37, -8, 115, 3, 122,
-6, -43, 36, 31, -49, -109, 78, 70, -93, -50, 20, 43, -38, -10, 113, 123, 30, -45, -115, 2,
86, -5, -9, 65, -94, -55, 19, -29, 111, 6, 127, 32, -39, -3, -114, 92, -4, 58, -86, -63,
22, -32, 109, -126, -5, -51, 36, -44, -121, 1, 90, -11, 59, -79, -59, 15, 48, -11, 106, -123,
26, 37, -40, 98, -3, -14, -80, -61, 13, 44, -25, 103, 8, -120, 29, 35, -35, -4, -127, 101,
46, -76, -71, 17, -24, 99, 7, -118, -58, 42, 124, 2, 103, -13, 47, -67, -65, 12, 55, -27,
-116, -57, 24, 44, -35, 119, 112, -21, 49, 8, -70, 10, 51, -19, -14, 93, -111, -62, 25, -30,
119, -78, 14, 9, -108, 113, 117, -18, -53, 63, -23, -15, 89, 9, -106, -2, -66, 21, 50, -125,
-33, -28, 57, 97, -97, -73, 19, 69, -12, -17, 81, -87, 57, 94, -99, -73, 72, -18, 82, -88,
-20, 27, 12, -100, 67, -16, 83, 16, -91, -122, 54, -26, 104, -101, 64, 85, 11, -82, 100, -101,
20, 84, -47, 125, 31, -102, 62, -57, -88, -125, 107, -102, -69, -15, -53, -77, 52, -31, -105, 66,
-21, 87, 121, 110, -67, 23, -60, -84, 13, 46, 111, 88, -75, 119, 42, 50, -32, 106, -107, 63,
91, -78, 117, 114, -108, 41, -51, -28, -65, 0, 0, 3, -107, 73, 68, 65, 84, 88, -61, -107,
-40, 105, 84, -108, 85, 24, 7, 112, -4, -113, -13, -112, -37, -101, -111, -96, 78, 67, -94, 38,
-18, -66, 14, 46, 52, -38, -28, -96, 34, 90, -67, -31, -106, 75, -114, 70, 74, 42, 90, 86,
-109, 4, 38, -88, -111, -90, 50, -126, -72, -46, -54, -102, 38, -102, -53, 104, -101, 123, -88, -71,
78, -18, -91, -90, 88, 82, 46, 89, 90, -71, -107, -27, 7, 95, 62, 120, -114, -100, -93, -121,
-5, 127, 63, -65, -65, 15, -9, -36, 123, -97, -5, 127, -98, -96, -96, 106, -80, 84, -73, -118,
-14, 23, 20, -4, 64, 13, -44, -84, 69, 0, -87, 93, 7, -38, -125, 12, -112, -70, 22, -32,
33, 6, -124, 60, 12, -44, 11, 37, -128, -124, -43, 71, -125, -122, 33, 4, -80, 61, 98, 71,
-8, -93, 4, -112, 70, 17, -48, 26, 51, 64, -102, 0, 53, -102, 50, 64, 30, -45, -48, 44,
-110, 1, -51, 91, -96, 101, 43, 43, 1, -84, -83, -19, -88, -39, -122, 0, 18, -38, 22, 104,
-89, 19, 64, -38, 59, 16, -43, -127, 1, -42, -114, 64, -89, -50, 4, -112, -80, 104, 68, 61,
-82, 19, -64, -39, -59, -126, -120, -82, 4, -112, -48, 39, -32, 122, -110, 1, -46, -51, 1, 119,
12, 3, -84, -35, -127, 30, 61, 9, 32, -51, 99, 17, -43, 75, 39, 64, 112, 92, 111, -12,
105, 68, 0, 121, -22, 105, 104, -49, 48, -64, 120, 22, -120, -17, 75, 0, -111, 126, 64, -1,
1, 12, 24, 24, -117, -25, 6, -39, 8, -32, 28, -20, 64, -77, 33, 4, -112, -95, -49, 3,
113, 6, 1, 100, -104, 7, -61, 99, 24, -32, 28, 1, -68, -112, 64, 0, 121, 113, 36, -30,
71, -23, 4, 72, 124, -55, -114, 78, -93, 9, 32, 99, -58, 34, 105, 28, 3, 100, -68, 27,
13, 94, 102, -128, -13, 21, 96, -62, -85, 4, -112, -127, -81, 33, -2, 117, -125, 0, -34, 55,
-110, 48, 49, -108, 0, -110, -36, 2, -82, 55, 25, -112, -110, 10, 76, 122, -117, 0, 34, -109,
-127, -76, 116, 6, 76, -103, -118, 73, -61, 116, 2, 120, -89, -71, -15, 118, 6, 1, -28, -99,
-23, -64, 12, -125, 0, -58, -69, -64, -52, 89, 4, -112, -39, -26, -70, 51, 125, 4, -112, 57,
89, -56, -98, -101, 66, 0, 95, -114, 3, -13, -26, 19, 64, -110, 23, -64, -79, -112, 1, 82,
-35, -123, -8, -95, 12, -16, -103, -21, 94, -76, -104, 0, 50, 101, 38, -122, 119, 51, 8, -112,
-5, -98, 11, 105, -75, 9, 80, 81, 58, -19, -17, 51, 64, -1, 0, -8, 112, 12, 1, -60,
-6, -111, -71, -18, -39, 4, -112, -113, -13, -112, 95, -112, 66, -128, -62, 34, -83, -8, -109, 37,
4, -112, -91, -79, -16, -92, 50, -64, -8, 20, 88, -58, 0, -55, 1, 74, 24, -80, 124, 36,
44, 43, 8, -112, -16, -103, -85, 120, -27, 42, 2, -84, 94, 3, -1, 90, 67, 29, -92, -41,
1, -42, 89, -43, 55, -50, 54, 24, -120, 14, 34, -114, -58, -25, 37, 112, 127, 65, 28, 62,
-33, -105, -59, -38, 87, -99, 9, -16, -11, 122, -8, 55, 16, 23, -56, 23, 14, 108, -12, -86,
95, 81, 91, 28, -80, 105, 51, 81, 4, -52, 10, 110, 73, 37, -54, -52, -106, -83, 46, -84,
-116, 36, -64, 55, 107, 80, -70, -115, -88, -83, -37, -21, 1, 59, -104, 98, -4, -83, -122, -84,
-47, 68, -71, -33, -71, 11, -10, -35, -60, 11, -108, -66, -57, -115, -52, 72, 2, -20, -51, -122,
127, -97, -95, 14, 2, 17, -64, 119, 78, -11, 103, 87, -17, 98, -34, -29, 90, -60, -61, -66,
60, 26, -98, -3, 68, 116, -16, 29, -48, -76, -52, -125, 4, 88, -67, 9, -2, 67, 68, -4,
-15, 30, 6, -70, 39, -86, 7, 44, -37, 17, 32, -21, 40, 17, -31, 118, -106, -64, 50, -120,
8, -119, -127, -17, 93, -104, -80, -124, 0, 63, 100, 35, -65, 61, -111, -116, -73, 31, 3, -114,
7, 8, 112, -62, 60, -43, 109, -120, -80, -66, 116, 42, -20, 63, 18, -3, 67, -32, -92, 27,
-89, 14, 18, -96, 111, -39, -67, 79, -11, -3, -128, -17, 52, -16, -109, 83, -67, 105, -46, -51,
61, -34, 21, 70, -76, 101, 63, -97, -127, 103, 60, -47, -8, 21, -106, 87, 122, -110, -85, 6,
-65, -4, -118, -46, -77, -122, 58, -16, -98, -82, 28, 43, -86, 2, 33, -25, -52, -32, 114, -108,
104, -64, -25, -100, -127, -3, 60, -47, -30, -5, -54, 93, -72, -16, 27, 1, 98, -4, -56, 46,
32, -26, 26, 9, 23, -127, -33, -1, 32, -64, 17, 13, 101, 42, 73, -27, -50, 63, -105, 74,
-48, -5, 50, 49, -3, 9, 20, 37, -95, -86, 61, -82, 4, 98, 74, -111, -1, -89, -95, 14,
114, -51, 123, -4, 87, -94, -6, -56, -53, -8, 27, -56, -69, 66, 12, -43, -82, 94, -125, -25,
58, 49, -74, 43, -68, -95, 21, 87, 57, -109, -71, 27, -4, -109, -121, -78, 127, 83, -44, -127,
-43, 124, -113, 111, 22, -118, 50, -48, -1, 3, -4, -86, -109, -54, 10, 80, 17, -8, -1, 23,
117, -112, 123, -53, 108, 41, 50, -44, -63, 109, -80, -19, 79, -78, 17, 39, 44, -102, 0, 0,
0, 0, 73, 69, 78, 68, -82, 66, 96, -126};
}

@ -0,0 +1,174 @@
package io.legado.app.ui.filechooser.adapter
import android.content.Context
import android.graphics.drawable.Drawable
import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.ui.filechooser.FilePickerIcon
import io.legado.app.ui.filechooser.entity.FileItem
import io.legado.app.ui.filechooser.utils.ConvertUtils
import io.legado.app.ui.filechooser.utils.FileUtils
import kotlinx.android.synthetic.main.item_path_filepicker.view.*
import org.jetbrains.anko.sdk27.listeners.onClick
import java.io.File
import java.util.*
class FileAdapter(context: Context, val callBack: CallBack) :
SimpleRecyclerAdapter<FileItem>(context, R.layout.item_file_filepicker) {
private var rootPath: String? = null
var currentPath: String? = null
private set
private var allowExtensions: Array<String?>? = null//允许的扩展名
/**
* 是否仅仅读取目录
*/
var isOnlyListDir = false//是否仅仅读取目录
/**
* 是否显示返回主目录
*/
var isShowHomeDir = false//是否显示返回主目录
/**
* 是否显示返回上一级
*/
var isShowUpDir = true//是否显示返回上一级
/**
* 是否显示隐藏的目录.开头
*/
var isShowHideDir = true//是否显示隐藏的目录(以“.”开头)
private var itemHeight = 40// dp
private var homeIcon: Drawable? = null
private var upIcon: Drawable? = null
private var folderIcon: Drawable? = null
private var fileIcon: Drawable? = null
fun setFileIcon(fileIcon: Drawable) {
this.fileIcon = fileIcon
}
fun setFolderIcon(folderIcon: Drawable) {
this.folderIcon = folderIcon
}
fun setHomeIcon(homeIcon: Drawable) {
this.homeIcon = homeIcon
}
fun setUpIcon(upIcon: Drawable) {
this.upIcon = upIcon
}
/**
* 允许的扩展名
*/
fun setAllowExtensions(allowExtensions: Array<String?>) {
this.allowExtensions = allowExtensions
}
fun setItemHeight(itemHeight: Int) {
this.itemHeight = itemHeight
}
fun loadData(path: String?) {
if (path == null) {
return
}
if (homeIcon == null) {
homeIcon = ConvertUtils.toDrawable(FilePickerIcon.getHOME())
}
if (upIcon == null) {
upIcon = ConvertUtils.toDrawable(FilePickerIcon.getUPDIR())
}
if (folderIcon == null) {
folderIcon = ConvertUtils.toDrawable(FilePickerIcon.getFOLDER())
}
if (fileIcon == null) {
fileIcon = ConvertUtils.toDrawable(FilePickerIcon.getFILE())
}
val datas = ArrayList<FileItem>()
if (rootPath == null) {
rootPath = path
}
currentPath = path
if (isShowHomeDir) {
//添加“返回主目录”
val fileRoot = FileItem()
fileRoot.isDirectory = true
fileRoot.icon = homeIcon
fileRoot.name = DIR_ROOT
fileRoot.size = 0
fileRoot.path = rootPath ?: path
datas.add(fileRoot)
}
if (isShowUpDir && path != "/") {
//添加“返回上一级目录”
val fileParent = FileItem()
fileParent.isDirectory = true
fileParent.icon = upIcon
fileParent.name = DIR_PARENT
fileParent.size = 0
fileParent.path = File(path).parent
datas.add(fileParent)
}
currentPath?.let { currentPath ->
val files: Array<File?>? = allowExtensions?.let { allowExtensions ->
if (isOnlyListDir) {
FileUtils.listDirs(currentPath, allowExtensions)
} else {
FileUtils.listDirsAndFiles(currentPath, allowExtensions)
}
} ?: let {
if (isOnlyListDir) {
FileUtils.listDirs(currentPath)
} else {
FileUtils.listDirsAndFiles(currentPath)
}
}
if (files != null) {
for (file in files) {
if (file == null || (!isShowHideDir && file.name.startsWith("."))) {
continue
}
val fileItem = FileItem()
val isDirectory = file.isDirectory
fileItem.isDirectory = isDirectory
if (isDirectory) {
fileItem.icon = folderIcon
fileItem.size = 0
} else {
fileItem.icon = fileIcon
fileItem.size = file.length()
}
fileItem.name = file.name
fileItem.path = file.absolutePath
datas.add(fileItem)
}
}
setItems(datas)
}
}
override fun convert(holder: ItemViewHolder, item: FileItem, payloads: MutableList<Any>) {
holder.itemView.apply {
image_view.setImageDrawable(item.icon)
text_view.text = item.name
onClick {
callBack.onFileClick(holder.layoutPosition)
}
}
}
interface CallBack {
fun onFileClick(position: Int)
}
companion object {
val DIR_ROOT = "."
val DIR_PARENT = ".."
}
}

@ -0,0 +1,63 @@
package io.legado.app.ui.filechooser.adapter
import android.content.Context
import android.os.Environment
import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.ui.filechooser.FilePickerIcon
import io.legado.app.ui.filechooser.utils.ConvertUtils
import kotlinx.android.synthetic.main.item_path_filepicker.view.*
import org.jetbrains.anko.sdk27.listeners.onClick
import java.util.*
class PathAdapter(context: Context, val callBack: CallBack) :
SimpleRecyclerAdapter<String>(context, R.layout.item_path_filepicker) {
private val paths = LinkedList<String>()
private val sdCardDirectory = Environment.getExternalStorageDirectory().absolutePath
private val arrowIcon = ConvertUtils.toDrawable(FilePickerIcon.getARROW())
fun getPath(position: Int): String {
val tmp = StringBuilder("$sdCardDirectory/")
//忽略根目录
if (position == 0) {
return tmp.toString()
}
for (i in 1..position) {
tmp.append(paths[i]).append("/")
}
return tmp.toString()
}
fun updatePath(path: String) {
var path1 = path
path1 = path1.replace(sdCardDirectory, "")
paths.clear()
if (path1 != "/" && path1 != "") {
val tmps = path1.substring(path1.indexOf("/") + 1).split("/".toRegex())
.dropLastWhile { it.isEmpty() }.toTypedArray()
Collections.addAll(paths, *tmps)
}
paths.addFirst(ROOT_HINT)
setItems(paths)
}
override fun convert(holder: ItemViewHolder, item: String, payloads: MutableList<Any>) {
holder.itemView.apply {
text_view.text = item
image_view.setImageDrawable(arrowIcon)
onClick {
callBack.onPathClick(holder.layoutPosition)
}
}
}
interface CallBack {
fun onPathClick(position: Int)
}
companion object {
private val ROOT_HINT = "SD"
}
}

@ -0,0 +1,17 @@
package io.legado.app.ui.filechooser.entity
import android.graphics.drawable.Drawable
/**
* 文件项信息
*
* @author 李玉江[QQ:1032694760]
* @since 2014-05-23 18:02
*/
class FileItem : JavaBean() {
var icon: Drawable? = null
var name: String? = null
var path = "/"
var size: Long = 0
var isDirectory = false
}

@ -0,0 +1,54 @@
package io.legado.app.ui.filechooser.entity
import java.io.Serializable
import java.lang.reflect.Field
import java.lang.reflect.Modifier
import java.util.*
/**
* JavaBean类
*
* @author 李玉江[QQ:1032694760]
* @since 2014-04-23 16:14
*/
open class JavaBean : Serializable {
/**
* 反射出所有字段值
*/
override fun toString(): String {
val list = ArrayList<Field>()
var clazz: Class<*>? = javaClass
list.addAll(listOf(*clazz!!.declaredFields))//得到自身的所有字段
val sb = StringBuilder()
while (clazz != Any::class.java) {
clazz = clazz!!.superclass//得到继承自父类的字段
val fields = clazz!!.declaredFields
for (field in fields) {
val modifier = field.modifiers
if (Modifier.isPublic(modifier) || Modifier.isProtected(modifier)) {
list.add(field)
}
}
}
val fields = list.toTypedArray()
for (field in fields) {
val fieldName = field.name
try {
val obj = field.get(this)
sb.append(fieldName)
sb.append("=")
sb.append(obj)
sb.append("\n")
} catch (ignored: IllegalAccessException) {
}
}
return sb.toString()
}
companion object {
private const val serialVersionUID = -6111323241670458039L
}
}

@ -0,0 +1,617 @@
package io.legado.app.ui.filechooser.utils;
import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.view.View;
import android.widget.ListView;
import android.widget.ScrollView;
import androidx.annotation.ColorInt;
import androidx.annotation.FloatRange;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.List;
/**
* 数据类型转换单位转换
*
* @author 李玉江[QQ:1023694760]
* @since 2014-4-18
*/
public class ConvertUtils {
public static final long GB = 1073741824;
public static final long MB = 1048576;
public static final long KB = 1024;
public static int toInt(Object obj) {
try {
return Integer.parseInt(obj.toString());
} catch (NumberFormatException e) {
return -1;
}
}
public static int toInt(byte[] bytes) {
int result = 0;
byte abyte;
for (int i = 0; i < bytes.length; i++) {
abyte = bytes[i];
result += (abyte & 0xFF) << (8 * i);
}
return result;
}
public static int toShort(byte first, byte second) {
return (first << 8) + (second & 0xFF);
}
public static long toLong(Object obj) {
try {
return Long.parseLong(obj.toString());
} catch (NumberFormatException e) {
return -1L;
}
}
public static float toFloat(Object obj) {
try {
return Float.parseFloat(obj.toString());
} catch (NumberFormatException e) {
return -1f;
}
}
/**
* int占4字节
*
* @param i the
* @return byte [ ]
*/
public static byte[] toByteArray(int i) {
// byte[] bytes = new byte[4];
// bytes[0] = (byte) (0xff & i);
// bytes[1] = (byte) ((0xff00 & i) >> 8);
// bytes[2] = (byte) ((0xff0000 & i) >> 16);
// bytes[3] = (byte) ((0xff000000 & i) >> 24);
// return bytes;
return ByteBuffer.allocate(4).putInt(i).array();
}
public static byte[] toByteArray(String hexData, boolean isHex) {
if (hexData == null || hexData.equals("")) {
return null;
}
if (!isHex) {
return hexData.getBytes();
}
hexData = hexData.replaceAll("\\s+", "");
String hexDigits = "0123456789ABCDEF";
ByteArrayOutputStream baos = new ByteArrayOutputStream(
hexData.length() / 2);
// 将每2位16进制整数组装成一个字节
for (int i = 0; i < hexData.length(); i += 2) {
baos.write((hexDigits.indexOf(hexData.charAt(i)) << 4 | hexDigits
.indexOf(hexData.charAt(i + 1))));
}
byte[] bytes = baos.toByteArray();
try {
baos.close();
} catch (IOException e) {
}
return bytes;
}
public static String toHexString(String str) {
if (TextUtils.isEmpty(str))
return "";
StringBuilder builder = new StringBuilder();
byte[] bytes = str.getBytes();
for (byte aByte : bytes) {
builder.append(Integer.toHexString(0xFF & aByte));
builder.append(" ");
}
return builder.toString();
}
/**
* To hex string string.
*
* @param bytes the bytes
* @return the string
*/
public static String toHexString(byte... bytes) {
char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
// 参见:http://www.oschina.net/code/snippet_116768_9019
char[] buffer = new char[bytes.length * 2];
for (int i = 0, j = 0; i < bytes.length; ++i) {
int u = bytes[i] < 0 ? bytes[i] + 256 : bytes[i];//转无符号整型
buffer[j++] = DIGITS[u >>> 4];
buffer[j++] = DIGITS[u & 0xf];
}
return new String(buffer);
}
/**
* To hex string string.
*
* @param num the num
* @return the string
*/
public static String toHexString(int num) {
String hexString = Integer.toHexString(num);
return hexString;
}
/**
* To binary string string.
*
* @param bytes the bytes
* @return the string
*/
public static String toBinaryString(byte... bytes) {
char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
// 参见:http://www.oschina.net/code/snippet_116768_9019
char[] buffer = new char[bytes.length * 8];
for (int i = 0, j = 0; i < bytes.length; ++i) {
int u = bytes[i] < 0 ? bytes[i] + 256 : bytes[i];//转无符号整型
buffer[j++] = DIGITS[(u >>> 7) & 0x1];
buffer[j++] = DIGITS[(u >>> 6) & 0x1];
buffer[j++] = DIGITS[(u >>> 5) & 0x1];
buffer[j++] = DIGITS[(u >>> 4) & 0x1];
buffer[j++] = DIGITS[(u >>> 3) & 0x1];
buffer[j++] = DIGITS[(u >>> 2) & 0x1];
buffer[j++] = DIGITS[(u >>> 1) & 0x1];
buffer[j++] = DIGITS[u & 0x1];
}
return new String(buffer);
}
/**
* To binary string string.
*
* @param num the num
* @return the string
*/
public static String toBinaryString(int num) {
String binaryString = Integer.toBinaryString(num);
return binaryString;
}
public static String toSlashString(String str) {
String result = "";
char[] chars = str.toCharArray();
for (char chr : chars) {
if (chr == '"' || chr == '\'' || chr == '\\') {
result += "\\";//符合“"”“'”“\”这三个符号的前面加一个“\”
}
result += chr;
}
return result;
}
public static <T> T[] toArray(List<T> list) {
//noinspection unchecked
return (T[]) list.toArray();
}
public static <T> List<T> toList(T[] array) {
return Arrays.asList(array);
}
public static String toString(Object[] objects) {
return Arrays.deepToString(objects);
}
public static String toString(Object[] objects, String tag) {
StringBuilder sb = new StringBuilder();
for (Object object : objects) {
sb.append(object);
sb.append(tag);
}
return sb.toString();
}
public static byte[] toByteArray(InputStream is) {
if (is == null) {
return null;
}
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] buff = new byte[100];
while (true) {
int len = is.read(buff, 0, 100);
if (len == -1) {
break;
} else {
os.write(buff, 0, len);
}
}
byte[] bytes = os.toByteArray();
os.close();
is.close();
return bytes;
} catch (IOException e) {
}
return null;
}
public static byte[] toByteArray(Bitmap bitmap) {
if (bitmap == null) {
return null;
}
ByteArrayOutputStream os = new ByteArrayOutputStream();
// 将Bitmap压缩成PNG编码,质量为100%存储,除了PNG还有很多常见格式,如jpeg等。
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
byte[] bytes = os.toByteArray();
try {
os.close();
} catch (IOException e) {
}
return bytes;
}
public static Bitmap toBitmap(byte[] bytes, int width, int height) {
Bitmap bitmap = null;
if (bytes.length != 0) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
// 不进行图片抖动处理
options.inDither = false;
// 设置让解码器以最佳方式解码
options.inPreferredConfig = null;
if (width > 0 && height > 0) {
options.outWidth = width;
options.outHeight = height;
}
bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
bitmap.setDensity(96);// 96 dpi
} catch (Exception e) {
}
}
return bitmap;
}
public static Bitmap toBitmap(byte[] bytes) {
return toBitmap(bytes, -1, -1);
}
/**
* 将Drawable转换为Bitmap
* 参考http://kylines.iteye.com/blog/1660184
*/
public static Bitmap toBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
} else if (drawable instanceof ColorDrawable) {
//color
Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(((ColorDrawable) drawable).getColor());
return bitmap;
} else if (drawable instanceof NinePatchDrawable) {
//.9.png
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
drawable.draw(canvas);
return bitmap;
}
return null;
}
/**
* 从第三方文件选择器获取路径
* 参见http://blog.csdn.net/zbjdsbj/article/details/42387551
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
public static String toPath(Context context, Uri uri) {
if (uri == null) {
return "";
}
String path = uri.getPath();
String scheme = uri.getScheme();
String authority = uri.getAuthority();
//是否是4.4及以上版本
boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
String docId = DocumentsContract.getDocumentId(uri);
String[] split = docId.split(":");
String type = split[0];
Uri contentUri = null;
switch (authority) {
// ExternalStorageProvider
case "com.android.externalstorage.documents":
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
break;
// DownloadsProvider
case "com.android.providers.downloads.documents":
contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
return _queryPathFromMediaStore(context, contentUri, null, null);
// MediaProvider
case "com.android.providers.media.documents":
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
String selection = "_id=?";
String[] selectionArgs = new String[]{split[1]};
return _queryPathFromMediaStore(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else {
if ("content".equalsIgnoreCase(scheme)) {
// Return the remote address
if (authority.equals("com.google.android.apps.photos.content")) {
return uri.getLastPathSegment();
}
return _queryPathFromMediaStore(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(scheme)) {
return uri.getPath();
}
}
return path;
}
private static String _queryPathFromMediaStore(Context context, Uri uri, String selection, String[] selectionArgs) {
String filePath = null;
try {
String[] projection = {MediaStore.Images.Media.DATA};
Cursor cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null) {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
filePath = cursor.getString(column_index);
cursor.close();
}
} catch (IllegalArgumentException e) {
}
return filePath;
}
/**
* 把view转化为bitmap截图
* 参见http://www.cnblogs.com/lee0oo0/p/3355468.html
*/
public static Bitmap toBitmap(View view) {
int width = view.getWidth();
int height = view.getHeight();
if (view instanceof ListView) {
height = 0;
// 获取listView实际高度
ListView listView = (ListView) view;
for (int i = 0; i < listView.getChildCount(); i++) {
height += listView.getChildAt(i).getHeight();
}
} else if (view instanceof ScrollView) {
height = 0;
// 获取scrollView实际高度
ScrollView scrollView = (ScrollView) view;
for (int i = 0; i < scrollView.getChildCount(); i++) {
height += scrollView.getChildAt(i).getHeight();
}
}
view.setDrawingCacheEnabled(true);
view.clearFocus();
view.setPressed(false);
boolean willNotCache = view.willNotCacheDrawing();
view.setWillNotCacheDrawing(false);
// Reset the drawing cache background color to fully transparent for the duration of this operation
int color = view.getDrawingCacheBackgroundColor();
view.setDrawingCacheBackgroundColor(Color.WHITE);//截图去黑色背景(透明像素)
if (color != Color.WHITE) {
view.destroyDrawingCache();
}
view.buildDrawingCache();
Bitmap cacheBitmap = view.getDrawingCache();
if (cacheBitmap == null) {
return null;
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(cacheBitmap, 0, 0, null);
canvas.save();
canvas.restore();
if (!bitmap.isRecycled()) {
bitmap.recycle();
}
// Restore the view
view.destroyDrawingCache();
view.setWillNotCacheDrawing(willNotCache);
view.setDrawingCacheBackgroundColor(color);
return bitmap;
}
public static Drawable toDrawable(Bitmap bitmap) {
return bitmap == null ? null : new BitmapDrawable(Resources.getSystem(), bitmap);
}
public static byte[] toByteArray(Drawable drawable) {
return toByteArray(toBitmap(drawable));
}
public static Drawable toDrawable(byte[] bytes) {
return toDrawable(toBitmap(bytes));
}
/**
* dp转换为px
*/
public static int toPx(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
int pxValue = (int) (dpValue * scale + 0.5f);
return pxValue;
}
/**
* px转换为dp
*/
public static int toDp(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
int dpValue = (int) (pxValue / scale + 0.5f);
return dpValue;
}
/**
* px转换为sp
*/
public static int toSp(Context context, float pxValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
int spValue = (int) (pxValue / fontScale + 0.5f);
return spValue;
}
public static String toGbk(String str) {
try {
return new String(str.getBytes("utf-8"), "gbk");
} catch (UnsupportedEncodingException e) {
return str;
}
}
public static String toFileSizeString(long fileSize) {
DecimalFormat df = new DecimalFormat("0.00");
String fileSizeString;
if (fileSize < KB) {
fileSizeString = fileSize + "B";
} else if (fileSize < MB) {
fileSizeString = df.format((double) fileSize / KB) + "K";
} else if (fileSize < GB) {
fileSizeString = df.format((double) fileSize / MB) + "M";
} else {
fileSizeString = df.format((double) fileSize / GB) + "G";
}
return fileSizeString;
}
public static String toString(InputStream is, String charset) {
StringBuilder sb = new StringBuilder();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(is, charset));
while (true) {
String line = reader.readLine();
if (line == null) {
break;
} else {
sb.append(line).append("\n");
}
}
reader.close();
is.close();
} catch (IOException e) {
}
return sb.toString();
}
public static String toString(InputStream is) {
return toString(is, "utf-8");
}
public static int toDarkenColor(@ColorInt int color) {
return toDarkenColor(color, 0.8f);
}
public static int toDarkenColor(@ColorInt int color, @FloatRange(from = 0f, to = 1f) float value) {
float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
hsv[2] *= value;//HSV指Hue、Saturation、Value,即色调、饱和度和亮度,此处表示修改亮度
return Color.HSVToColor(hsv);
}
/**
* 转换为6位十六进制颜色代码不含#
*/
public static String toColorString(@ColorInt int color) {
return toColorString(color, false);
}
/**
* 转换为6位十六进制颜色代码不含#
*/
public static String toColorString(@ColorInt int color, boolean includeAlpha) {
String alpha = Integer.toHexString(Color.alpha(color));
String red = Integer.toHexString(Color.red(color));
String green = Integer.toHexString(Color.green(color));
String blue = Integer.toHexString(Color.blue(color));
if (alpha.length() == 1) {
alpha = "0" + alpha;
}
if (red.length() == 1) {
red = "0" + red;
}
if (green.length() == 1) {
green = "0" + green;
}
if (blue.length() == 1) {
blue = "0" + blue;
}
String colorString;
if (includeAlpha) {
colorString = alpha + red + green + blue;
} else {
colorString = red + green + blue;
}
return colorString;
}
/**
* 对TextViewButton等设置不同状态时其文字颜色
* 参见http://blog.csdn.net/sodino/article/details/6797821
* Modified by liyujiang at 2015.08.13
*/
public static ColorStateList toColorStateList(@ColorInt int normalColor, @ColorInt int pressedColor,
@ColorInt int focusedColor, @ColorInt int unableColor) {
int[] colors = new int[]{pressedColor, focusedColor, normalColor, focusedColor, unableColor, normalColor};
int[][] states = new int[6][];
states[0] = new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled};
states[1] = new int[]{android.R.attr.state_enabled, android.R.attr.state_focused};
states[2] = new int[]{android.R.attr.state_enabled};
states[3] = new int[]{android.R.attr.state_focused};
states[4] = new int[]{android.R.attr.state_window_focused};
states[5] = new int[]{};
return new ColorStateList(states, colors);
}
public static ColorStateList toColorStateList(@ColorInt int normalColor, @ColorInt int pressedColor) {
return toColorStateList(normalColor, pressedColor, pressedColor, normalColor);
}
}

@ -0,0 +1,226 @@
package io.legado.app.ui.filechooser.utils;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
/**
* 日期时间工具类
*
* @author 李玉江[QQ:1023694760]
* @since 2015/8/5
*/
public class DateUtils extends android.text.format.DateUtils {
public static final int Second = 0;
public static final int Minute = 1;
public static final int Hour = 2;
public static final int Day = 3;
@IntDef(value = {Second, Minute, Hour, Day})
@Retention(RetentionPolicy.SOURCE)
public @interface DifferenceMode {
}
public static long calculateDifferentSecond(Date startDate, Date endDate) {
return calculateDifference(startDate, endDate, Second);
}
public static long calculateDifferentMinute(Date startDate, Date endDate) {
return calculateDifference(startDate, endDate, Minute);
}
public static long calculateDifferentHour(Date startDate, Date endDate) {
return calculateDifference(startDate, endDate, Hour);
}
public static long calculateDifferentDay(Date startDate, Date endDate) {
return calculateDifference(startDate, endDate, Day);
}
public static long calculateDifferentSecond(long startTimeMillis, long endTimeMillis) {
return calculateDifference(startTimeMillis, endTimeMillis, Second);
}
public static long calculateDifferentMinute(long startTimeMillis, long endTimeMillis) {
return calculateDifference(startTimeMillis, endTimeMillis, Minute);
}
public static long calculateDifferentHour(long startTimeMillis, long endTimeMillis) {
return calculateDifference(startTimeMillis, endTimeMillis, Hour);
}
public static long calculateDifferentDay(long startTimeMillis, long endTimeMillis) {
return calculateDifference(startTimeMillis, endTimeMillis, Day);
}
/**
* 计算两个时间戳之间相差的时间戳数
*/
public static long calculateDifference(long startTimeMillis, long endTimeMillis, @DifferenceMode int mode) {
return calculateDifference(new Date(startTimeMillis), new Date(endTimeMillis), mode);
}
/**
* 计算两个日期之间相差的时间戳数
*/
public static long calculateDifference(Date startDate, Date endDate, @DifferenceMode int mode) {
long[] different = calculateDifference(startDate, endDate);
if (mode == Minute) {
return different[2];
} else if (mode == Hour) {
return different[1];
} else if (mode == Day) {
return different[0];
} else {
return different[3];
}
}
private static long[] calculateDifference(Date startDate, Date endDate) {
return calculateDifference(endDate.getTime() - startDate.getTime());
}
private static long[] calculateDifference(long differentMilliSeconds) {
long secondsInMilli = 1000;//1s==1000ms
long minutesInMilli = secondsInMilli * 60;
long hoursInMilli = minutesInMilli * 60;
long daysInMilli = hoursInMilli * 24;
long elapsedDays = differentMilliSeconds / daysInMilli;
differentMilliSeconds = differentMilliSeconds % daysInMilli;
long elapsedHours = differentMilliSeconds / hoursInMilli;
differentMilliSeconds = differentMilliSeconds % hoursInMilli;
long elapsedMinutes = differentMilliSeconds / minutesInMilli;
differentMilliSeconds = differentMilliSeconds % minutesInMilli;
long elapsedSeconds = differentMilliSeconds / secondsInMilli;
return new long[]{elapsedDays, elapsedHours, elapsedMinutes, elapsedSeconds};
}
/**
* 计算每月的天数
*/
public static int calculateDaysInMonth(int month) {
return calculateDaysInMonth(0, month);
}
/**
* 根据年份及月份计算每月的天数
*/
public static int calculateDaysInMonth(int year, int month) {
// 添加大小月月份并将其转换为list,方便之后的判断
String[] bigMonths = {"1", "3", "5", "7", "8", "10", "12"};
String[] littleMonths = {"4", "6", "9", "11"};
List<String> bigList = Arrays.asList(bigMonths);
List<String> littleList = Arrays.asList(littleMonths);
// 判断大小月及是否闰年,用来确定"日"的数据
if (bigList.contains(String.valueOf(month))) {
return 31;
} else if (littleList.contains(String.valueOf(month))) {
return 30;
} else {
if (year <= 0) {
return 29;
}
// 是否闰年
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
return 29;
} else {
return 28;
}
}
}
/**
* 月日时分秒0-9前补0
*/
@NonNull
public static String fillZero(int number) {
return number < 10 ? "0" + number : "" + number;
}
/**
* 截取掉前缀0以便转换为整数
*
* @see #fillZero(int)
*/
public static int trimZero(@NonNull String text) {
try {
if (text.startsWith("0")) {
text = text.substring(1);
}
return Integer.parseInt(text);
} catch (NumberFormatException e) {
return 0;
}
}
/**
* 功能判断日期是否和当前date对象在同一天
* 参见http://www.cnblogs.com/myzhijie/p/3330970.html
*
* @param date 比较的日期
* @return boolean 如果在返回true否则返回false
*/
public static boolean isSameDay(Date date) {
if (date == null) {
throw new IllegalArgumentException("date is null");
}
Calendar nowCalendar = Calendar.getInstance();
Calendar newCalendar = Calendar.getInstance();
newCalendar.setTime(date);
return (nowCalendar.get(Calendar.ERA) == newCalendar.get(Calendar.ERA) &&
nowCalendar.get(Calendar.YEAR) == newCalendar.get(Calendar.YEAR) &&
nowCalendar.get(Calendar.DAY_OF_YEAR) == newCalendar.get(Calendar.DAY_OF_YEAR));
}
/**
* 将yyyy-MM-dd HH:mm:ss字符串转换成日期<br/>
*
* @param dateStr 时间字符串
* @param dataFormat 当前时间字符串的格式
* @return Date 日期 ,转换异常时返回null
*/
public static Date parseDate(String dateStr, String dataFormat) {
try {
SimpleDateFormat dateFormat = new SimpleDateFormat(dataFormat, Locale.PRC);
Date date = dateFormat.parse(dateStr);
return new Date(date.getTime());
} catch (ParseException e) {
return null;
}
}
/**
* 将yyyy-MM-dd HH:mm:ss字符串转换成日期<br/>
*
* @param dateStr yyyy-MM-dd HH:mm:ss字符串
* @return Date 日期 ,转换异常时返回null
*/
public static Date parseDate(String dateStr) {
return parseDate(dateStr, "yyyy-MM-dd HH:mm:ss");
}
/**
* 将指定的日期转换为一定格式的字符串
*/
public static String formatDate(Date date, String format) {
SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.PRC);
return sdf.format(date);
}
/**
* 将当前日期转换为一定格式的字符串
*/
public static String formatDate(String format) {
return formatDate(Calendar.getInstance(Locale.CHINA).getTime(), format);
}
}

@ -0,0 +1,669 @@
package io.legado.app.ui.filechooser.utils
import android.webkit.MimeTypeMap
import androidx.annotation.IntDef
import java.io.*
import java.nio.charset.Charset
import java.util.*
import java.util.regex.Pattern
/**
* 文件处理
* <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
* <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
*
* @author 李玉江[QQ:1023694760]
* @since 2014-4-18
*/
object FileUtils {
const val BY_NAME_ASC = 0
const val BY_NAME_DESC = 1
const val BY_TIME_ASC = 2
const val BY_TIME_DESC = 3
const val BY_SIZE_ASC = 4
const val BY_SIZE_DESC = 5
const val BY_EXTENSION_ASC = 6
const val BY_EXTENSION_DESC = 7
@IntDef(value = [BY_NAME_ASC, BY_NAME_DESC, BY_TIME_ASC, BY_TIME_DESC, BY_SIZE_ASC, BY_SIZE_DESC, BY_EXTENSION_ASC, BY_EXTENSION_DESC])
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
annotation class SortType
/**
* 将目录分隔符统一为平台默认的分隔符并为目录结尾添加分隔符
*/
fun separator(path: String): String {
var path = path
val separator = File.separator
path = path.replace("\\", separator)
if (!path.endsWith(separator)) {
path += separator
}
return path
}
fun closeSilently(c: Closeable?) {
if (c == null) {
return
}
try {
c.close()
} catch (ignored: IOException) {
}
}
/**
* 列出指定目录下的所有子目录
*/
@JvmOverloads
fun listDirs(
startDirPath: String,
excludeDirs: Array<String?>? = null, @SortType sortType: Int = BY_NAME_ASC
): Array<File?> {
var excludeDirs = excludeDirs
val dirList = ArrayList<File>()
val startDir = File(startDirPath)
if (!startDir.isDirectory) {
return arrayOfNulls(0)
}
val dirs = startDir.listFiles(FileFilter { f ->
if (f == null) {
return@FileFilter false
}
f.isDirectory
})
?: return arrayOfNulls(0)
if (excludeDirs == null) {
excludeDirs = arrayOfNulls(0)
}
for (dir in dirs) {
val file = dir.absoluteFile
if (!ConvertUtils.toString(excludeDirs).contains(file.name)) {
dirList.add(file)
}
}
when (sortType) {
BY_NAME_ASC -> Collections.sort(dirList, SortByName())
BY_NAME_DESC -> {
Collections.sort(dirList, SortByName())
dirList.reverse()
}
BY_TIME_ASC -> Collections.sort(dirList, SortByTime())
BY_TIME_DESC -> {
Collections.sort(dirList, SortByTime())
dirList.reverse()
}
BY_SIZE_ASC -> Collections.sort(dirList, SortBySize())
BY_SIZE_DESC -> {
Collections.sort(dirList, SortBySize())
dirList.reverse()
}
BY_EXTENSION_ASC -> Collections.sort(dirList, SortByExtension())
BY_EXTENSION_DESC -> {
Collections.sort(dirList, SortByExtension())
dirList.reverse()
}
}
return dirList.toTypedArray()
}
/**
* 列出指定目录下的所有子目录及所有文件
*/
@JvmOverloads
fun listDirsAndFiles(
startDirPath: String,
allowExtensions: Array<String?>? = null
): Array<File?>? {
val dirs: Array<File?>?
val files: Array<File?>? = if (allowExtensions == null) {
listFiles(startDirPath)
} else {
listFiles(startDirPath, allowExtensions)
}
val dirsAndFiles: Array<File?>
dirs = listDirs(startDirPath)
if (files == null) {
return null
}
dirsAndFiles = arrayOfNulls(dirs.size + files.size)
System.arraycopy(dirs, 0, dirsAndFiles, 0, dirs.size)
System.arraycopy(files, 0, dirsAndFiles, dirs.size, files.size)
return dirsAndFiles
}
/**
* 列出指定目录下的所有文件
*/
@JvmOverloads
fun listFiles(
startDirPath: String,
filterPattern: Pattern? = null, @SortType sortType: Int = BY_NAME_ASC
): Array<File?> {
val fileList = ArrayList<File>()
val f = File(startDirPath)
if (!f.isDirectory) {
return arrayOfNulls(0)
}
val files = f.listFiles(FileFilter { f ->
if (f == null) {
return@FileFilter false
}
if (f.isDirectory) {
return@FileFilter false
}
filterPattern?.matcher(f.name)?.find() ?: true
})
?: return arrayOfNulls(0)
for (file in files) {
fileList.add(file.absoluteFile)
}
when (sortType) {
BY_NAME_ASC -> Collections.sort(fileList, SortByName())
BY_NAME_DESC -> {
Collections.sort(fileList, SortByName())
fileList.reverse()
}
BY_TIME_ASC -> Collections.sort(fileList, SortByTime())
BY_TIME_DESC -> {
Collections.sort(fileList, SortByTime())
fileList.reverse()
}
BY_SIZE_ASC -> Collections.sort(fileList, SortBySize())
BY_SIZE_DESC -> {
Collections.sort(fileList, SortBySize())
fileList.reverse()
}
BY_EXTENSION_ASC -> Collections.sort(fileList, SortByExtension())
BY_EXTENSION_DESC -> {
Collections.sort(fileList, SortByExtension())
fileList.reverse()
}
}
return fileList.toTypedArray()
}
/**
* 列出指定目录下的所有文件
*/
fun listFiles(startDirPath: String, allowExtensions: Array<String?>): Array<File?>? {
val file = File(startDirPath)
return file.listFiles { dir, name ->
//返回当前目录所有以某些扩展名结尾的文件
val extension = FileUtils.getExtension(name)
ConvertUtils.toString(allowExtensions).contains(extension)
}
}
/**
* 列出指定目录下的所有文件
*/
fun listFiles(startDirPath: String, allowExtension: String?): Array<File?>? {
return listFiles(startDirPath, arrayOf(allowExtension))
}
/**
* 判断文件或目录是否存在
*/
fun exist(path: String): Boolean {
val file = File(path)
return file.exists()
}
/**
* 删除文件或目录
*/
@JvmOverloads
fun delete(file: File, deleteRootDir: Boolean = false): Boolean {
var result = false
if (file.isFile) {
//是文件
result = deleteResolveEBUSY(file)
} else {
//是目录
val files = file.listFiles() ?: return false
if (files.isEmpty()) {
result = deleteRootDir && deleteResolveEBUSY(file)
} else {
for (f in files) {
delete(f, deleteRootDir)
result = deleteResolveEBUSY(f)
}
}
if (deleteRootDir) {
result = deleteResolveEBUSY(file)
}
}
return result
}
/**
* bug: open failed: EBUSY (Device or resource busy)
* fix: http://stackoverflow.com/questions/11539657/open-failed-ebusy-device-or-resource-busy
*/
private fun deleteResolveEBUSY(file: File): Boolean {
// Before you delete a Directory or File: rename it!
val to = File(file.absolutePath + System.currentTimeMillis())
file.renameTo(to)
return to.delete()
}
/**
* 删除文件或目录
*/
@JvmOverloads
fun delete(path: String, deleteRootDir: Boolean = false): Boolean {
val file = File(path)
return if (file.exists()) {
delete(file, deleteRootDir)
} else false
}
/**
* 复制文件为另一个文件或复制某目录下的所有文件及目录到另一个目录下
*/
fun copy(src: String, tar: String): Boolean {
val srcFile = File(src)
return srcFile.exists() && copy(srcFile, File(tar))
}
/**
* 复制文件或目录
*/
fun copy(src: File, tar: File): Boolean {
try {
if (src.isFile) {
val `is` = FileInputStream(src)
val op = FileOutputStream(tar)
val bis = BufferedInputStream(`is`)
val bos = BufferedOutputStream(op)
val bt = ByteArray(1024 * 8)
while (true) {
val len = bis.read(bt)
if (len == -1) {
break
} else {
bos.write(bt, 0, len)
}
}
bis.close()
bos.close()
} else if (src.isDirectory) {
val files = src.listFiles()
tar.mkdirs()
for (file in files) {
copy(file.absoluteFile, File(tar.absoluteFile, file.name))
}
}
return true
} catch (e: Exception) {
return false
}
}
/**
* 移动文件或目录
*/
fun move(src: String, tar: String): Boolean {
return move(File(src), File(tar))
}
/**
* 移动文件或目录
*/
fun move(src: File, tar: File): Boolean {
return rename(src, tar)
}
/**
* 文件重命名
*/
fun rename(oldPath: String, newPath: String): Boolean {
return rename(File(oldPath), File(newPath))
}
/**
* 文件重命名
*/
fun rename(src: File, tar: File): Boolean {
return src.renameTo(tar)
}
/**
* 读取文本文件, 失败将返回空串
*/
@JvmOverloads
fun readText(filepath: String, charset: String = "utf-8"): String {
try {
val data = readBytes(filepath)
if (data != null) {
return String(data, Charset.forName(charset)).trim { it <= ' ' }
}
} catch (ignored: UnsupportedEncodingException) {
}
return ""
}
/**
* 读取文件内容, 失败将返回空串
*/
fun readBytes(filepath: String): ByteArray? {
var fis: FileInputStream? = null
try {
fis = FileInputStream(filepath)
val baos = ByteArrayOutputStream()
val buffer = ByteArray(1024)
while (true) {
val len = fis.read(buffer, 0, buffer.size)
if (len == -1) {
break
} else {
baos.write(buffer, 0, len)
}
}
val data = baos.toByteArray()
baos.close()
return data
} catch (e: IOException) {
return null
} finally {
closeSilently(fis)
}
}
/**
* 保存文本内容
*/
@JvmOverloads
fun writeText(filepath: String, content: String, charset: String = "utf-8"): Boolean {
try {
writeBytes(filepath, content.toByteArray(charset(charset)))
return true
} catch (e: UnsupportedEncodingException) {
return false
}
}
/**
* 保存文件内容
*/
fun writeBytes(filepath: String, data: ByteArray): Boolean {
val file = File(filepath)
var fos: FileOutputStream? = null
try {
if (!file.exists()) {
file.parentFile.mkdirs()
file.createNewFile()
}
fos = FileOutputStream(filepath)
fos.write(data)
return true
} catch (e: IOException) {
return false
} finally {
closeSilently(fos)
}
}
/**
* 追加文本内容
*/
fun appendText(path: String, content: String): Boolean {
val file = File(path)
var writer: FileWriter? = null
try {
if (!file.exists()) {
file.createNewFile()
}
writer = FileWriter(file, true)
writer.write(content)
return true
} catch (e: IOException) {
return false
} finally {
closeSilently(writer)
}
}
/**
* 获取文件大小
*/
fun getLength(path: String): Long {
val file = File(path)
return if (!file.isFile || !file.exists()) {
0
} else file.length()
}
/**
* 获取文件或网址的名称包括后缀
*/
fun getName(pathOrUrl: String?): String {
if (pathOrUrl == null) {
return ""
}
val pos = pathOrUrl.lastIndexOf('/')
return if (0 <= pos) {
pathOrUrl.substring(pos + 1)
} else {
System.currentTimeMillis().toString() + "." + getExtension(pathOrUrl)
}
}
/**
* 获取文件名不包括扩展名
*/
fun getNameExcludeExtension(path: String): String {
try {
var fileName = File(path).name
val lastIndexOf = fileName.lastIndexOf(".")
if (lastIndexOf != -1) {
fileName = fileName.substring(0, lastIndexOf)
}
return fileName
} catch (e: Exception) {
return ""
}
}
/**
* 获取格式化后的文件大小
*/
fun getSize(path: String): String {
val fileSize = getLength(path)
return ConvertUtils.toFileSizeString(fileSize)
}
/**
* 获取文件后缀,不包括.
*/
fun getExtension(pathOrUrl: String): String {
val dotPos = pathOrUrl.lastIndexOf('.')
return if (0 <= dotPos) {
pathOrUrl.substring(dotPos + 1)
} else {
"ext"
}
}
/**
* 获取文件的MIME类型
*/
fun getMimeType(pathOrUrl: String): String {
val ext = getExtension(pathOrUrl)
val map = MimeTypeMap.getSingleton()
return map.getMimeTypeFromExtension(ext) ?: "*/*"
}
/**
* 获取格式化后的文件/目录创建或最后修改时间
*/
@JvmOverloads
fun getDateTime(path: String, format: String = "yyyy年MM月dd日HH:mm"): String {
val file = File(path)
return getDateTime(file, format)
}
/**
* 获取格式化后的文件/目录创建或最后修改时间
*/
fun getDateTime(file: File, format: String): String {
val cal = Calendar.getInstance()
cal.timeInMillis = file.lastModified()
return DateUtils.formatDate(cal.time, format)
}
/**
* 比较两个文件的最后修改时间
*/
fun compareLastModified(path1: String, path2: String): Int {
val stamp1 = File(path1).lastModified()
val stamp2 = File(path2).lastModified()
return if (stamp1 > stamp2) {
1
} else if (stamp1 < stamp2) {
-1
} else {
0
}
}
/**
* 创建多级别的目录
*/
fun makeDirs(path: String): Boolean {
return makeDirs(File(path))
}
/**
* 创建多级别的目录
*/
fun makeDirs(file: File): Boolean {
return file.mkdirs()
}
class SortByExtension : Comparator<File> {
override fun compare(f1: File?, f2: File?): Int {
return if (f1 == null || f2 == null) {
if (f1 == null) {
-1
} else {
1
}
} else {
if (f1.isDirectory && f2.isFile) {
-1
} else if (f1.isFile && f2.isDirectory) {
1
} else {
f1.name.compareTo(f2.name, ignoreCase = true)
}
}
}
}
class SortByName : Comparator<File> {
private var caseSensitive: Boolean = false
constructor(caseSensitive: Boolean) {
this.caseSensitive = caseSensitive
}
constructor() {
this.caseSensitive = false
}
override fun compare(f1: File?, f2: File?): Int {
if (f1 == null || f2 == null) {
return if (f1 == null) {
-1
} else {
1
}
} else {
return if (f1.isDirectory && f2.isFile) {
-1
} else if (f1.isFile && f2.isDirectory) {
1
} else {
val s1 = f1.name
val s2 = f2.name
if (caseSensitive) {
s1.compareTo(s2)
} else {
s1.compareTo(s2, ignoreCase = true)
}
}
}
}
}
class SortBySize : Comparator<File> {
override fun compare(f1: File?, f2: File?): Int {
return if (f1 == null || f2 == null) {
if (f1 == null) {
-1
} else {
1
}
} else {
if (f1.isDirectory && f2.isFile) {
-1
} else if (f1.isFile && f2.isDirectory) {
1
} else {
if (f1.length() < f2.length()) {
-1
} else {
1
}
}
}
}
}
class SortByTime : Comparator<File> {
override fun compare(f1: File?, f2: File?): Int {
return if (f1 == null || f2 == null) {
if (f1 == null) {
-1
} else {
1
}
} else {
if (f1.isDirectory && f2.isFile) {
-1
} else if (f1.isFile && f2.isDirectory) {
1
} else {
if (f1.lastModified() > f2.lastModified()) {
-1
} else {
1
}
}
}
}
}
}

@ -0,0 +1,71 @@
package io.legado.app.ui.filechooser.utils;
import android.app.Activity;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.Window;
import android.view.WindowManager;
/**
* 获取屏幕宽高等信息全屏切换保持屏幕常亮截屏等
*
* @author liyujiang[QQ:1032694760]
* @since 2015/11/26
*/
public final class ScreenUtils {
private static boolean isFullScreen = false;
private static DisplayMetrics dm = null;
public static DisplayMetrics displayMetrics(Context context) {
if (null != dm) {
return dm;
}
DisplayMetrics dm = new DisplayMetrics();
WindowManager windowManager = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
windowManager.getDefaultDisplay().getMetrics(dm);
return dm;
}
public static int widthPixels(Context context) {
return displayMetrics(context).widthPixels;
}
public static int heightPixels(Context context) {
return displayMetrics(context).heightPixels;
}
public static float density(Context context) {
return displayMetrics(context).density;
}
public static int densityDpi(Context context) {
return displayMetrics(context).densityDpi;
}
public static boolean isFullScreen() {
return isFullScreen;
}
public static void toggleFullScreen(Activity activity) {
Window window = activity.getWindow();
int flagFullscreen = WindowManager.LayoutParams.FLAG_FULLSCREEN;
if (isFullScreen) {
window.clearFlags(flagFullscreen);
isFullScreen = false;
} else {
window.setFlags(flagFullscreen, flagFullscreen);
isFullScreen = true;
}
}
/**
* 保持屏幕常亮
*/
public static void keepBright(Activity activity) {
//需在setContentView前调用
int keepScreenOn = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
activity.getWindow().setFlags(keepScreenOn, keepScreenOn);
}
}

@ -0,0 +1,161 @@
package io.legado.app.ui.filechooser.utils;
import android.content.Context;
import android.os.Environment;
import android.text.TextUtils;
import java.io.File;
import java.io.IOException;
/**
* 存储设备工具类
* <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
* <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
*
* @author 李玉江[QQ:1023694760]
* @since 2013-11-2
*/
public final class StorageUtils {
/**
* 判断外置存储是否可用
*
* @return the boolean
*/
public static boolean externalMounted() {
String state = Environment.getExternalStorageState();
if (state.equals(Environment.MEDIA_MOUNTED)) {
return true;
}
return false;
}
/**
* 返回以/结尾的内部存储根目录
*/
public static String getInternalRootPath(Context context, String type) {
File file;
if (TextUtils.isEmpty(type)) {
file = context.getFilesDir();
} else {
file = new File(FileUtils.INSTANCE.separator(context.getFilesDir().getAbsolutePath()) + type);
//noinspection ResultOfMethodCallIgnored
file.mkdirs();
}
String path = "";
if (file != null) {
path = FileUtils.INSTANCE.separator(file.getAbsolutePath());
}
return path;
}
public static String getInternalRootPath(Context context) {
return getInternalRootPath(context, null);
}
/**
* 返回以/结尾的外部存储根目录外置卡不可用则返回空字符串
*/
public static String getExternalRootPath(String type) {
File file = null;
if (externalMounted()) {
file = Environment.getExternalStorageDirectory();
}
if (file != null && !TextUtils.isEmpty(type)) {
file = new File(file, type);
//noinspection ResultOfMethodCallIgnored
file.mkdirs();
}
String path = "";
if (file != null) {
path = FileUtils.INSTANCE.separator(file.getAbsolutePath());
}
return path;
}
public static String getExternalRootPath() {
return getExternalRootPath(null);
}
/**
* 各种类型的文件的专用的保存路径/结尾
*
* @return 诸如/mnt/sdcard/Android/data/[package]/files/[type]/
*/
public static String getExternalPrivatePath(Context context, String type) {
File file = null;
if (externalMounted()) {
file = context.getExternalFilesDir(type);
}
//高频触发java.lang.NullPointerException,是SD卡不可用或暂时繁忙么?
String path = "";
if (file != null) {
path = FileUtils.INSTANCE.separator(file.getAbsolutePath());
}
return path;
}
public static String getExternalPrivatePath(Context context) {
return getExternalPrivatePath(context, null);
}
/**
* 下载的文件的保存路径必须为外部存储/结尾
*
* @return 诸如 /mnt/sdcard/Download/
*/
public static String getDownloadPath() throws RuntimeException {
File file;
if (externalMounted()) {
file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
} else {
throw new RuntimeException("外置存储不可用!");
}
return FileUtils.INSTANCE.separator(file.getAbsolutePath());
}
/**
* 各种类型的文件的专用的缓存存储保存路径优先使用外置存储/结尾
*/
public static String getCachePath(Context context, String type) {
File file;
if (externalMounted()) {
file = context.getExternalCacheDir();
} else {
file = context.getCacheDir();
}
if (!TextUtils.isEmpty(type)) {
file = new File(file, type);
//noinspection ResultOfMethodCallIgnored
file.mkdirs();
}
String path = "";
if (file != null) {
path = FileUtils.INSTANCE.separator(file.getAbsolutePath());
}
return path;
}
public static String getCachePath(Context context) {
return getCachePath(context, null);
}
/**
* 返回以/结尾的临时存储目录
*/
public static String getTempDirPath(Context context) {
return getExternalPrivatePath(context, "temporary");
}
/**
* 返回临时存储文件路径
*/
public static String getTempFilePath(Context context) {
try {
return File.createTempFile("lyj_", ".tmp", context.getCacheDir()).getAbsolutePath();
} catch (IOException e) {
return getTempDirPath(context) + "lyj.tmp";
}
}
}

@ -9,10 +9,17 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<View
android:layout_width="match_parent"
android:layout_height="0.2dp"
android:background="@color/btn_bg_press_2" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_path"
android:layout_width="match_parent"
android:layout_height="30dp" />
android:layout_height="24dp"
android:paddingLeft="10dp"
android:paddingRight="10dp" />
<View
android:layout_width="match_parent"

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:padding="5dp">
<ImageView
android:id="@+id/image_view"
android:layout_width="24dp"
android:layout_height="24dp"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical" />
</LinearLayout>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true">
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical" />
<ImageView
android:id="@+id/image_view"
android:layout_width="20dp"
android:layout_height="match_parent"
tools:ignore="ContentDescription" />
</LinearLayout>

@ -535,5 +535,6 @@
<string name="dur_pos">当前位置:</string>
<string name="precision_search">精准搜索</string>
<string name="service_starting">正在启动服务</string>
<string name="empty"></string>
</resources>

Loading…
Cancel
Save