先上效果图:
本文的FTP客户端基于commons-net-3.3.jar库实现。
实现了ftp服务器登录。
单个文件的下载和上传,以及本地复制和删除文件。
一、登录服务器活动模块编写:
这块呢首先是要编写一个登录的界面的。
我的界面XML如下:
主要就是利用TextInputLayout这个控件来编写的。不清楚这个控件的可以 百度/Google 学习一下。
<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".FtpConnectActivity">
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="地址"
android:maxLines="1"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/port"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="端口/默认21"
android:maxLines="1"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/user"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="账户"
android:maxLines="1"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="密码"
android:inputType="textPassword"
android:maxLines="1"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/connect_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="连接"/>
</LinearLayout>
接下来看一下我的登录模块活动的Java代码:
public class FtpConnectActivity extends AppCompatActivity {
private FTPClient mFtpClient;
private Button Connect_Button;
private String address,password,user;
private int port=0;
private EditText addressInput,portInput,userInput,passwordInput;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ftp_connect);
SharedPreferences pref =getSharedPreferences("connectData",MODE_PRIVATE);
address=pref.getString("address","X");
password=pref.getString("password","X");
user=pref.getString("user","X");
port=pref.getInt("port",0);
addressInput=(EditText)findViewById(R.id.address);
portInput=(EditText)findViewById(R.id.port);
userInput=(EditText)findViewById(R.id.user);
passwordInput=(EditText)findViewById(R.id.password);
if(address!=null&&!address.equals("X")){
addressInput.setText(address);
}
if(password!=null&&!password.equals("X")){
passwordInput.setText(password);
}
if(user!=null&&!user.equals("X")){
userInput.setText(user);
}
if(port!=0){
portInput.setText(" "+port);
}
Connect_Button=(Button)findViewById(R.id.connect_button);
Connect_Button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
address=addressInput.getText().toString();
try {
port = Integer.parseInt(portInput.getText().toString().trim());
}catch (NumberFormatException e){
port=0;
e.printStackTrace();
}
user=userInput.getText().toString();
password=passwordInput.getText().toString();
//信息保存
SharedPreferences.Editor editor=getSharedPreferences("connectData",MODE_PRIVATE).edit();
editor.putString("address",address);
editor.putString("user",user);
editor.putString("password",password);
editor.putInt("port",port);
editor.apply();
attemptLogin();
}
});
}
private void attemptLogin(){
if(address.equals("")||port==0||user.equals("")||password.equals("")){
Toast.makeText(this,"请填写完整的信息",Toast.LENGTH_SHORT).show();
return;
}else{
FtpLogin(address,port,user,password);
}
}
private void FtpLogin(final String address, final int port, final String user, final String password ){
FtpUtil.Init(address,port,user,password);
if(FtpUtil.Connect()){
//备注:后续,这里可以试着模仿okhttp的回调模式
//如果连接成功
Intent intent=new Intent();
intent.setAction("com.app.bhk.connected");
sendBroadcast(intent);
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(FtpConnectActivity.this, "success", Toast.LENGTH_SHORT).show();
}
});
}else {
//连接失败
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(FtpConnectActivity.this, "failure", Toast.LENGTH_SHORT).show();
}
});
}
}
}
首先一般登录模块都会有记住密码功能所以这里我实现了记住密码功能,不过这里我并没有添加记住密码的这个勾选框,也就是默认记住密码的,有需要的朋友的可以自己添加。记住密码主要是利用SharePreference实现的。大概逻辑如下:当用户点击连接时,获取到用户的输入信息将其存放在SharePreference数据库内。下次启动这个登录活动时自动提取出上次的输入信息,就可以了。具体可看代码。
然后就是说一下attemptLogin()这个方法,这个方法用于做登录之前的准备,包括用户输入信息的格式判断。有兴趣研究登录模块编写的朋友可以看我的另一篇博客《Android登录模块代码解析》。
最后FtpLogin()就是正式的关于连接ftp服务器的一些操作了。这里我写了一个FtpUtil类专门来处理关于Ftp服务器的一些操作。这里直接调用相关方法进行登录就行了。如果登录成功这里还要写一个广播去通知主活动我们登录成功了,还有将获取到的FTPClient对象保存在FtpUtil中以便其他地方获取!
二、工具类
在看主活动代码之前我们先看一下我的两个工具类:FileUtil(本地文件操作工具类)、FtpUtil(服务器文件操作工具类)的代码。
public class FileUtil {
private static File saveFile=null;
private static FTPClient ftpClient;
public static FTPClient getFtpClient() {
return ftpClient;
}
public static void setFtpClient(FTPClient ftpClient) {
FileUtil.ftpClient = ftpClient;
}
public static File getSaveFile() {
return saveFile;
}
public static void setSaveFile(File saveFile) {
FileUtil.saveFile = saveFile;
}
public static void Copy(File file, File directory){
FileOutputStream outputStream=null;
FileInputStream inputStream=null;
if(!directory.isDirectory()||!file.isFile()||file.getParentFile().equals(directory))
return;
try {
File newFile=new File(directory,file.getName());
if(!newFile.exists()){
newFile.createNewFile();
}
outputStream=new FileOutputStream(newFile);
inputStream = new FileInputStream(file);
byte[] buf = new byte[1024];
int bytesRead;
while((bytesRead=inputStream.read(buf))>0){
outputStream.write(buf,0,bytesRead);
}
inputStream.close();
outputStream.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
public class FtpUtil {
private static FTPClient mClient;
private static String hostname;
private static int port=21;//端口号默认21
private static String account;
private static String password;
private static boolean check;
private static FTPFile lists[];
private static StringBuffer CurrentFile=new StringBuffer();
private static FTPFile ftpFile;
public static FTPFile getFtpFile() {
return ftpFile;
}
public static void setFtpFile(FTPFile ftpFile) {
FtpUtil.ftpFile = ftpFile;
}
public static void LastDirectory(){
//切换目录,没用的原因是多个子线程没法同步控制有待改进。思考可以用锁来实现留待后续。
try {
int i = CurrentFile.toString().lastIndexOf('/');
if (i >= 0) {
CurrentFile.delete(i, CurrentFile.length());
boolean f = mClient.changeToParentDirectory();
Log.d("test1234", " " + f + " " + CurrentFile);
}
}catch (IOException e){
e.printStackTrace();
}
}
public static void NextDirectoty(final FTPFile file){
//切换目录,没用的原因是多个子线程没法同步控制有待改进。思考可以用锁来实现留待后续。
new Thread(new Runnable() {
@Override
public void run() {
try {
CurrentFile.append("/" + file.getName());
boolean f = mClient.changeWorkingDirectory(CurrentFile.toString());
}catch (IOException e){
e.printStackTrace();
}
}
}).start();
}
public static void Init(String hostname1,int port1,String account1,String password1){
hostname=hostname1;
port=port1;
account=account1;
password=password1;
if(mClient==null) {
mClient = new FTPClient();
}
if(mClient.isConnected()){
try {
mClient.disconnect();
}catch (IOException e){
e.printStackTrace();
}
}
}
public static FTPClient getmClient() {
return mClient;
}
public static boolean Connect(){
check=true;
new Thread(new Runnable() {
@Override
public void run() {
try {
if (!mClient.isConnected()) {
mClient.connect(hostname,port);
boolean status = mClient.login(account, password);
if (status) {
check=true;
try {
mClient.setFileTransferMode(org.apache.commons.net.ftp.FTP.COMPRESSED_TRANSFER_MODE);
// 使用被动模式设为默认
mClient.enterLocalPassiveMode();
// 二进制文件支持
mClient.setFileType(FTP.BINARY_FILE_TYPE);
//设置缓存
mClient.setBufferSize(1024);
//设置编码格式,防止中文乱码
//中文乱码问题后期再说吧
mClient.setControlEncoding("UTF-8");
//设置连接超时时间
mClient.setConnectTimeout(10 * 1000);
//设置数据传输超时时间
mClient.setDataTimeout(10 * 1000);
}catch (IOException e){
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
check=false;
}
}
}).start();
return check;
}
/**
* 上传.
*
* @param localFilePath 需要上传的本地文件路径
* @return 上传结果
* @throws IOException
*/
public static boolean uploadFile(String localFilePath) throws IOException {
boolean flag = false;
// 二进制文件支持
mClient.setFileType(FTPClient.BINARY_FILE_TYPE);
// 设置模式
mClient.setFileTransferMode(FTPClient.STREAM_TRANSFER_MODE);
File localFile = new File(localFilePath);
if (localFile.exists() && localFile.isFile()) {
flag = uploadingSingle(localFile);
}
// 返回值
return flag;
}
private static boolean uploadingSingle(File localFile) throws IOException {
boolean flag;
// 创建输入流
InputStream inputStream = new FileInputStream(localFile);
// 上传单个文件
flag = mClient.storeFile(localFile.getName(), inputStream);
// 关闭文件流
inputStream.close();
return flag;
}
public boolean downloadFile(String localPath,FTPFile ftpFile) throws IOException {
boolean result = false;
//在本地创建对应文件夹目录
File localFile= new File(localPath);
if (!localFile.exists()) {
localFile.createNewFile();
}
result = downloadSingle(localFile, ftpFile);
return result;
}
/**
* 下载单个文件,此时ftpFile必须在ftp工作目录下
*
* @param localFile 本地目录
* @param ftpFile FTP文件
* @return true下载成功, false下载失败
* @throws IOException
*/
public static boolean downloadSingle(File localFile, FTPFile ftpFile) throws IOException {
boolean flag;
// 创建输出流
File file=new File(localFile,ftpFile.getName());
if(!file.exists()){
file.createNewFile();
}
OutputStream outputStream = new FileOutputStream(file);
// 下载单个文件
flag = mClient.retrieveFile(ftpFile.getName(), outputStream);
// 关闭文件流
outputStream.close();
return flag;
}
}
上述注释基本都比较详细就不再赘述其中细节了。下面进入核心的主活动以及其相关代码部分。
三、主活动代码
先说明我的代码的一个重要部分。我们面临这样一个问题:因为服务器的文件是用FTPFile类表示的,而本地的文件是用File表示的。这样我们主活动上用于显示文件列表的Recyclerview就不能用一个适配器来解决了。因为这个两各类没有父子关系和亦或是同一个接口或者父类的子类关系。我的解决方法是:既然不能用一个适配器那就写两个适配器,在需要在服务器文件列表和本地文件列表之间切换时,主活动的recyclerview就使用setAdapter()进行切换。这样就像一个插口一样既可以插这个适配器,又可以插那个适配器。以下是我的主活动代码:
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private FileAdapter adapter;
private FtpFileAdapter adapter1;
private List<File> FileList;
private ProgressDialog progressDialog;
private DrawerLayout mDrawerLayout;
private ConnectBroadcastReceiver connectBroadcastReceiver;
private FTPClient mClient;
private List<FTPFile> Ftplist=new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getPermissions();
mDrawerLayout=(DrawerLayout)findViewById(R.id.drawer_layout);
ActionBar actionBar=getSupportActionBar();
if(actionBar!=null){
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.drawable.menu);
}
NavigationView navigationView=(NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
case R.id.nav_setting:
Intent intent=new Intent(MainActivity.this,FtpConnectActivity.class);
startActivity(intent);
break;
case R.id.nav_FtpServerDirectory:
if(mClient!=null&&mClient.isConnected()){
new Thread(new Runnable() {
@Override
public void run() {
try {
FTPFile files[] = mClient.listDirectories();
for (int i = 0; i < files.length; i++) {
boolean check = false;
for (FTPFile ftpFile : Ftplist) {
if (ftpFile.getName().equals(files[i].getName()))
check = true;
}
if (!check)
Ftplist.add(files[i]);
}
runOnUiThread(new Runnable() {
@Override
public void run() {
recyclerView.setAdapter(adapter1);
adapter1.notifyDataSetChanged();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
break;
case R.id.nav_LocalDirectory:
recyclerView.setAdapter(adapter);
adapter.notifyDataSetChanged();
break;
}
return true;
}
});
progressDialog=new ProgressDialog(this);
progressDialog.setTitle("正在复制...");
InitData();
recyclerView=(RecyclerView)findViewById(R.id.file_list);
LinearLayoutManager layoutManager=new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
adapter=new FileAdapter(FileList);
adapter1=new FtpFileAdapter(Ftplist,this);
recyclerView.setAdapter(adapter);
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction("com.app.bhk.connected");
connectBroadcastReceiver=new ConnectBroadcastReceiver();
registerReceiver(connectBroadcastReceiver,intentFilter);
}
private void getPermissions(){
if(ContextCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
}
if(ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_COARSE_LOCATION)!=PackageManager.PERMISSION_GRANTED)
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},2);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode){
case 1:
if(grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED){
}else{
Toast.makeText(this,"没有权限打开相册",Toast.LENGTH_SHORT).show();
}
case 2:
}
}
private void InitData(){
File file =Environment.getExternalStorageDirectory();
FileList=new ArrayList<File>( Arrays.asList(file.listFiles()));
}
@Override
public void onBackPressed(){
if(recyclerView.getAdapter()==adapter)
{
adapter.LastFile();
}
else{
adapter1.LastFile();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main,menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
switch (item.getItemId()){
case R.id.copy:
if(FileUtil.getSaveFile()!=null) {
progressDialog.show();
new Thread(new Runnable() {
@Override
public void run() {
if(recyclerView.getAdapter()==adapter) {
FileUtil.Copy(FileUtil.getSaveFile(), adapter.getCurrentFile());
runOnUiThread(new Runnable() {
@Override
public void run() {
progressDialog.dismiss();
FileList.add(FileUtil.getSaveFile());
adapter.notifyDataSetChanged();
}
});
}else{
try {
FtpUtil.uploadFile(FileUtil.getSaveFile().getPath());
Ftplist.clear();
Ftplist.addAll( Arrays.asList(FtpUtil.getmClient().listFiles()));
runOnUiThread(new Runnable() {
@Override
public void run() {
progressDialog.dismiss();
adapter1.notifyDataSetChanged();
}
});
}catch (IOException e){
e.printStackTrace();
}
}
}
}).start();
}
break;
case android.R.id.home:
mDrawerLayout.openDrawer(GravityCompat.START);
break;
case R.id.copy_from_server:
if(recyclerView.getAdapter()==adapter&&FtpUtil.getFtpFile()!=null) {
progressDialog.show();
new Thread(new Runnable() {
@Override
public void run() {
try {
boolean result= FtpUtil.downloadSingle(adapter.getCurrentFile(), FtpUtil.getFtpFile());
runOnUiThread(new Runnable() {
@Override
public void run() {
progressDialog.dismiss();
}
});
if(result) {
runOnUiThread(new Runnable() {
@Override
public void run() {
FileList.clear();
FileList.addAll(Arrays.asList(adapter.getCurrentFile().listFiles()));
adapter.notifyDataSetChanged();
}
});
}else{
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this,"复制失败",Toast.LENGTH_SHORT).show();
}
});
}
}catch (IOException e){
e.printStackTrace();
}
}
}).start();
}
break;
}
return true;
}
class ConnectBroadcastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction().toString()){
case "com.app.bhk.connected":
Toast.makeText(MainActivity.this,"连接上了",Toast.LENGTH_SHORT).show();
new Thread(new Runnable() {
@Override
public void run() {
mClient=FtpUtil.getmClient();
adapter1.setFtpClient(mClient);
}
}).start();
break;
}
}
}
}
其中:NavigationView是右滑的导航栏。需要提醒的一点是所有的关于ftp服务器的操作都必须要在子线程内进行否则就会报错的,毕竟这是网络操作,为了避免主线程阻塞是需要在子线程操作的。这里我写了广播接受器,用来接收登录成功的广播以便获取FTPClient对象。关于退回上一级目录的功能我是用重写手机的返回键实现的,具体看onBackPressed()这个方法。
下面看看两个适配器类:
public class FtpFileAdapter extends RecyclerView.Adapter<FtpFileAdapter.ViewHolder> {
private List<FTPFile> FtpFilelist;
private StringBuffer CurrentFile=new StringBuffer();
private FTPClient ftpClient;
private MainActivity mainActivity;
public void LastFile(){//切换到父目录
new Thread(new Runnable() {
@Override
public void run() {
try {
int i= CurrentFile.toString().lastIndexOf('/');
if(i>=0) {
CurrentFile.delete(i, CurrentFile.length());
boolean f=FtpUtil.getmClient().changeToParentDirectory();
Log.d("test1234"," "+f+" "+CurrentFile);
}
FTPFile files[]=FtpUtil.getmClient().listFiles();
FtpFilelist.clear();
FtpFilelist.addAll(Arrays.asList(files));
mainActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
return;
}
});
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}
public FTPClient getFtpClient() {
return ftpClient;
}
public void setFtpClient(FTPClient ftpClient) {
this.ftpClient = ftpClient;
}
static class ViewHolder extends RecyclerView.ViewHolder{
private ImageView File_imageview;
private TextView File_textView;
public ViewHolder(View v){
super(v);
File_imageview=(ImageView) v.findViewById(R.id.file_image);
File_textView=(TextView) v.findViewById(R.id.file_name);
}
}
public FtpFileAdapter(List<FTPFile> list,MainActivity activity){
FtpFilelist=list;
this.mainActivity=activity;
}
@Override
public int getItemCount() {
return FtpFilelist.size();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.file_item1,parent,false);
final ViewHolder viewHolder=new ViewHolder(view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final FTPFile file=FtpFilelist.get(viewHolder.getAdapterPosition());
if(file.isDirectory()) {
new Thread(new Runnable() {
@Override
public void run() {
try {
CurrentFile.append("/"+file.getName());
boolean f= FtpUtil.getmClient().changeWorkingDirectory(CurrentFile.toString());
//更新文件列表
FTPFile files[] = FtpUtil.getmClient().listFiles();
FtpFilelist.clear();
FtpFilelist.addAll(Arrays.asList(files));
Log.d("test123", "11212"+f+" "+CurrentFile);
mainActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
return;
}
});
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
Log.d("ftpfile1","good");
// Toast.makeText(getContext(),"hello",Toast.LENGTH_SHORT).show();
}
}
});
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
//创建弹出式菜单对象(最低版本11)
PopupMenu popup = new PopupMenu(getContext(), v);//第二个参数是绑定的那个view
//获取菜单填充器
final MenuInflater inflater = popup.getMenuInflater();
//填充菜单
inflater.inflate(R.menu.operation_ftp, popup.getMenu());
//绑定菜单项的点击事件
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
FTPFile file=FtpFilelist.get(viewHolder.getAdapterPosition());
switch (item.getItemId()){
case R.id.copy_operate:
FtpUtil.setFtpFile(file);
break;
}
//notifyDataSetChanged();
return true;
}
});
popup.show();
//显示(这一行代码不要忘记了)
return true;
}
});
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
FTPFile ftpFile=FtpFilelist.get(position);
holder.File_textView.setText(ftpFile.getName());
if(ftpFile.isDirectory()){
holder.File_imageview.setImageDrawable(getContext().getDrawable(R.drawable.directory));
}else{
holder.File_imageview.setImageDrawable(getContext().getDrawable(R.drawable.file));
}
}
}
public class FileAdapter extends RecyclerView.Adapter<FileAdapter.ViewHolder> {
private List<File> arraylist;
public File getCurrentFile() {
return CurrentFile;
}
private File CurrentFile=Environment.getExternalStorageDirectory();//当前的文件夹
static class ViewHolder extends RecyclerView.ViewHolder{
ImageView imageView;
TextView textView;
public ViewHolder(View v){
super(v);
imageView=(ImageView)v.findViewById(R.id.file_image);
textView=(TextView)v.findViewById(R.id.file_name);
}
}
public FileAdapter(List<File> arraylist){
this.arraylist=arraylist;
}
@Override
public FileAdapter.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.file_item,parent,false);
final ViewHolder holder =new ViewHolder(view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
File file=arraylist.get(holder.getAdapterPosition());
if(file.isDirectory()) {
CurrentFile=file;
arraylist.clear();
arraylist.addAll(Arrays.asList(file.listFiles()));
notifyDataSetChanged();
}
}
});
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
//创建弹出式菜单对象(最低版本11)
PopupMenu popup = new PopupMenu(getContext(), v);//第二个参数是绑定的那个view
//获取菜单填充器
final MenuInflater inflater = popup.getMenuInflater();
//填充菜单
inflater.inflate(R.menu.operation, popup.getMenu());
//绑定菜单项的点击事件
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
File file=arraylist.get(holder.getAdapterPosition());
switch (item.getItemId()){
case R.id.copy_operate:
FileUtil.setSaveFile(file);
break;
case R.id.delete_operate:
arraylist.remove(file);
file.delete();
break;
}
notifyDataSetChanged();
return true;
}
});
popup.show();
//显示(这一行代码不要忘记了)
return true;
}
});
return holder;
}
@Override
public void onBindViewHolder(@NonNull FileAdapter.ViewHolder holder, int position) {
File file=arraylist.get(position);
holder.textView.setText(file.getName());
if(file.isFile()){
holder.imageView.setImageDrawable(getContext().getDrawable(R.drawable.file));
}else{
holder.imageView.setImageDrawable(getContext().getDrawable(R.drawable.directory));
}
}
@Override
public int getItemCount() {
return arraylist.size();
}
public void LastFile(){
if (!CurrentFile.equals(Environment.getExternalStorageDirectory())){
CurrentFile=CurrentFile.getParentFile();
arraylist.clear();
arraylist.addAll(Arrays.asList(CurrentFile.listFiles()));
notifyDataSetChanged();
}
}
}
在这两个适配器类里我都定义了一个CurrentFile用来保存当前目录地址,方便回到上一个目录,以及下载文件和上传文件的时的操作。
本地文件适配器主要编写了长按复制以及删除的功能。复制功能大概实现逻辑是,先把想要复制的文件存放到FileUtil中然后到了想要粘贴的目录后再进行IO操作(我的粘贴功能写在menu的触发事件里),如果是要复制到服务器上就必须使用相关FtpUtil的上传方法了,这里就需要在主活动里通过对当前recyclerview的适配器进行判断以便判断是复制到服务器还是本地以决定作何操作。删除就很简单了不赘述。
服务器文件适配器主要编写了长按复制功能,这里主要是复制到本地文件。和本地差不多不过是先保存到FtpUtil然后切换到本地文件到想要的目录进行调用FTPUtil的方法就可以了。
源码有人需要可以评论。看情况更新文章。
项目地址:https://github.com/DhyanaCoder/FileFtp
给本文章或者github项目点个赞!(*^__^*) 嘻嘻……