先上效果圖:
本文的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項目點個贊!(*^__^*) 嘻嘻……