You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在单个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.

Core Setup & Data Model

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
)
Implementing the Dynamic List

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
    }
}
Handling Back Navigation

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()
    }
}
Document View Implementation

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")
}
Key Tips for Polish
  • 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:41:29