如何在单个Activity中实现多级列表遍历,点击返回键回到上一级列表
Alright, let's build this single-Activity nested file/folder browser that loads dynamic content from an API. I'll walk you through a practical, step-by-step implementation that handles navigation, dynamic lists, and document viewing seamlessly.
First, let's lay the groundwork. We need three key things to make this work:
- A data class to distinguish between folders and documents
- A navigation stack to track each folder level (so we can go back with the back button)
- A RecyclerView to display the dynamic list (it's perfect for variable item counts)
Here's a simple data model to start with:
enum class ItemType { FOLDER, DOCUMENT } data class FileSystemItem( val id: String, val name: String, val type: ItemType, val contentUrl: String? = null // Only filled in for documents )
We'll use a RecyclerView with an adapter that handles both folder and document items. The adapter will trigger different actions based on the item type when clicked.
Step 1: RecyclerView Adapter
Create an adapter that adjusts the UI for folders vs documents (you can use different layouts or just swap icons/text):
class FileSystemAdapter( private val itemList: List<FileSystemItem>, private val onItemClick: (FileSystemItem) -> Unit ) : RecyclerView.Adapter<FileSystemAdapter.ViewHolder>() { inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind(item: FileSystemItem) { itemView.tv_item_name.text = item.name // Set distinct icons for folders and documents itemView.iv_item_icon.setImageResource( if (item.type == ItemType.FOLDER) R.drawable.ic_folder else R.drawable.ic_document ) itemView.setOnClickListener { onItemClick(item) } } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_file_system, parent, false) return ViewHolder(view) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.bind(itemList[position]) } override fun getItemCount() = itemList.size }
Step 2: Load Initial & Folder Content
In your Activity, initialize the navigation stack and load content from your API. We'll push each new folder's content to the stack as we navigate deeper:
class FileBrowserActivity : AppCompatActivity() { private lateinit var recyclerView: RecyclerView private lateinit var adapter: FileSystemAdapter private val navigationStack = Stack<List<FileSystemItem>>() private val progressBar: ProgressBar by lazy { findViewById(R.id.progress_bar) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_file_browser) recyclerView = findViewById(R.id.rv_file_system) recyclerView.layoutManager = LinearLayoutManager(this) // Load root folder content on launch loadFolderContent("root_folder_id") } private fun loadFolderContent(folderId: String) { progressBar.visibility = View.VISIBLE // Replace this with your actual API call (example using Retrofit) apiService.getFolderContents(folderId).enqueue(object : Callback<List<FileSystemItem>> { override fun onResponse(call: Call<List<FileSystemItem>>, response: Response<List<FileSystemItem>>) { progressBar.visibility = View.GONE response.body()?.let { items -> // Update stack: push current items if we're navigating deeper if (navigationStack.isNotEmpty()) { navigationStack.push(items) } else { // Initialize stack with root content navigationStack.add(items) } updateRecyclerView(items) // Update toolbar title to current folder name (adjust as needed) supportActionBar?.title = getFolderName(folderId) } } override fun onFailure(call: Call<List<FileSystemItem>>, t: Throwable) { progressBar.visibility = View.GONE // Handle API errors (show toast, add retry option) Toast.makeText(this@FileBrowserActivity, "Failed to load content", Toast.LENGTH_SHORT).show() } }) } private fun updateRecyclerView(items: List<FileSystemItem>) { adapter = FileSystemAdapter(items) { item -> when (item.type) { ItemType.FOLDER -> { // Navigate into the folder loadFolderContent(item.id) } ItemType.DOCUMENT -> { // Open document viewer showDocument(item.contentUrl) } } } recyclerView.adapter = adapter } }
To go back to the previous folder, override the onBackPressed() method to pop the navigation stack and reload the previous level's content:
override fun onBackPressed() { if (navigationStack.size > 1) { // Pop current level, load the previous one navigationStack.pop() val previousItems = navigationStack.peek() updateRecyclerView(previousItems) // Update toolbar title to match the previous folder supportActionBar?.title = getPreviousFolderName() } else { // If we're at the root level, exit the activity super.onBackPressed() } }
For viewing documents, you can use a DialogFragment (clean and non-intrusive) or switch visibility between the RecyclerView and a document view (like a WebView) in your Activity. Here's a quick dialog example:
Step 1: Create Document View Dialog
class DocumentViewDialog : DialogFragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.dialog_document_view, container, false) val webView = view.findViewById<WebView>(R.id.web_view) val contentUrl = arguments?.getString("CONTENT_URL") contentUrl?.let { webView.loadUrl(it) } return view } companion object { fun newInstance(contentUrl: String): DocumentViewDialog { val dialog = DocumentViewDialog() val args = Bundle() args.putString("CONTENT_URL", contentUrl) dialog.arguments = args return dialog } } }
Step 2: Show the Dialog from Your Activity
private fun showDocument(contentUrl: String?) { contentUrl ?: return val dialog = DocumentViewDialog.newInstance(contentUrl) dialog.show(supportFragmentManager, "DocumentViewDialog") }
- Error Handling: Add empty state views for when a folder has no content, and retry buttons for failed API calls.
- Configuration Changes: Save the navigation stack state in
onSaveInstanceState()to avoid losing progress on screen rotation. - Performance: If your API returns large datasets, implement pagination to keep the list scrolling smoothly.
内容的提问来源于stack exchange,提问作者VSK Bhargav

