Android如何不使用ListView/RecyclerView遍历数据动态生成Item View
我尝试为列表中的每个POJO数据对象实例生成对应的XML视图,实现类似ListView的条目渲染效果,但实际并不使用ListView组件,而是将条目视图添加到空LinearLayout容器中;该容器位于ScrollView内部,放置在Fragment其他内容的下方。
单条目布局(item.xml)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > <TextView android:id="@+id/item_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginLeft="8dp" android:layout_marginTop="0dp" android:text="ITEM_LABEL" android:textSize="16sp" /> <TextView android:id="@+id/item_text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginLeft="8dp" android:layout_marginTop="4dp" android:text="ITEM_TEXT_1" android:textSize="14sp" /> <TextView android:id="@+id/item_text2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginLeft="8dp" android:layout_marginTop="4dp" android:text="ITEM_TEXT_2" android:textSize="14sp" /> </LinearLayout>
原有Fragment填充逻辑
public class MyFragment extends Fragment { // 省略模板代码 // [...] @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // 加载Fragment布局 View view = inflater.inflate(R.layout.my_fragment, container, false); // 通过Room+LiveData从数据库查询POJO列表 LiveData<List<MyItem>> items_list = itemsViewModel.getItemsByParentId(some_id); LinearLayout items_list_layout = view.findViewById(R.id.items_list_layout); items_list.observe(getActivity(), new Observer<List<MyItem>>() { @Override public void onChanged(List<MyItem> items) { // 清空原有视图,避免切换数据源残留旧条目 objs_list_layout.removeAllViews() for (MyItem item : items) { // 加载item布局 LinearLayout obj_item = (LinearLayout) View.inflate(view.getContext(), R.layout.item, items_list_layout); // 尝试绑定item数据 TextView item_label = obj_item.findViewById(R.id.item_label); item_label.setText(item.item_label); TextView item_text1 = obj_item.findViewById(R.id.item_text1); item_text1.setText(item.item_text1); TextView item_text2 = obj_item.findViewById(R.id.item_text2); item_text2.setText(item.item_text2); } } }); } }
异常表现
- 最顶部首个填充的item视图的TextView最终显示为列表最后一个条目的数据,和位置不匹配
- 其余填充的条目视图未做数据修改,始终显示XML模板中硬编码的
ITEM_LABEL、ITEM_TEXT1、ITEM_TEXT2默认字符串 - 条目布局填充数量和预期一致,等于
items.size()
需求
实现每个填充的item视图绑定对应条目的属性数据,完全不使用ListView/RecyclerView,保证ScrollView内除了列表LinearLayout之外,其他异质视图可以正常展示。
核心错误是View.inflate()方法的使用逻辑理解有误:
当inflate方法的第三个参数传入非空的父容器时,方法会自动将加载的子布局添加到父容器中,此时方法的返回值不是新加载的item根布局,而是传入的父容器对象本身。
因此循环中每次拿到的obj_item其实都是外层的items_list_layout容器,每次调用findViewById只会匹配到容器内第一个对应ID的控件——也就是第一个加载的item里的三个TextView。整个循环执行过程中,你只是反复覆盖第一个item的文本内容,最终第一个item显示最后一条数据的值,后续自动添加的其他item从来没被绑定过数据,自然一直显示XML里的默认硬编码文本。只有列表长度为1时,不存在后续条目,看起来才是正常的。
另外代码里存在笔误:清空容器时写的objs_list_layout.removeAllViews()和之前定义的变量名items_list_layout不一致,会直接触发编译错误。
调整inflate方法的参数,确保每次循环都能拿到独立的item布局对象,绑定数据后再添加到容器中即可,推荐写法如下:
@Override public void onChanged(List<MyItem> items) { // 修正笔误,先清空容器原有内容 items_list_layout.removeAllViews(); for (MyItem item : items) { // 第三个参数传false:加载子布局但不自动添加到父容器,此时返回值就是item的根布局 LinearLayout obj_item = (LinearLayout) LayoutInflater.from(requireContext()) .inflate(R.layout.item, items_list_layout, false); // 给当前item的控件绑定数据 TextView item_label = obj_item.findViewById(R.id.item_label); item_label.setText(item.item_label); TextView item_text1 = obj_item.findViewById(R.id.item_text1); item_text1.setText(item.item_text1); TextView item_text2 = obj_item.findViewById(R.id.item_text2); item_text2.setText(item.item_text2); // 数据绑定完成后,手动将item添加到容器中 items_list_layout.addView(obj_item); } }
额外优化建议
观察LiveData时不要传getActivity()作为生命周期所有者,替换为getViewLifecycleOwner(),可以避免Fragment视图销毁后回调还在执行,触发内存泄漏或空指针异常,即observe调用改为:
items_list.observe(getViewLifecycleOwner(), new Observer<List<MyItem>>() { // 原有逻辑不变 });
内容的提问来源于stack exchange,提问作者SebasSBM

