关于BaseAdapter多类型页面复用问题

本文深入探讨了自定义适配器在Android应用开发中的使用,包括其内部实现机制、复用策略及关键代码解析,并提出优化建议以提升性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 MyAdapter

/**
 * @author ruyi
 * @version 创建时间:2016年5月12日 下午4:42:11
 * 
 */
public class MyAdapter extends BaseAdapter {
private Context mContext;
private ArrayList<Person> mList = new ArrayList<Person>();


public MyAdapter(Context context) {
mContext = context;
}


public void setData(ArrayList<Person> list) {
mList = list;
}


@Override
public int getCount() {
return mList == null ? 0 : mList.size();
}


@Override
public Object getItem(int position) {
return mList == null ? null : mList.get(position);
}


@Override
public long getItemId(int position) {
return position;
}


@Override
public int getViewTypeCount() {
return 2;
}


@Override
public int getItemViewType(int position) {
Person p = mList.get(position);
return p == null ? 3 : p.type;
}


@Override
public View getView(int position, View convertView, ViewGroup parent) {
Person p = mList.get(position);


ViewHolder1 viewHolder1 = null;
ViewHolder2 viewHolder2 = null;

if (convertView == null) {
if (getItemViewType(position) == 0) {
Log.i("ASDFGHJKL", "NULL ----------------0");
convertView = LayoutInflater.from(mContext).inflate(R.layout.item_1,null);
viewHolder1 = new ViewHolder1(convertView);
convertView.setTag(0x7f060001,viewHolder1);
} else{
Log.i("ASDFGHJKL", "NULL ----------------1");
convertView = LayoutInflater.from(mContext).inflate(R.layout.item_2,null);
viewHolder2 = new ViewHolder2(convertView);
convertView.setTag(0x7f060000,viewHolder2);
}

} else {
if (getItemViewType(position) == 0) {
Log.i("ASDFGHJKL", "TRUE ----------------0");
viewHolder1 = (ViewHolder1) convertView.getTag(0x7f060001);
}else{
Log.i("ASDFGHJKL", "TRUE ----------------1");
viewHolder2 = (ViewHolder2) convertView.getTag(0x7f060000);
}
}

if (getItemViewType(position) == 0) {
viewHolder1.name.setText(p.name);
}else{
viewHolder2.name.setText(p.name);
viewHolder2.age.setText(p.age);
}


return convertView;
}


public class ViewHolder1 {
private TextView name;


public ViewHolder1(View view) {
name = (TextView) view.findViewById(R.id.name_1);
}
}


public class ViewHolder2 {
private TextView name;
private TextView age;


public ViewHolder2(View view) {
name = (TextView) view.findViewById(R.id.name_2);
age = (TextView) view.findViewById(R.id.age_2);
}
}


}


第一点:对convertView.setTag(0x7f060000,viewHolder2) 第一个int 参数的限制问题

 public void setTag(int key, final Object tag) {
        // If the package id is 0x00 or 0x01, it's either an undefined package
        // or a framework id
        if ((key >>> 24) < 2) {
            throw new IllegalArgumentException("The key must be an application-specific "
                    + "resource id.");
        }


        setKeyedTag(key, tag);
    }


第二点:关于复用的总结

当第一屏全为类型0,继续向下滑动时,类型0的可复用已消失的类型0的view

当第一屏全为类型0,继续向下滑动时,类型1无法复用已消失的类型0的view,此时判断convertView也是null,也就是类型1可复用的view为空,即使类型0的可复用view存在

<think>我们面临的问题:在BaseAdapter的ItemView中有一个按钮,点击按钮需要携带参数跳转到另一个Activity,并在该Activity修改数据后返回,将修改后的数据带回给BaseAdapter所在的Activity(或Fragment)。 分析: 1. BaseAdapter通常用于ListView、GridView等,而Adapter本身不持有Activity的上下文,也不具备启动Activity并接收结果的能力。 2. 我们需要在Adapter中通过一个上下文(Activity)来启动另一个Activity,并且需要处理返回结果。 解决方案: 我们可以利用Activity Result API(推荐)或者传统的startActivityForResult方式(已过时,但在一些老项目中仍使用)。考虑到Activity Result API更现代且生命周期安全,我们使用它。 步骤: 1. 在Adapter中,我们通过构造函数传入一个Activity(或Fragment)的引用,以便启动Activity。 2. 在ItemView的按钮点击事件中,使用传入的Activity来注册并启动一个ActivityResultLauncher(注意:不能在Adapter中直接注册,因为每次getView都可能被调用多次,导致重复注册)。 3. 但是,注意:我们不应该在Adapter中注册ActivityResultLauncher,因为Adapter可能会被多次创建,而且注册应该只进行一次。所以,我们可以在创建Adapter的Activity(或Fragment)中注册启动器,然后通过回调传递给Adapter。 因此,我们设计如下: 方案A(推荐):在Activity(或Fragment)中注册启动器,然后通过接口将启动Activity的能力传递给Adapter,并在Adapter中通过该接口启动Activity。同时,定义一个回调接口用于接收返回的数据。 方案B:在Adapter中持有Activity,然后在Activity中定义一个公共的ActivityResultLauncher,让Adapter可以直接使用该启动器。但这样耦合较高,不推荐。 我们采用方案A: 步骤详解: 1. 在Activity(或Fragment)中: - 注册一个ActivityResultLauncher,用于启动目标Activity并处理返回结果。 - 定义一个接口,该接口包含一个启动目标Activity的方法(用于传递给Adapter)。 - 在创建Adapter时,实现该接口并传递给Adapter。 2. 在Adapter中: - 通过构造函数接收这个接口的实现。 - 在ItemView的按钮点击事件中,调用接口的方法来启动目标Activity,并传递所需参数。 3. 在目标Activity中修改数据后,将修改后的数据通过Intent返回。 4. 回到原Activity,在ActivityResultLauncher的回调中处理返回的数据,并更新Adapter的数据集。 注意:如果返回的数据需要更新具体的某个Item,那么我们需要能够定位到该Item(例如通过位置或唯一标识)。因此,在启动目标Activity时,除了传递需要修改的数据,还需要传递该Item的位置或唯一标识(比如在数据模型中有id),这样在返回时才能知道更新哪个Item。 具体代码实现: 步骤1:在Activity中注册启动器并定义接口 我们假设要启动的Activity是EditActivity,它用于编辑一个字符串。我们传递的数据是一个字符串和一个位置(或id),返回时我们同样需要位置(或id)来定位。 在Activity中(假设是MainActivity):</think>### 在 BaseAdapter 中实现 Item 点击跳转并带回修改数据 在 Android 开发中,通过 BaseAdapter 实现 Item 点击跳转并带回修改数据需要解决两个核心问题:**参数传递**和**结果回调**。以下是完整实现方案: --- #### 解决方案架构 ```mermaid graph TD A[Activity/Fragment] -->|1. 创建Adapter| B[BaseAdapter] B -->|2. 设置监听接口| C[Item点击事件] C -->|3. 携带参数| D[启动目标Activity] D -->|4. 修改数据| E[返回结果] E -->|5. 回调接口| A A -->|6. 更新Adapter| B ``` --- #### 实现步骤(Kotlin 示例) ##### 1. 定义回调接口 ```kotlin interface AdapterCallback { fun onItemClick(data: String, position: Int) // 点击事件回调 fun onDataUpdated(data: String, position: Int) // 数据更新回调 } ``` ##### 2. 在 BaseAdapter 中实现点击逻辑 ```kotlin class MyAdapter( private val context: Context, private val dataList: MutableList<String>, private val callback: AdapterCallback // 通过构造函数传入回调接口 ) : BaseAdapter() { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val view = convertView ?: LayoutInflater.from(context).inflate(R.layout.item_layout, parent, false) val button = view.findViewById<Button>(R.id.btn_item) val itemData = dataList[position] button.text = itemData button.setOnClickListener { // 通过回调接口通知Activity启动新页面 callback.onItemClick(itemData, position) } return view } // 其他必要方法省略... } ``` ##### 3. 在 Activity/Fragment 中实现回调 ```kotlin class MainActivity : AppCompatActivity(), AdapterCallback { private lateinit var adapter: MyAdapter private val dataList = mutableListOf("数据1", "数据2", "数据3") // 注册Activity结果启动器 private val resultLauncher = registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) { result -> if (result.resultCode == RESULT_OK) { val updatedData = result.data?.getStringExtra("UPDATED_DATA") val position = result.data?.getIntExtra("POSITION", -1) ?: -1 if (position != -1 && updatedData != null) { // 更新数据并通知Adapter dataList[position] = updatedData adapter.notifyDataSetChanged() // 可选:调用AdapterCallback通知更新 onDataUpdated(updatedData, position) } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 初始化Adapter并传入当前Activity作为回调 adapter = MyAdapter(this, dataList, this) listView.adapter = adapter } // 实现AdapterCallback接口 override fun onItemClick(data: String, position: Int) { val intent = Intent(this, EditActivity::class.java).apply { putExtra("INITIAL_DATA", data) putExtra("POSITION", position) // 传递位置信息 } resultLauncher.launch(intent) } override fun onDataUpdated(data: String, position: Int) { // 可在此处添加额外处理逻辑 Toast.makeText(this, "位置$position 已更新: $data", Toast.LENGTH_SHORT).show() } } ``` ##### 4. 在目标 Activity 中处理数据返回 ```kotlin class EditActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_edit) val initialData = intent.getStringExtra("INITIAL_DATA") val position = intent.getIntExtra("POSITION", -1) editText.setText(initialData) btnSave.setOnClickListener { val updatedData = editText.text.toString() val resultIntent = Intent().apply { putExtra("UPDATED_DATA", updatedData) putExtra("POSITION", position) // 返回原始位置 } setResult(RESULT_OK, resultIntent) finish() } } } ``` --- #### 关键点解析 1. **回调接口设计** - 通过接口解耦 Adapter 和 Activity[^4] - Activity 实现接口处理具体业务逻辑 2. **位置信息传递** - 启动时传递 `position` 确保能定位到具体 Item - 返回时带回 `position` 实现精准更新 3. **数据更新流程** ```mermaid sequenceDiagram participant A as Activity participant B as BaseAdapter participant C as EditActivity A->>B: 创建Adapter(传入回调接口) B->>A: Item点击事件(onItemClick) A->>C: 启动EditActivity(携带数据和位置) C->>A: 返回修改结果(setResult) A->>B: 更新数据源并notifyDataSetChanged ``` 4. **生命周期处理** - 使用 `registerForActivityResult` 自动处理配置变更 - 避免在 Adapter 中直接持有 Activity 引用防止内存泄漏 --- #### 优化建议 1. **使用 ViewModel 管理数据** ```kotlin // 在ViewModel中管理数据 class ListViewModel : ViewModel() { val dataList = MutableLiveData<MutableList<String>>() } // Activity中观察数据变化 viewModel.dataList.observe(this) { list -> adapter.updateData(list) } ``` 2. **封装可复用 BaseAdapter** ```kotlin abstract class BaseRecyclerAdapter<T>( private var dataList: List<T>, private val callback: AdapterCallback? ) : RecyclerView.Adapter<BaseViewHolder>() { fun updateData(newData: List<T>) { dataList = newData notifyDataSetChanged() } // 在ViewHolder中设置点击事件 override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { holder.itemView.setOnClickListener { callback?.onItemClick(dataList[position], position) } } } ``` 3. **传递复杂对象** ```kotlin // 实现Parcelable @Parcelize data class User(val id: Int, val name: String) : Parcelable // 传递对象 intent.putExtra("USER_DATA", userObj) // 接收对象 val user = intent.getParcelableExtra<User>("USER_DATA") ``` --- #### 常见问题解决 **Q1: 为什么返回后列表没有刷新?** - 确保调用了 `notifyDataSetChanged()` - 检查数据源是否被正确更新 - 验证返回的 position 是否有效 **Q2: 如何防止屏幕旋转导致数据丢失?** - 使用 ViewModel 保存数据 - 在 onSaveInstanceState 中保存关键数据 - Activity Result API 自动处理配置变更[^4] **Q3: 如何实现多类型 Item 的不同点击事件?** ```kotlin // 在Adapter中定义多回调接口 interface MultiCallback { fun onEditClick(position: Int) fun onDeleteClick(position: Int) fun onDetailClick(position: Int) } // 在getView中根据View类型设置不同监听器 when (itemViewType) { TYPE_EDIT -> buttonEdit.setOnClickListener { callback?.onEditClick(position) } TYPE_DELETE -> buttonDelete.setOnClickListener { callback?.onDeleteClick(position) } } ``` --- ### 总结 通过回调接口 + Activity Result API 的组合方案,实现了: 1. Adapter 与 Activity 的解耦 2. 精准的数据传递和位置定位 3. 修改数据的双向传递 4. 列表的高效更新 这种方法符合 Android 架构组件的最佳实践,解决了传统方案中请求码管理复杂和回调混乱的问题[^1][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值