2010-01-11 49 views
8

大家好,Android的ListView的选择问题

我以下长的问题道歉......

我有一个LinearLayout中包含一个ListView和其他一些物品。至于ListView,它的每行都是一个LinearLayout,它包含3个视图 - 复选框,ImageView和TextView(从左到右 - 水平)。 由于我希望在使用轨迹球时选择整行(用背景色突出显示),因此我将LinearLayout行内的所有三个视图设置为不可聚焦,并且它工作正常。

现在我有2个关于这个ListView的问题。 首先,我希望每当我在ListView中触摸一行(用我的手指),以获得与使用轨迹球时相同的行为 - 意味着我想要选择(突出显示)该行。这是怎么回事现在的问题是,当我触摸排它真的成为选择的,但是当我松开我的手指选择走了(就像发生在设备的联系人列表)。

二 - 从菜单中,我可以显示一个新的LinearLayout,而不是包含ListView控件(不同应用程序的屏幕)的人。发生这种情况时,我仍然存储包含ListView的LinearLayout的对象,因为我希望能够稍后重新显示它,而无需从头开始创建它。 问题是,当我使用ListView重新使用LinearLayout时,即使在使用ListView的“LinearLayout”“离开”屏幕时选择了角色行,也不会选择任何ListView的行。

对不起再长的职位。

谢谢!

回答

3
  1. 这是他默认和预期的行为。篡改这是强烈建议反对。
  2. 这从1到某种程度上。选定状态不是持久的。而是分配一个OnItemClickListener,并让它将选定项目的ID存储到某个变量中。如果您需要重新选择,当你回来的项目,你可以使用setSelection()
+0

我已经尝试在返回时使用setSelection(),但它不好 - 首先所有行都没有突出显示(没有背景色),第二行位于屏幕的顶部,尽管当我离开时它位于中间某处。 谢谢。 – WhiteTigerK 2010-01-11 14:32:01

+0

另一件事 - 在设备的联系人列表中,当使用轨迹球选择联系人,然后按轨迹球查看其详细信息时,联系人列表的状态将被保存,因此返回联系人列表时,联系人仍处于选中状态在屏幕上的位置是相同的。所以我认为这种行为是可以实现的。 谢谢。 – WhiteTigerK 2010-01-11 14:36:33

+0

是的,选择状态将被保存,但如果在任何时候您触摸屏幕,您将切换*触摸模式*,并且在触摸模式下,如您已经注意到的,没有选择项目的视觉反馈只有当物品被触摸时才提供简短的反馈,当你返回到列表视图时这不会发生)。这是默认行为。不要改变它。 – 2010-01-12 10:21:29

0

这应有助于:

private void setListviewSelection(final ListView list, final int pos) { 
    list.post(new Runnable() { 
     @Override 
     public void run() { 
      list.setSelection(pos); 
      View v = list.getChildAt(pos); 
      if (v != null) { 
       v.requestFocus(); 
      } 
     } 
    }); 
} 
+1

无法正常工作。在进入方法,但没有任何反应 – 2013-02-18 14:14:09

1

我已经解决了这个问题notifyDataSetChanged() ;后发送消息,并呼吁setSelection(0)我的handleMessage函数。它看起来很丑陋,但在SDK表现奇怪时帮了我好几次。

5
private void setListviewSelection(final ListView list, final int pos) { 
list.post(new Runnable() 
    { 
    @Override 
    public void run() 
     { 
     list.setSelection(pos); 
     View v = list.getChildAt(pos); 
     if (v != null) 
     { 
      v.requestFocus(); 
     } 
    } 
}); 
} 

上面帮助我设置了一个专注于列表的行。

+2

嗯,这是不正确的。 'setSelection'将选择列表的第i个元素,但'getChildAt'返回* visible *子元素,即它取决于当前的滚动。所以,如果列表滚动,这将无法正常工作。 – smok 2013-12-11 18:57:07

+0

同意@smok – Divers 2014-01-24 11:07:46

19

呀,从iOS开发者的角度来看,我觉得这是非常难以应用像功能“组默认选择启动时”和“记得选择状态用户点击一行后”到ListView控件。

因此,让我们开始“记住选择”第一。问题是,即使你知道 你可以选择使用XML来定义亮点/压/聚焦style.But这种风格不会 保持用户后点击该行。举例来说,我有(下RES /绘制的文件夹list_selector.xml)一个高亮选择这样的XML(但你可能有其他领域需要突出像行的TextView的文本颜色):

<?xml version="1.0" encoding="utf-8"?> 
<selector xmlns:android="http://schemas.android.com/apk/res/android"> 
    <item android:drawable="@drawable/list_selector_pressed" android:state_pressed="true" /> 
    <item android:drawable="@drawable/list_selector_pressed" android:state_selected="true" /> 
</selector>  

和list_selector_pressed。其中限定的突出显示风格的xml - 设置背景颜色 到灰色:

<?xml version="1.0" encoding="utf-8"?> 
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> 
    <item> 
     <shape xmlns:android="http://schemas.android.com/apk/res/android"> 
      <solid android:color="@color/dark_gray" /> 
     </shape> 
    </item> 
</layer-list> 

因此,作为@大卫Hedlund的建议:

而是,分配一个OnItem ClickListener,并让它将选定项目的ID存储到某个变量中。

您需要创建类的顶部的实例变量:

private View currentSelectedView; 

然后去

@Override 
public void onListItemClick(ListView l, View v, int position, long id) { 
    if (currentSelectedView != null && currentSelectedView != v) { 
     unhighlightCurrentRow(currentSelectedView); 
    } 

    currentSelectedView = v; 
    highlightCurrentRow(currentSelectedView); 
    //other codes 
    } 

很简单:我们检查,如果currentSelectedView为空或电流点击视图或不。我们首先通过调用方法unhighlightCurrentRow(currentSelectedView)来忽略任何样式---您可能想知道为什么我们将即时变量currentSelectedView作为参数传递,稍后我会解释它。然后我们将视图分配给currentSelectedView并突出显示当前行;以便在用户点击完成后样式将保持不变。

private void unhighlightCurrentRow(View rowView) { 
    rowView.setBackgroundColor(Color.TRANSPARENT); 
    TextView textView = (TextView) rowView.findViewById(R.id.menuTitle); 
    textView.setTextColor(getResources().getColor(R.color.white)); 
} 

private void highlightCurrentRow(View rowView) { 
    rowView.setBackgroundColor(getResources().getColor(
      R.color.dark_gray)); 
    TextView textView = (TextView) rowView.findViewById(R.id.menuTitle); 
    textView.setTextColor(getResources().getColor(R.color.yellow)); 

} 

啊哈,就是这样。这就是我们如何实现列表视图的“记忆选择”。正如你看到的, 我们要复制代码都在XML和Java代码样式 - 非常愚蠢:(

下一页关于“设置默认选择”你可能会认为你可以做到这一点

。在片段中
listView.setAdapter(adatper) 
listView.setSelection(0); 
currentSelectedView = listView.getChildAt(0); 
highlightCurrentRow(currentSelectedView); 
的onCreate()在活动或onActivityCreated()


但是,如果你运行它,你会得到空指针异常,为什么? 因为此时,列表视图尚未呈现和Android不像有viewWillAppear的iOS,所以你必须创建一个即时变量来记住它是否是第一次渲染的ListView细胞和onListItemClick取消设置变量:所以下currentSelectedView声明

private Boolean firstTimeStartup = true; 

然后添加方法:假设我们想强调在列表视图中的第一行:

public class HomeAdapter extends ArrayAdapter<String> { 
    int layoutResourceId; 

    public HomeAdapter(Context context, int textViewResourceId, 
      ArrayList<String> objects) { 
     super(context, textViewResourceId, objects); 
     layoutResourceId = textViewResourceId; 
    } 

    @Override 
    public View getView(int position, View convertView, ViewGroup parent) { 
     if (convertView == null) { 
      convertView = LayoutInflater.from(getContext()).inflate(
        layoutResourceId, null); 
     } 

     if (firstTimeStartup && postion == 0) { 
      highlightCurrentRow(convertView); 
     } else { 
      unhighlightCurrentRow(convertView); 
     } 

     TextView title = (TextView) convertView 
       .findViewById(R.id.menuTitle); 
     title.setText(getItem(position)); 
     return convertView; 
    } 
} 

很简单。 但是你需要在onListItemClick方法的一些变化:

@Override 
public void onListItemClick(ListView l, View v, int position, long id) { 

    if (firstTimeStartup) {// first time highlight first row 
     currentSelectedView = l.getChildAt(0); 
    } 
    firstTimeStartup = false; 
    if (currentSelectedView != null && currentSelectedView != v) { 
     unhighlightCurrentRow(currentSelectedView); 
    } 

    currentSelectedView = v; 
    highlightCurrentRow(currentSelectedView); 

    //other codes 
} 

你去那里!享受Android :)

+0

我在这里关于代码的记忆/突出显示部分的经验是,不需要同时复制xml和编程设置。相反,这些XML的不工作,并在编程上做。从这个意义上讲,可以跳过创建'list_selector.xml'和'list_selector_pressed.xml',并且只写出你描述的代码 – Thomas 2014-05-09 13:05:14

0

我正在使用适配器,并不想设置自定义背景颜色,但使用android:state_selected绘制xml。 SetSelection对我不起作用,但也许这也是因为我需要SetNotifyDataChanged,它显示Selected State不是持久的。

我还发现ListView中的项目的Selected状态不是持久的,因为SetNotifyDataChanged导致更新清除它们的ListView布局。在适配器的GetView中将项目设置为“选中”也太快了。

最后,我设置的选中状态为ListView的布局已更改后所选择的项目,这是当被触发LayoutChange事件的视图(在Java中它可能附加到ListView的OnLayoutChangeListener)。

为了使它非常容易,我将所选项目的视图存储为适配器的SelectedItemView。 在ListView的LayoutChange事件处理程序中,我只是将适配器的SelectedItemView.Selected设置为true。

下面是从我的活动中的代码,我设置的适配器为ListView还订阅LayoutChange(或Java中附加一个OnLayoutChangeListener)

 ringTonesListView.Adapter = ringTonesListAdapter; 
     ringTonesListView.LayoutChange += (s, layoutChangeArgs) => { 
      //At this stage the layout has been updated and the Selected can be set to true for the view of the selected item. This will result in android:state_selected logic to be applied as desired and styling can be completely done per layout in Resources. 
      ringTonesListAdapter.SelectedItemView.Selected = true; 
     }; 

这里是我的适配器代码:

public class RingTonesListAdapter : BaseAdapter<RingToneItem> 
{ 
    List<RingTone> Items { get; set; } 

    public override View GetView(int position, View convertView, ViewGroup parent) 
    { 
     View view = convertView; 

     // re-use an existing view, if one is available 
     // otherwise create a new one 
     if (view == null) 
     { 
      view = Context.LayoutInflater.Inflate(Resource.Layout.AlertSoundItem, parent, false); 
      view.Click += SelectRingTone; 
     } 

     RingTone ringTone = this[position]; 
     if (ringTone.Selected) 
     { 
      //==> Important 
      //Store this view since it's the view for the Selected Item 
      SelectedItemView = view; 
      //Setting view.Selected to true here doesn't help either, since Selected will be cleared after. 
     } 

     return view; 
    } 

    private void SelectRingTone(object sender, EventArgs args) 
    { 
     View view = (View)sender; 
     string title = view.FindViewById<TextView>(Resource.Id.ringToneTitle).Text; 
     RingToneItem ringToneItem = Items.First(rt => rt.Title == title); 
     if (!ringToneItem.Selected) 
     { 
      //The RingTone was not selected and is selected now 
      //Deselect Old and Select new 
      foreach (RingToneItem oldItem in Items.Where(rt => rt.Selected)) 
      { 
       oldItem.Selected = false; 
      } 

      // Select New RingTone 
      ringToneItem.Selected = true; 
      //Update the ListView. 
      //This will result in removal of Selected state for all Items when the ListView updates it's layout 
      NotifyDataSetChanged(); 
     } 

     //Now play the test sound 
     NotifierService.TestSound(Context, ringToneItem); 
    } 

    public View SelectedItemView { get; set; } 
} 
1

对于我来说,更清洁的解决方案主要集中在以下事实上:如果您在ListView中设置了android:choiceMode="singleChoice"android:choiceMode="multipleChoice",则ListView将尝试将检查状态维持为单选或多选ListView。在疑难杂症的是,这取决于实施Checkable

所以,你通过实现一个CheckableLinearLayout(或FrameLayout可能会更好)启动列表中的细胞,使Android将保持选中状态,为您和您可以保持绘制对象的同步。这里的关键是refreshDrawableStateonCreateDrawableState他们更新了Drawable,因此背景将被绘制为高亮或不高亮。

public class CheckableLinearLayout extends LinearLayout implements Checkable { 
    boolean checked; 

    public CheckableLinearLayout(Context context) { 
     super(context); 
    } 

    public CheckableLinearLayout(Context context, AttributeSet attrs) { 
     super(context, attrs); 
    } 

    public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { 
     super(context, attrs, defStyleAttr); 
    } 

    @Override 
    public void setChecked(boolean checked) { 
     this.checked = checked; 
     refreshDrawableState(); 
    } 

    @Override 
    public boolean isChecked() { 
     return checked; 
    } 

    @Override 
    public void toggle() { 
     setChecked(!isChecked()); 
    } 

    private static final int[] CheckedStateSet = { android.R.attr.state_checked }; 

    @Override 
    protected int[] onCreateDrawableState(int extraSpace) { 
     final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 
     if (isChecked()) { 
      mergeDrawableStates(drawableState, CheckedStateSet); 
     } 
     return drawableState; 
    } 
} 

然后,一个简单的selector为背景绘制,将显示的选中状态:

<selector xmlns:android="http://schemas.android.com/apk/res/android"> 
    <item android:drawable="#ccc" android:state_checked="true" /> 
    <item android:drawable="@android:color/transparent" /> 
</selector> 

现在我可以,我想显示永久选择状态(多个或单个任何列表中使用CheckableLinearLayout ):

<CheckableLinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    android:orientation="vertical" 
    android:layout_width="match_parent" 
    android:layout_height="wrap_content" 
    android:background="@drawable/checkable_cell" 
    > 

    <TextView 
     android:layout_width="match_parent" 
     android:layout_height="wrap_content" 
     android:id="@+id/text" 
     android:layout_margin="10dp" 
     android:maxLines="8" 
     /> 
</CheckableLinearLayout> 

我不需要在Fragment任何特殊代码,以更新的选中状态,我可以保持选定的围绕与旋转位置:

state.putInt("selection", listView.getCheckedItemPosition()); 

listView.setItemChecked(state.getInt("selection"), true); 

同样,我可以使用setItemChecked来设置初始选择的项目,或者使用getCheckedItemPosition(或getCheckedItemPositions()对于多重选择),以获得当前的选择。

+0

在我的用例中工作。谢谢。 – MCLLC 2017-07-11 04:59:29