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

RecyclerView子项按钮跳转SecondFragment及ID识别问题求助

问题描述

我在RecyclerView的子项布局RecycleItem.xml中设置了ID为detalhes的按钮,希望点击该按钮时从FirstFragment跳转到SecondFragment,展示对应API图片的详情。我已将RecyclerView添加至FirstFragment,但在FirstFragment.kt中调用该按钮ID时,程序提示无法识别此ID。我已实现自定义CustomAdapter用于加载API数据,相关布局与代码如下:

RecycleItem.xml

<TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:gravity="center_horizontal|center_vertical"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/imageView"
        app:layout_constraintTop_toBottomOf="@+id/imageView"
        tools:text="@tools:sample/lorem" />


    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal|center_vertical"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:src="@drawable/ic_launcher_background" />


    <Button
        android:id="@+id/detalhes"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="Detalhes"
        app:layout_constraintTop_toBottomOf="@id/imageView"/>

FirstFragment.xml

<androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycleView"
            android:layout_width="200dp"
            android:layout_height="match_parent"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_constraintTop_toBottomOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        </androidx.constraintlayout.widget.ConstraintLayout>

FirstFragment.kt

class FirstFragment : Fragment() {

    //private var _binding: FragmentFirstBinding? = null
    private lateinit var _binding: FragmentFirstBinding
    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!
    private var adapter:CustomAdapter? = null
    private val viewModel: BobBurgerViewModel by viewModels()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        _binding = FragmentFirstBinding.inflate(inflater, container, false)
        return binding.root

    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.getData()
    }


    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        adapter = CustomAdapter(requireContext())
        binding.recycleView.adapter = adapter
        lifecycleScope.launch(Dispatchers.Main) {
            viewModel.rocketsFlow.collectLatest {
                adapter?.items = it.body() ?: emptyList()
            }
        }
}

CustomAdapter

class CustomAdapter(private val context: Context) :
    RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
    inner class ViewHolder(val binding: RecyclerItemBinding) : RecyclerView.ViewHolder(binding.root)

    private val diffCallback = object : DiffUtil.ItemCallback<Characters>() {
        override fun areItemsTheSame(oldItem: Characters, newItem: Characters): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: Characters, newItem: Characters): Boolean {
            return oldItem == newItem
        }
    }
    private val differ = AsyncListDiffer(this, diffCallback)
    var items: List<Characters>
        get() = differ.currentList
        set(value) {
            differ.submitList(value)
        }

    override fun getItemCount() = items.size
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(
            RecyclerItemBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )
    }


    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.binding.apply {
            val item = items[position]
            textView.text = item.name

            Glide.with(context)
                .load(item.image)
                .into(imageView)
        }
        holder.binding.imageView

    }
}

解决方法

无法在FirstFragment中直接调用detalhes按钮ID的原因是:该按钮属于RecyclerView的子项布局,是每个列表项的一部分,并非FirstFragment布局的直接子View,因此FragmentFirstBinding中不会包含这个ID的引用。正确的做法是在CustomAdapter中处理按钮点击事件,通过回调通知FirstFragment执行跳转逻辑。

步骤1:给CustomAdapter添加点击回调

修改CustomAdapter,添加点击回调并绑定按钮点击事件:

class CustomAdapter(
    private val context: Context,
    private val onDetailsClick: (Characters) -> Unit // 新增回调Lambda
) : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
    inner class ViewHolder(val binding: RecyclerItemBinding) : RecyclerView.ViewHolder(binding.root)

    // 保留原有diffCallback、differ、items相关代码

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.binding.apply {
            val item = items[position]
            textView.text = item.name

            Glide.with(context)
                .load(item.image)
                .into(imageView)

            // 绑定detalhes按钮点击事件
            detalhes.setOnClickListener {
                onDetailsClick(item)
            }
        }
    }
}

步骤2:在FirstFragment中处理跳转逻辑

修改FirstFragment.kt,创建Adapter时传入回调,在回调中执行跳转:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    // 创建Adapter并传入点击回调
    adapter = CustomAdapter(requireContext()) { character ->
        // 方式1:使用Navigation组件跳转(推荐)
        val action = FirstFragmentDirections.actionFirstToSecond(character)
        findNavController().navigate(action)

        // 方式2:使用FragmentManager跳转
        // val secondFragment = SecondFragment()
        // val bundle = Bundle().apply { putParcelable("selected_character", character) }
        // secondFragment.arguments = bundle
        // parentFragmentManager.beginTransaction()
        //     .replace(R.id.fragment_container, secondFragment)
        //     .addToBackStack(null)
        //     .commit()
    }
    binding.recycleView.adapter = adapter
    lifecycleScope.launch(Dispatchers.Main) {
        viewModel.rocketsFlow.collectLatest {
            adapter?.items = it.body() ?: emptyList()
        }
    }
}

步骤3:在SecondFragment中接收数据

在SecondFragment中获取传递的Characters数据并展示:

class SecondFragment : Fragment() {
    private lateinit var binding: FragmentSecondBinding

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentSecondBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // 获取传递的数据
        val character = arguments?.getParcelable<Characters>("selected_character")
        // 如果用Navigation Safe Args:
        // val args = SecondFragmentArgs.fromBundle(requireArguments())
        // val character = args.character

        // 更新UI展示详情
        character?.let {
            binding.detailName.text = it.name
            Glide.with(requireContext())
                .load(it.image)
                .into(binding.detailImage)
        }
    }
}

内容的提问来源于stack exchange,提问作者João Alves

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 13:05:07