当ListView的item为CheckBox,EditText,Spinner的时候处理滑动数据错乱

本文详细介绍了一个自定义Adapter的实现过程,包括如何处理CheckBox、Spinner和EditText等组件的状态保存与更新。此外,还提供了解决EditText弹出输入框及CheckBox与ListView点击事件冲突的方法。

Adapter中的关键代码

package com.listviewtest;

import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.Spinner;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by liuqiang on 2017/3/6.
 */

public class MyAdapter extends BaseAdapter {
    private Context context;
	//spinner数据源
    private String [] items={"请选择","同意","不同意"};
	//保存CheckBox选中状态
    private Map<Integer,Boolean> isCheck=new HashMap<Integer, Boolean>();
	//保存Spinner选中的position
    private Map<Integer,Integer> isSpin=new HashMap<Integer, Integer>(); 
	//保存EditText输入的值
    private Map<Integer,String> isText=new HashMap<Integer, String>();
    private int postion;
    public MyAdapter() {
        super();
    }
    public MyAdapter(Context context) {
        super();
        this.context=context;
		//数据初始化
        for (int i=0;i<20;i++){
            isCheck.put(i,false);
            isSpin.put(i,0);
            isText.put(i,"");
        }
    }

    @Override
    public int getCount() {
        return 20;
    }

    @Override
    public Object getItem(int position) {
        return position;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }
    ViewHolder vh;
    ArrayAdapter<String> adapter;
    @Override
    public View getView(int position, View v, ViewGroup parent) {
        if(v==null){
            v= LayoutInflater.from(context).inflate(R.layout.list_item,null);
            vh=new ViewHolder(v);
			//在控件初始化之后绑定相应事件
			//CheckBox选项发生改变
            vh.checkBox.setOnCheckedChangeListener(new MyOnCheckedChangeListener(vh.checkBox));
			//spinner选中项发生改变
            vh.spinner.setOnItemSelectedListener(new MyOnOnItemSelectedListener(vh.spinner));
            adapter=new ArrayAdapter<String>(context,android.R.layout.simple_spinner_item,items);
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
			//EditText文本框值发生改变
            vh.editText.addTextChangedListener(new MyTextChangedListener(vh.editText));
            v.setTag(vh);
        }else{
            vh= (ViewHolder) v.getTag();
        }
		//在这里给控件添加Tag值为当前ItemView的position
        vh.checkBox.setTag(position);
        vh.spinner.setTag(position);
        vh.editText.setTag(position);
        vh.spinner.setAdapter(adapter);
		//为控件赋值
        vh.checkBox.setChecked(isCheck.get(position));
        vh.spinner.setSelection(isSpin.get(position));
        vh.editText.setText(isText.get(position));
        return v;
    }

    class MyTextChangedListener implements TextWatcher{
        private EditText et_text;

        public MyTextChangedListener(EditText et_text) {
            this.et_text = et_text;
        }

        public MyTextChangedListener() {
            super();
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {

        }

        @Override
        public void afterTextChanged(Editable s) {
		    //获取该控件的tag值,并以次值为key来保存数据
            int index= (int) et_text.getTag();
            isText.put(index,s.toString());
        }
    }

    class MyOnOnItemSelectedListener implements AdapterView.OnItemSelectedListener{
        public MyOnOnItemSelectedListener(Spinner sp_test) {
            this.sp_test = sp_test;
        }
        private Spinner sp_test;

        public MyOnOnItemSelectedListener() {
            super();
        }

        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
		//获取该控件的tag值,并以次值为key来保存数据
            int index= (int) sp_test.getTag();
            isSpin.put(index,position);
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {

        }
    }

    class MyOnCheckedChangeListener implements CompoundButton.OnCheckedChangeListener{
        private CheckBox checkBox;

        public MyOnCheckedChangeListener(CheckBox checkBox) {
            this.checkBox = checkBox;
        }

        public MyOnCheckedChangeListener() {
            super();
        }

        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
		//获取该控件的tag值,并以次值为key来保存数据
            int index= (int) checkBox.getTag();
            isCheck.put(index,isChecked);
        }
    }

    class ViewHolder{
        private Spinner spinner;
        private CheckBox checkBox;
        private EditText editText;
       public ViewHolder (){
           super();
       }
        public ViewHolder (View view){
            spinner= (Spinner) view.findViewById(R.id.sp_test);
            checkBox= (CheckBox) view.findViewById(R.id.ch_test);
            editText= (EditText) view.findViewById(R.id.et_test);
        }
    }

}

通过清单文件和xml属性设置,完成EditText弹出输入框EditText失去焦点需要二次点击情况和CheckBox和listView点击事件冲突问题

//清单文件添加android:windowSoftInputMode属性
 	<activity android:name=".MainActivity"
                  android:windowSoftInputMode="adjustPan|stateHidden"

            >
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
//listView页面添加descendantFocusability属性
	<ListView
        android:id="@+id/list_test"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentTop="true"
        android:descendantFocusability="beforeDescendants"
        android:layout_alignParentStart="true"/>
//item页面
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginTop="5dp"
    android:layout_marginBottom="5dp"
    >

    <CheckBox
        android:id="@+id/ch_test"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="2"
        android:gravity="center"
        />

    <EditText
        android:id="@+id/et_test"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="4"
        />

    <Spinner
        android:id="@+id/sp_test"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="4"
        />






<think>我们已知用户想要在ListView的每个条目中集成Spinner组件。这通常用于在列表的每一行提供一个下拉选择框,比如设置每个条目的状态或属性。注意:在Android中,通常不会直接在ListView的每个条目中使用Spinner,因为Spinner本身也是一个可交互的视图,而在ListView处理交互可能会比较复杂(例如滚动冲突),但技术上是可以实现的。我们需要做的是:1.ListView的每个项创建一个自定义布局,该布局中包含一个Spinner。2.创建一个自定义Adapter(继承自BaseAdapter或ArrayAdapter),在该Adapter中处理每个列表项的视图,包括Spinner数据绑定和事件处理。然而,需要注意的是,在ListView中使用Spinner可能会导致焦点问题,因为Spinner本身会弹出下拉列表,可能会与ListView的滚动事件冲突。因此,我们通常建议使用RecyclerView代替ListView,因为它有更完善的机制来处理复杂的交互。但用户明确要求ListView,所以我们按此实现。步骤:1.创建列表项布局(list_item.xml),包含一个TextView(用于显示条目名称)和一个Spinner(用于选择)。2.创建自定义Adapter,继承自BaseAdapter,并在getView方法中为每个列表项的Spinner设置数据适配器和事件监听。但是,需要特别注意:由于ListView的视图复用机制,Spinner的状态可能会在滚动时出现错乱。因此,我们需要在Adapter中保存每个Spinner的选中状态(根据位置),并在getView中恢复。根据引用[4]提到,我们可以使用PopupWindow实现带搜索的Spinner,但用户只要求Spinner,所以我们先实现基本的Spinner。由于引用中并没有直接给出在ListView中使用Spinner的示例代码,我将按照标准方法编写。以下是一个简化的示例:首先,创建列表项布局(list_item.xml):```xml<?xmlversion="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:padding="10dp"><TextViewandroid:id="@+id/tvItemName"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:text="Item"/><Spinnerandroid:id="@+id/spOptions"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"/></LinearLayout>```然后,创建自定义Adapter:```javapublicclassMyAdapterextendsBaseAdapter{privateContextcontext;privateList<String>itemNames;//列表项的名称privateList<List<String>>spinnerOptions;//每个列表项对应的Spinner选项列表//记录每个位置Spinner选中的位置,避免由于复用导致错乱privateSparseArray<Integer>selectedPositions=newSparseArray<>();publicMyAdapter(Contextcontext,List<String>itemNames,List<List<String>>spinnerOptions){this.context=context;this.itemNames=itemNames;this.spinnerOptions=spinnerOptions;}@OverridepublicintgetCount(){returnitemNames.size();}@OverridepublicObjectgetItem(intposition){returnitemNames.get(position);}@OverridepubliclonggetItemId(intposition){returnposition;}@OverridepublicViewgetView(finalintposition,ViewconvertView,ViewGroupparent){ViewHolderholder;if(convertView==null){convertView=LayoutInflater.from(context).inflate(R.layout.list_item,parent,false);holder=newViewHolder();holder.tvItemName=convertView.findViewById(R.id.tvItemName);holder.spinner=convertView.findViewById(R.id.spOptions);convertView.setTag(holder);}else{holder=(ViewHolder)convertView.getTag();}holder.tvItemName.setText(itemNames.get(position));//设置Spinner的适配器ArrayAdapter<String>spinnerAdapter=newArrayAdapter<>(context,android.R.layout.simple_spinner_item,spinnerOptions.get(position));spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);holder.spinner.setAdapter(spinnerAdapter);//如果有保存的选中位置,则设置if(selectedPositions.get(position)!=null){holder.spinner.setSelection(selectedPositions.get(position));}//设置Spinner的监听器,记录选中的位置holder.spinner.setOnItemSelectedListener(newAdapterView.OnItemSelectedListener(){@OverridepublicvoidonItemSelected(AdapterView<?>parent,Viewview,intpos,longid){//记录该position的Spinner选中的位置selectedPositions.put(position,pos);//这里可以根据需要处理选中事件}@OverridepublicvoidonNothingSelected(AdapterView<?>parent){}});returnconvertView;}staticclassViewHolder{TextViewtvItemName;Spinnerspinner;}}```在Activity中使用:```javapublicclassMainActivityextendsAppCompatActivity{privateListViewlistView;@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);listView=findViewById(R.id.listView);//模拟数据List<String>itemNames=newArrayList<>();List<List<String>>spinnerOptions=newArrayList<>();for(inti=0;i<20;i++){itemNames.add("Item"+(i+1));List<String>options=newArrayList<>();for(intj=0;j<5;j++){options.add("Option"+(j+1));}spinnerOptions.add(options);}MyAdapteradapter=newMyAdapter(this,itemNames,spinnerOptions);listView.setAdapter(adapter);}}```注意:由于每个Spinner都有自己的选项列表,这里用List<List<String>>来存储。同时,我们使用SparseArray来存储每个位置Spinner的选中位置。但是,这种方法可能会导致性能问题,因为每次getView都创建新的ArrayAdapter和设置监听器,而且Spinner的弹出视图可能会在滚动时被重新创建。为了优化,可以考虑重用Spinner的适配器(但每个Spinner的选项可能不同,所以重用有限),或者使用RecyclerView来替代ListView。另外,在Spinner的监听器中,我们存储了选中的位置。这样即使列表项被复用时,也能恢复选中状态。用户可能还需要对选中的结果进行获取,可以在Adapter中提供获取选中值的方法。提醒:上面的代码只是示例,实际使用时可能需要根据需求调整。根据引用[4],如果用户需要带搜索功能的Spinner,则需要自定义Spinner(如使用PopupWindow和EditText等)。但是用户目前没有要求搜索功能,所以实现基本Spinner即可。§§相关问题§§1.如何在ListView的每个Item中使用多个交互组件(例如SpinnerCheckBox)?2.ListView中使用Spinner会遇到哪些问题?如何解决?3.如何优化包含SpinnerListView的性能?4.当使用RecyclerView代替ListView时,如何实现类似功能?</think> 在 Android 应用中,在 `ListView` 的每个列表项中集成 `Spinner` 组件需要以下关键步骤: ### 实现步骤及代码示例 1. **自定义列表项布局**(`list_item.xml`) ```xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="10dp"> <TextView android:id="@+id/tvItemName" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="Item" /> <Spinner android:id="@+id/spOptions" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" /> </LinearLayout> ``` 2. **创建自定义 Adapter**(继承 `BaseAdapter`) ```java public class CustomAdapter extends BaseAdapter { private final Context context; private final List<String> items; private final SparseArray<ArrayAdapter<CharSequence>> spinnerAdapters; public CustomAdapter(Context context, List<String> items) { this.context = context; this.items = items; this.spinnerAdapters = new SparseArray<>(); } @Override public int getCount() { return items.size(); } @Override public Object getItem(int position) { return items.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false); holder = new ViewHolder(); holder.tvItemName = convertView.findViewById(R.id.tvItemName); holder.spinner = convertView.findViewById(R.id.spOptions); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } // 设置文本内容 holder.tvItemName.setText(items.get(position)); // 配置Spinner数据 ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource( context, R.array.spinner_options, // 在res/values/arrays.xml中定义的数组 android.R.layout.simple_spinner_item ); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); holder.spinner.setAdapter(adapter); // 保存选中的位置(可选) holder.spinner.setTag(position); holder.spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) { int itemPosition = (int) parent.getTag(); // 处理选择事件 String selected = parent.getItemAtPosition(pos).toString(); Log.d("SPINNER", "Item "+itemPosition+" selected: "+selected); } @Override public void onNothingSelected(AdapterView<?> parent) {} }); return convertView; } static class ViewHolder { TextView tvItemName; Spinner spinner; } } ``` 3. **在 Activity/Fragment 中使用** ```java public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ListView listView = findViewById(R.id.listView); List<String> items = Arrays.asList("Item 1", "Item 2", "Item 3"); // 数据源 CustomAdapter adapter = new CustomAdapter(this, items); listView.setAdapter(adapter); } } ``` 4. **添加数据源**(`res/values/arrays.xml`) ```xml <resources> <string-array name="spinner_options"> <item>Option A</item> <item>Option B</item> <item>Option C</item> </string-array> </resources> ``` ### 关键注意事项 1. **视图复用处理**: - 使用 `ViewHolder` 模式避免重复查找视图 - 通过 `setTag()` 存储当前位置,解决列表项复用导致的定位错误 2. **数据绑定**: - 每个 `Spinner` 使用独立的数据适配器 - 通过 `SparseArray` 保存各位置的选择状态(需额外实现) 3. **交互优化**: - 使用 `spinnerAdapters` 缓存适配器对象提升性能 - 解决滚动冲突:在 `ListView` 的 `onTouchEvent` 中添加滚动处理 ### 高级实现方案 对于复杂需求(如带搜索功能的 `Spinner`),建议: 1. 参考引用[^4]的自定义 `PopupWindow` 方案 2. 创建扩展 `AppCompatSpinner` 的自定义组件 3. 在 `ListView` 中使用 `RecyclerView` 替代可获得更好性能
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值