Android ListView使用单选模式和自定义行布局中的RadioButton

30

我有一个ListView,它处于单选模式。我想要的只是在旁边显示一个RadioButton,当点击时高亮以表示已选择,当点击不同的选项时,该选项返回未选择状态,新的选项则变为已选中状态。这为什么这么难?这不应该这么复杂。我花了几天的时间寻找适当的答案,但找不到,所以我希望能够清晰明了地问出来。

我的listview布局:(R.layout.view_orders)

<?xml version="1.0" encoding="utf-8"?>
<ListView 
        android:choiceMode="singleChoice"
        android:id="@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:divider="@drawable/list_divider"
        android:dividerHeight="1px"
        android:focusable="false"
        android:focusableInTouchMode="false"
        android:cacheColorHint="#00000000">
</ListView>

我的自定义行(R.layout.orders_row)

<?xml version="1.0" encoding="utf-8"?>
    
<RelativeLayout
    xmlns:app="http://schemas.android.com/apk/res/com.xxx.xxxxxx"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="6dip">
    
    <com.xxx.xxxxxx.VerticalLabelView
        app:text="SHORT"
        app:textColor="#666"
        app:textSize="14sp"
        android:id="@+id/state"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true" />
    
    <TextView
        android:id="@+id/quantity"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/state" 
        android:layout_centerVertical="true"
        android:gravity="center"
        android:textSize="40sp"
        android:layout_margin="2dip"
        android:minWidth="30dip"
        android:textColor="#555" />
        
        
    <RelativeLayout
        android:layout_toRightOf="@id/quantity"
        android:layout_centerVertical="true"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
        
        <TextView
            android:id="@+id/instrument"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:textColor="#333"
            android:layout_marginLeft="2dip"
            android:layout_marginRight="2dip"
            />
            
            
        <TextView
            android:id="@+id/deets"
            android:layout_below="@id/instrument"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:textSize="16sp"
            android:textColor="#888"
            android:layout_marginLeft="2dip"
            android:layout_marginRight="2dip"
            />
        
    </RelativeLayout>
    
        <RadioButton
            android:id="@+id/selector"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            />

</RelativeLayout>

我的 onCreate() 方法:

@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.view_orders);
    client = new Client(handler);
    ola = new OrdersAdapter(this, R.layout.orders_row, Orders);
    setListAdapter(ola);
    final RelativeLayout loading = (RelativeLayout) findViewById(R.id.loading);
    panel = (PositionsPanel) findViewById(R.id.panel);
    Utility.showProgressBar(loading);
    client.Connect("orders");
}

现在一切的基础工作都按预期进行,当你点击单选按钮时,通过它的标签,我可以适当地从列表中选择该项目并对其进行操作。然而,当第一个单选按钮被点击时,最后一个将被选中。再次点击同一单选按钮,它现在也被选中。再次点击它,什么也不会发生,第一个和最后一个都被选中。现在我点击列表中的任何其他单选按钮,就像预期的那样会被选中。点击任何一个已选中的单选按钮,什么也不会发生,该单选按钮保持选中状态。

我已经尝试在onCreate()中使用以下代码:

@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.view_orders);
    client = new Client(handler);
    ola = new OrdersAdapter(this, android.R.layout.simple_list_item_single_choice, Orders);
    setListAdapter(ola);
    final RelativeLayout loading = (RelativeLayout) findViewById(R.id.loading);
    panel = (PositionsPanel) findViewById(R.id.panel);
    Utility.showProgressBar(loading);
    client.Connect("orders");
}

这只是根本不显示单选按钮。太好了

也许(也就是大概),我只是迟钝,无法弄清楚这一点,但我看到很多人问了这个问题,却没有真正的答案。有许多参考其他教程或Commonsware的书。然而,评论现在已经过时,他的存储库已经发生了很大的变化,那些不再是正确的答案了。

所以,有人知道如何获得预期的功能吗?或者失败了,只需将Gmail应用程序的源代码传递给我。 :)

7个回答

9
请记住,在ListView行项目中,它们是重复使用的。这可能解释了为什么一行上的操作会影响到另一行。在马克的书中仔细研究一下,你会找到相关内容。
如果您正在使用适配器来管理列表,则可以对适配器上的getView()方法进行操作,以便在每个行创建/回收时添加单击处理程序,并确保正确管理状态,因为行项目被创建和重复使用。
我的方法是将状态存储在自己的数据结构中,然后使用getView()方法在用户向上或向下滚动ListView时,在UI中将其映射出来。可能有更好的解决方案,但这对我很有效。

所以这就是正在发生的事情。我不知道为什么其他答案没有直接说明这个简单的事实。在意识到我正在使用convertView并对其进行回收后,这变得非常明显。花费一两分钟搜索,我知道如何解决这个问题。我查看了马克的书第9章“与列表一起变得花哨”的RateList示例,在那里详细解释了它,讲得很清楚。任何遇到此问题的人都可以查看Mark Murphy的《Beginning Android》该章节。 - zkwentz
Android中的这些怪癖确实使其学习曲线陡峭,但一旦理解,我们就可以获得回收利益的性能优势。 - Ollie C
所以那个方法最终没有起作用,我仔细检查了一遍,虽然回收利用肯定是导致我看到的问题的原因,但马克的书并没有详细说明如何解决我遇到的具体问题。 - zkwentz
我不是很确定。按钮们肯定知道它们在哪里,但它们没有存储正确的状态。我只是把它改成了一个删除按钮,然后就完成了。浪费了太多时间。不过还是谢谢你的帮助,回收利用肯定是这里的潜在问题。 - zkwentz
我将状态保存在一个单独的对象中,在适配器的getView()方法中从存储中获取状态,并将其应用于复选框(可能已被回收利用)。为了保持简单,您可以通过每次进入getView()时重新创建新视图来剥离回收,这可能有助于调试。如果您没有及时获得释放,则性能可能会变差。ListView在大多数情况下都很慢。我建议明确问题所在,因为这样您就会被引导到解决方案。 - Ollie C
如果状态仍然随机出现,可以通过禁用回收或强制从自己的状态存储中获取状态来排除回收。您可以在Android首选项(速度较慢但永久性)中存储复选框状态,也可以在Activity或Application中存储(子类化Application对象)。 - Ollie C

8

简单的解决方案:

将以下行添加到您的xml文件中的RadioButton布局中:

<RadioButton 
   ...
   android:onClick="onClickRadioButton"
   ...
/>

然后在使用ListView的活动类中添加以下内容:

   private RadioButton listRadioButton = null;
   int listIndex = -1;

   public void onClickRadioButton(View v) {
        View vMain = ((View) v.getParent());
        // getParent() must be added 'n' times, 
        // where 'n' is the number of RadioButtons' nested parents
        // in your case is one.

        // uncheck previous checked button. 
        if (listRadioButton != null) listRadioButton.setChecked(false);
        // assign to the variable the new one
        listRadioButton = (RadioButton) v;
        // find if the new one is checked or not, and set "listIndex"
        if (listRadioButton.isChecked()) {
            listIndex = ((ViewGroup) vMain.getParent()).indexOfChild(vMain); 
        } else {
            listRadioButton = null;
            listIndex = -1;
        }
    }

使用这段简单的代码只有一个RadioButton被选中。 如果您触摸已经选中的那个,则它将返回到未选中模式。

“listIndex”跟踪所选项目,如果没有则为-1。

如果您想始终保持一个选中状态,则onClickRadioButton中的代码应该是:

   public void onClickRadioButton(View v) {
        View vMain = ((View) v.getParent());
        int newIndex = ((ViewGroup) vMain.getParent()).indexOfChild(vMain);
        if (listIndex == newIndex) return;

        if (listRadioButton != null) {
                listRadioButton.setChecked(false);
        }
        listRadioButton = (RadioButton) v;
        listIndex = newIndex; 
    }

2
嗨,当列表适合一页时,这个程序可以工作,但是当列表超过一页时,listIndex会出现问题,因为它从第一个可见的列表项开始计数,而不是从列表的实际开头开始计数。如何解决这个问题? - user1118764
有人解决了长列表项的问题吗? - PayToPwn

3

我的名字是Gourab Singha。我解决了这个问题。我编写了这段代码来解决它。希望这能对大家有所帮助。谢谢。我需要三小时时间。

public void add_option_to_list(View v){


    LinearLayout ll = (LinearLayout)v.getParent();
    LinearLayout ll_helper = null; 
    LinearLayout ll2 =  (LinearLayout)ll.getParent();
    LinearLayout ll3 =  (LinearLayout)ll2.getParent();

    ListView ll4 =  (ListView)ll3.getParent();
    //RadioButton rb1= (RadioButton)ll.getChildAt(1);
    Toast.makeText(getApplicationContext(), ", "+ll4.getChildCount(), Toast.LENGTH_LONG).show();    
    //ll.findViewById(R.id.radio_option_button)
    for(int k=0;k<ll4.getChildCount();k++){

        ll3 = (LinearLayout)ll4.getChildAt(k);
        ll2 = (LinearLayout)ll3.getChildAt(0);
        ll_helper = (LinearLayout)ll2.getChildAt(0);
        RadioButton rb1 = (RadioButton)ll_helper.getChildAt(1);
        if(rb1.isChecked())
        rb1.setChecked(false);


    }
    RadioButton rb1 = (RadioButton)ll.getChildAt(1);
    rb1.setChecked(true);
    //Toast.makeText(getApplicationContext(), ""+((TextView)ll.getChildAt(4)).getText().toString(), Toast.LENGTH_LONG).show();


    }

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="match_parent"
    android:orientation="vertical" >

    <LinearLayout

        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >


        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal" >

            <TextView
                android:id="@+id/id"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Category Name"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:visibility="gone" />

            <RadioButton
                android:id="@+id/radio_option_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="add_option_to_list"
                android:text="" />

            <TextView
                android:id="@+id/option_name"
                android:layout_width="wrap_content"
                android:layout_height="fill_parent"
                android:layout_marginLeft="3dp"
                android:layout_weight="5.35"
                android:text="Category Name"
                android:textAppearance="?android:attr/textAppearanceMedium" />



            <TextView
                android:id="@+id/option_price"
                android:layout_width="76dp"
                android:layout_height="wrap_content"
                android:layout_marginRight="10dp"
                android:gravity="right"
                android:text="$0.00"
                android:textAppearance="?android:attr/textAppearanceMedium" />

            <TextView
                android:id="@+id/option_unit_price"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginRight="5dp"
                android:gravity="right"
                android:text="$0.00"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:visibility="gone" />
        </LinearLayout>

    </LinearLayout>

</LinearLayout>

20
欢迎来到 Stack Overflow。我编辑了你的回答以使其更易于阅读。顺便说一下,你不需要在回答中写下你的名字;你的名字总是显示在回答的末尾。 - David Gorsline

2

我刚遇到了一个类似的问题。看起来与视图回收有关。但我添加了各种日志并注意到回收处理正常。 问题出在RadioButtons的处理上。我使用的是:

if(answer == DBAdapter.YES){
   rbYes.setChecked(true); 
   rbNo.setChecked(false);                    
}
else if(answer == DBAdapter.NO){
   rbYes.setChecked(false); 
   rbNo.setChecked(true);
}
else{
  rbYes.setChecked(false); 
  rbNo.setChecked(false);
}                

正确的做法是:

if(answer == DBAdapter.YES){
   rbYes.setChecked(true); 
}
else if(answer == DBAdapter.NO){
   rbNo.setChecked(true);
}
else{
   RadioGroup rg = (RadioGroup)(holder.answerView);
   rg.clearCheck();
}                

2
你可以通过以下方式更简单地实现,这在MarshMallow中已经尝试过。
1)拥有一个CheckedTextView
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:checkMark="?android:attr/listChoiceIndicatorSingle"
    android:padding="10dp"
    android:textAppearance="?android:attr/textAppearanceMedium">

  • ListChoiceIndicatorSingle将显示RadioButton

  • ListChoiceIndicatorMultiple将显示CheckBox

别忘了在你的ListView中加上choiceMode:Single


0

适配器内部

 viewHolder.radioBtn.setOnClickListener(new OnClickListener() {

               @Override
               public void onClick(View v) {
                   Log.e("called", "called");
                   if(position != mSelectedPosition && mSelectedRB != null){
                       mSelectedRB.setChecked(false);
                   }



                   mSelectedPosition = position;
                   mSelectedRB = (RadioButton)v;
               }
           });

           viewHolder.radioBtn.setText(mList[position]);  
           if(mSelectedPosition != position){
               viewHolder.radioBtn.setChecked(false);

           }else{
               viewHolder.radioBtn.setChecked(true);

               if(mSelectedRB != null && viewHolder.radioBtn != mSelectedRB){
                   mSelectedRB = viewHolder.radioBtn;
               }
           }

为单选按钮添加样式

<?xml version="1.0" encoding="utf-8"?>
 <selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/tick" android:state_checked="true"/>
    <item android:drawable="@drawable/untick" android:state_checked="false"/>

 </selector>

在 XML 中使用单选按钮样式

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <RadioButton
        android:id="@+id/radioBtn"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:button="@null"
        android:checked="true"
        android:drawablePadding="30dp"
        android:drawableRight="@drawable/checkmark"
        android:text="first" />

</RelativeLayout>

-1

在你的适配器上:

public void setChecked(View v, int position) {

    ViewGroup parent = ((ViewGroup) v.getParent()); // linear layout

    for (int x = 0; x < parent.getChildCount() ; x++) {

        View cv = parent.getChildAt(x);
        ((RadioButton)cv.findViewById(R.id.checkbox)).setChecked(false); // your checkbox/radiobtt id
    }

    RadioButton radio = (RadioButton)v.findViewById(R.id.checkbox);
    radio.setChecked(true);
}

在你的活动/片段中:

   listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            ((ModelNameAdapter)adapter).setChecked(view, position);

        }
    });

你的 set checked 方法中从未使用过 position。 - Sagar Limbani
为了使其正常工作,您不能回收视图。不要这样做:if convertView == null { convertView = inflate},始终要充气一个新的视图。 - Renato Probst

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接