【Android】14.3 浏览手机中的所有文件夹和文件
分类:C#、Android、VS2015;
创建日期:2016-02-27
一、简介
前面我们了解了内部存储、外部存储的含义,用一句话说,内部存储实际上是保存在“data”文件夹下,外部存储(SD卡)实际是保存在“sdcard”或者“storage”文件夹下。
这个例子演示如何将这些内部存储和外部存储的文件夹及其子文件架下的文件全部显示出来,类似于树形结构一层一层地向下看(例子没有实现返回上层的功能,或者说,仅仅实现了Android自带的文件浏览功能的一部分功能)。
二、示例3运行截图
下面左图为单击【sdcard】后看到的结果,右图为单击【Download】后看到的结果。
三、主要设计步骤
本示例需要应用程序具有对外部文件的读写权限,由于上一个例子已经设置了对应的权限,所以可直接运行。
1、添加图像
在drawable文件夹下添加ch14_file.png、ch14_folder.png文件,图片自己找吧。
2、添加ch1403_ListItem.axml文件
在Resources/layout文件夹下添加该文件。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <ImageView android:id="@+id/ch14_image1" android:layout_width="40dip" android:layout_height="40dip" android:layout_marginTop="5dip" android:layout_marginBottom="5dip" android:layout_marginLeft="5dip" android:src="@drawable/ch14_file" android:scaleType="centerCrop" /> <TextView android:id="@+id/ch14_text1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="1" android:layout_gravity="left|center_vertical" android:textSize="28sp" android:layout_marginLeft="10dip" android:singleLine="true" android:text="文件名" /> </LinearLayout>
3、添加ch1403Helpers.cs文件
在SrcDemos文件夹下添加该文件,模板选择【Class】。
using Android.Content; using Android.Runtime; using Android.Views; using System.IO; namespace MyDemos.SrcDemos { public static class ch1403Helpers { public static LayoutInflater GetLayoutInflater(this Context context) { return context.GetSystemService(Context.LayoutInflaterService).JavaCast<LayoutInflater>(); } public static bool IsDirectory(this FileSystemInfo fsi) { if (fsi == null || !fsi.Exists) { return false; } return (fsi.Attributes & FileAttributes.Directory) == FileAttributes.Directory; } public static bool IsFile(this FileSystemInfo fsi) { if (fsi == null || !fsi.Exists) { return false; } return !IsDirectory(fsi); } public static bool IsVisible(this FileSystemInfo fsi) { if (fsi == null || !fsi.Exists) { return false; } var isHidden = (fsi.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden; return !isHidden; } } }
4、添加ch1403FileListRowViewHolder.cs文件
在SrcDemos文件夹下添加该文件,模板选择【Class】。
using Android.Widget; namespace MyDemos.SrcDemos { public class ch1403FileListRowViewHolder : Java.Lang.Object { public ImageView ImageView { get; private set; } public TextView TextView { get; private set; } public ch1403FileListRowViewHolder(TextView textView, ImageView imageView) { TextView = textView; ImageView = imageView; } public void Update(string fileName, int fileImageResourceId) { TextView.Text = fileName; ImageView.SetImageResource(fileImageResourceId); } } }
该类是这个例子中第1个比较重要的组件,这是一个性能优化类(适配器的GetView用这个类来实现)。
在这个类中,要显示的图标和名称分别用ImageView和TextView来显示。由于类中设置了Tag属性,因此在回收视图时,就没有必要每次都通过FindViewById来调用这两个视图了。
注意该类继承自Java.Lang.Object类,这是因为View.Tag类型不是.NET的System.Object类型,而是Java.Lang.Object类型。
当用户点击某个文件夹时,ListView会自动填充该文件夹下子文件夹的内容。
当用户选择某个文件时,该例子仅仅用Toast显示了文件的完整路径,并没有做其他的更多处理。
5、添加ch1403FileListAdapter.cs文件
在SrcDemos文件夹下添加该文件,模板选择【Class】。
using System.Collections.Generic; using System.Linq; using Android.Content; using Android.Views; using Android.Widget; using System.IO; namespace MyDemos.SrcDemos { public class ch1403FileListAdapter : ArrayAdapter<FileSystemInfo> { private readonly Context _context; public ch1403FileListAdapter(Context context, IList<FileSystemInfo> fsi) : base(context, Resource.Layout.ch1403_ListItem, Android.Resource.Id.Text1, fsi) { _context = context; } public void AddDirectoryContents(IEnumerable<FileSystemInfo> directoryContents) { Clear(); if (directoryContents.Any()) { AddAll(directoryContents.ToArray()); NotifyDataSetChanged(); } else { NotifyDataSetInvalidated(); } } public override View GetView(int position, View convertView, ViewGroup parent) { FileSystemInfo fileSystemEntry = GetItem(position); ch1403FileListRowViewHolder viewHolder; View row; if (convertView == null) { row = ch1403Helpers.GetLayoutInflater(_context).Inflate(Resource.Layout.ch1403_ListItem, parent, false); viewHolder = new ch1403FileListRowViewHolder( row.FindViewById<TextView>(Resource.Id.ch14_text1), row.FindViewById<ImageView>(Resource.Id.ch14_image1)); row.Tag = viewHolder; } else { row = convertView; viewHolder = (ch1403FileListRowViewHolder)row.Tag; } if (ch1403Helpers.IsDirectory(fileSystemEntry)) { viewHolder.Update(fileSystemEntry.Name, Resource.Drawable.ch14_folder); } else { viewHolder.Update(fileSystemEntry.Name, Resource.Drawable.ch14_file); } return row; } } }
该类是这个例子中第2个比较重要的组件。让ArrayAdapter基类处理需要显示的动态列表时,这种方式是一种比较好的处理办法,这是因为通过这种方式可照顾到很多需要在管理列表中做的工作。
在这个类中,让列表中的每一行显示一个文件夹(包括文件夹的图标和文件夹的名称),或者显示文件的图标和文件的名称。对于这些行来说,由于创建视图时重写了GetView()方法。因此无论列表中的行是文件名还是目录名,都可以用相同的XML布局来显示(见ch1403_ListItem.axml文件)。
在重写的GetView()方法中,ArrayAdapter基类会从基础列表中获取FileSystemInfo对象,然后将其返回给视图。
从性能来说,由于ListView可能回收现有的行,这种循环的行可作为convertView参数来传递。代码中通过检查convertView是否为null来决定填充方式。如果为null,则通过指定的XML布局填充它。
6、添加ch1403FileListFragment.cs文件
在SrcDemos文件夹下添加该文件,模板选择【Fragment】。
using System; using System.Collections.Generic; using System.Linq; using Android.App; using Android.OS; using Android.Views; using Android.Widget; using System.IO; namespace MyDemos.SrcDemos { public class ch1403FileListFragment : ListFragment { public static readonly string DefaultInitialDirectory = "/"; private ch1403FileListAdapter _adapter; private DirectoryInfo _directory ; public override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); _adapter = new ch1403FileListAdapter(Activity, new FileSystemInfo[0]); ListAdapter = _adapter; } public override void OnListItemClick(ListView l, View v, int position, long id) { var fileSystemInfo = _adapter.GetItem(position); if (ch1403Helpers.IsFile(fileSystemInfo)) { Toast.MakeText(Activity, "你选择了文件:" + fileSystemInfo.FullName, ToastLength.Short).Show(); } else { RefreshFilesList(fileSystemInfo.FullName); } base.OnListItemClick(l, v, position, id); } public override void OnResume() { base.OnResume(); RefreshFilesList(DefaultInitialDirectory); } public void RefreshFilesList(string directory) { IList<FileSystemInfo> visibleThings = new List<FileSystemInfo>(); var dir = new DirectoryInfo(directory); try { var a = dir.GetFileSystemInfos() .Where(item => ch1403Helpers.IsVisible(item)); foreach (var item in a) { visibleThings.Add(item); } } catch (Exception ex) { Toast.MakeText(Activity, $"获取{directory}的内容出错," + $"无法存取该目录:{_directory.FullName}\n" + "出错原因:" + ex, ToastLength.Long).Show(); return; } _directory = dir; _adapter.AddDirectoryContents(visibleThings); ListView.RefreshDrawableState(); } } }
FileListFragment子类继承自ListFragment类,用于显示文件夹下的内容。这是这个例子中第3个比较重要的组件,在这个文件中,用一个单独的activity承载(hosts)这个ListFragment。
FileListAdapter加载Activity以后,会自动创建这个fragment,如layout文件夹下的Main.axml文件所示。
创建这个fragment时,在其生命周期内会自动调用OnCreate()方法。在该方法中,用FileListAdapter构造一个空数组来初始化FileSystemInfo对象,并设置ListAdapter属性。
fragment下一个生命周期实现的是OnResume()方法。此方法将在当前目录中创建的文件和子目录列表提供给FileListAdapter。
刷新适配器的逻辑是在RefreshFileList()方法中实现的。
接下来需要重写OnListItemClick()方法。用户每次点击列表视图中的行,都会调用此方法。在这个方法中,FileSystemInfoobject是从适配器被单击的行中检索的。如果对象是一个文件,Toast将显示带路径的的完整文件名,否则显示原目录名。
7、添加纵向放置的ch1403_Main.axml文件
在Resources/layout文件夹下添加该文件。
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment class="MyDemos.SrcDemos.ch1403FileListFragment" android:id="@+id/ch14_fragment" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout>
8、添加横向放置的ch1403_main.axml文件
在Resources/layout-land子文件夹下添加该文件,注意文件名必须和纵向放置的文件名相同,否则切换到横屏显示时它无法自动找到对应的文件。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <fragment class="MyDemos.SrcDemos.ch1403FileListFragment" android:id="@+id/ch14_titles_fragment" android:layout_weight="1" android:layout_width="0px" android:layout_height="match_parent" /> <FrameLayout android:id="@+id/ch14_details" android:layout_weight="1" android:layout_width="0px" android:layout_height="match_parent" android:background="@android:color/holo_blue_light" /> </LinearLayout>
9、添加ch1403MainActivity.cs文件
在SrcDemos文件夹下添加该文件,模板选择【Activity】。
using Android.App; using Android.OS; namespace MyDemos.SrcDemos { [Activity(Label = "例14-3 手机目录和文件浏览")] public class ch1403MainActivity : Activity { protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); SetContentView(Resource.Layout.ch1403_Main); } } }
注意:ch1403MainActivity继承自FragmentActivity而不是继承自Activity。
FragmentActivity是Android.Support.V4.App命名空间下提供的类,添加程序包的办法见【13.0节】的介绍。
按<F5>键运行程序,即得到截图所示的结果。