Android ListView 选择问题
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2042278/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Android ListView Selection Problem
提问by WhiteTigerK
Hi All,
大家好,
I apologize for the following long question...
我为以下冗长的问题道歉......
I have a LinearLayout which contains a ListView and some other items. As for the ListView, each on of its rows is a LinearLayout that contains 3 views - Checkbox, ImageView and TextView (from left to right - horizontal). Since I wanted the whole row to be selected when using the trackball (to be highlighted with a background color), I set the all three views inside the LinearLayout row as not focusable, and it worked.
我有一个包含 ListView 和其他一些项目的 LinearLayout。对于 ListView,它的每一行都是一个 LinearLayout,包含 3 个视图 - Checkbox、ImageView 和 TextView(从左到右 - 水平)。由于我希望在使用轨迹球时选择整行(用背景颜色突出显示),因此我将 LinearLayout 行内的所有三个视图设置为不可聚焦,并且它起作用了。
Now I'm having 2 problems regarding this ListView. First, I want that whenever I touch a row in the ListView (with my finger), to get the same behavior as when using the trackball - means that I want the row to be selected (highlighted). What's happening right now is that when I touch the row it really becomes selected, but when I release my finger the selection is gone (much like happens in device's contact list).
现在我有两个关于这个 ListView 的问题。首先,我希望每当我触摸 ListView 中的一行时(用我的手指),获得与使用轨迹球时相同的行为 - 意味着我希望选择该行(突出显示)。现在发生的事情是,当我触摸该行时,它确实被选中了,但是当我松开手指时,选择消失了(很像在设备的联系人列表中发生的情况)。
Second - from a Menu, I can display a new LinearLayout instead the one that contains the ListView (different application's screen). When this happens, I still stores the object of the LinearLayout that contains the ListView, because I want to be able to re-display it later without creating it from scratch. The problem is that when I re-disaply the LinearLayout with the ListView, none of the ListView's rows are selected, even if a ceratin row was selected when the the LinearLayout with the ListView "left" the screen.
其次 - 从菜单中,我可以显示一个新的 LinearLayout 而不是包含 ListView(不同应用程序的屏幕)的那个。发生这种情况时,我仍然存储包含 ListView 的 LinearLayout 对象,因为我希望以后能够重新显示它,而无需从头开始创建它。问题是,当我使用 ListView 重新显示 LinearLayout 时,没有选择 ListView 的任何行,即使在带有 ListView 的 LinearLayout “离开”屏幕时选择了某个特定行。
Sorry again for the long post.
再次抱歉长篇幅。
Thanks!
谢谢!
回答by Tuo
Yeah, From an iOS developer's perspective, I find that it is extremely hard to apply features like "set default selection when starting" and "remember selection status after user clicked row" to ListView.
是的,从 iOS 开发人员的角度来看,我发现将“启动时设置默认选择”和“用户单击行后记住选择状态”等功能应用到 ListView非常困难。
So let's start with "remember selection"first.The problem is that even if you know that you can use selector xml to define highlight/pressed/focus style.But that style will not be kept after user clicked that row. For instance, I have a highlighting selector xml (list_selector.xml under res/drawable folder) like this (but you may have other fields need to highlight like text color of textview in row):
所以让我们先从“记住选择”开始。问题是即使你知道你可以使用选择器xml来定义高亮/按下/焦点样式。但是在用户单击该行后不会保留该样式。例如,我有一个像这样的突出显示选择器 xml(在 res/drawable 文件夹下的 list_selector.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>
and list_selector_pressed.xml which defined the highlighting style--set the background color to a gray color :
和 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>
So as @David Hedlund suggested:
正如@David Hedlund 建议的那样:
Rather, assign an OnItemClickListener, and have it store away the id of the selected item into some variable.
相反,分配一个 OnItemClickListener,并让它将所选项目的 id 存储到某个变量中。
you need to create a instance variable on top of your class:
您需要在类的顶部创建一个实例变量:
private View currentSelectedView;
then go to
然后去
@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
}
Pretty simple: we check if currentSelectedView is null or current clicked view or not. we first to unhighlight any style by calling method unhighlightCurrentRow(currentSelectedView)---you may wonder why we pass instant variable currentSelectedView as parameter, I will explain it later. Then we assign view to currentSelectedView and highlight current row; so that the style will persist after user's clicking is done.
很简单:我们检查 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));
}
Aha, that's it. That is how we implement "remember selection" for list view. As you see, we have to duplicate the codes for styling both in xml and java code--pretty stupid :(
啊哈,就是这样。这就是我们为列表视图实现“记住选择”的方式。如您所见,我们必须在 xml 和 java 代码中复制样式代码——非常愚蠢:(
Next about "set default selection". You may think that you can do this
接下来关于“设置默认选择”。你可能认为你可以做到这一点
listView.setAdapter(adatper)
listView.setSelection(0);
currentSelectedView = listView.getChildAt(0);
highlightCurrentRow(currentSelectedView);
in onCreate() in activity or onActivityCreated() in fragment.
But if you run it , you will get NullPointer exception and why ?
because at this time, the listview is not rendered yet and Android doesn't like iOS which have viewWillAppear. SO you have to create an instant variable to remember whether it is first time to render listview cell and in onListItemClick to unset that variable:
在活动中的 onCreate() 或片段中的 onActivityCreated() 中。
但是如果你运行它,你会得到 NullPointer 异常,为什么?因为此时,listview 还没有呈现,Android 不喜欢有 viewWillAppear 的 iOS。所以你必须创建一个即时变量来记住它是否是第一次呈现列表视图单元格并在 onListItemClick 中取消设置该变量:
So under currentSelectedView declaration:
所以在 currentSelectedView 声明下:
private Boolean firstTimeStartup = true;
then add methods : suppose we want to highlight the first row in list view:
然后添加方法:假设我们要突出显示列表视图中的第一行:
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;
}
}
Pretty simple. But you need to make some changes in onListItemClick method:
很简单。但是您需要在 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
}
There you go! Enjoy Android :)
你去吧!享受安卓 :)
回答by carox
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();
}
}
});
}
Above helps me setting a row focussed in the list.
以上帮助我在列表中设置一行。
回答by dxh
- This is he default and expected behavior. Tampering with this is stronglysuggested against.
- This follows from 1. to some extent. The selected-state is not persistent. Rather, assign an OnItemClickListener, and have it store away the id of the selected item into some variable. If you need to re-select the item when you come back, you can use
setSelection()
- 这是他的默认行为和预期行为。强烈建议不要篡改这一点。
- 这在某种程度上是从 1. 得出的。选择状态不是持久的。相反,分配一个 OnItemClickListener,并让它将所选项目的 id 存储到某个变量中。如果您回来时需要重新选择该项目,则可以使用
setSelection()
回答by Irek Wisniowski
I've solved that issue sending a message after notifyDataSetChanged() ;
and calling setSelection(0)
in my handleMessage function. It
Seems ugly, but helped me several times when SDK behaves oddly.
我已经解决了发送消息notifyDataSetChanged() ;
并调用setSelection(0)
我的 handleMessage 函数的问题。它看起来很丑,但是当 SDK 表现异常时帮助了我好几次。
回答by David Berry
A much cleaner solution for me centered around the fact that if you set either android:choiceMode="singleChoice"
or android:choiceMode="multipleChoice"
in your ListView
, then the ListView
will attempt to maintain the checked state for either a single or multiple selection ListView
. The gotcha is that it depends on the list cell implementing Checkable
对我来说,一个更简洁的解决方案围绕这样一个事实:如果您设置了android:choiceMode="singleChoice"
或android:choiceMode="multipleChoice"
在您的ListView
,那么ListView
将尝试为单个或多个选择保持选中状态ListView
。问题在于它取决于列表单元的实现Checkable
So, you start by implementing a CheckableLinearLayout
(or FrameLayout
might be better) so that android will maintain the checked state for you and you can keep the Drawable's in sync. The key here is refreshDrawableState
and onCreateDrawableState
They update the Drawable so the background will be drawn highlighted or not.
因此,您首先实现一个CheckableLinearLayout
(或FrameLayout
可能更好),以便 android 为您维护已检查的状态,并且您可以保持 Drawable 的同步。这里的关键是refreshDrawableState
和onCreateDrawableState
他们更新可绘制这样的背景将被吸引突出与否。
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;
}
}
Then, a simple selector
for the background drawable that will show the checked state:
然后,一个简单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>
Now I can use CheckableLinearLayout in any list that I want to show persistent selection state (multiple or single):
现在,我可以在要显示持久选择状态(多个或单个)的任何列表中使用 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>
I don't need any special code in the Fragment
to update the checked state, and I can maintain the selected position around rotation with:
我不需要任何特殊代码Fragment
来更新选中状态,我可以使用以下方法保持旋转的选定位置:
state.putInt("selection", listView.getCheckedItemPosition());
and
和
listView.setItemChecked(state.getInt("selection"), true);
likewise, I can use setItemChecked
to set an initially selected item, or use getCheckedItemPosition
(or getCheckedItemPositions()
for multiple selection) to get the current selection.
同样,我可以使用setItemChecked
来设置初始选择的项目,或者使用getCheckedItemPosition
(或getCheckedItemPositions()
用于多选)来获取当前选择。
回答by Yunis Rasulzada
Just paste below code line to ListView
in XML. Hope it helps someone :)
只需将下面的代码行粘贴到ListView
XML 中。希望它可以帮助某人:)
android:listSelector="@android:color/ANY_COLOR"
回答by Dacker
I am using an Adapter and didn't want to set custom background colors, but use the android:state_selected in drawable xml. SetSelection didn't work for me, but maybe that's also since I needed SetNotifyDataChanged which shows that the Selected State is not persistent.
我正在使用 Adapter 并且不想设置自定义背景颜色,而是在 drawable xml 中使用 android:state_selected。SetSelection 对我不起作用,但也许这也是因为我需要 SetNotifyDataChanged,它表明 Selected State 不是持久的。
I also found that the Selected state for an item in a ListView is not persistent, since SetNotifyDataChanged results in updating the ListView layout which clears them all. Setting the item to Selected in the Adapter's GetView is too soon too.
我还发现 ListView 中某个项目的 Selected 状态不是持久的,因为 SetNotifyDataChanged 会导致更新 ListView 布局,从而将它们全部清除。在 Adapter 的 GetView 中将项目设置为 Selected 也为时过早。
Eventually I set the Selected state for the view of the selected item after the layout of the listview has been changed, which is when LayoutChange event is being triggered (in Java it's probably attaching a to OnLayoutChangeListener of the ListView).
最终,在列表视图的布局更改后,我为所选项目的视图设置了 Selected 状态,也就是在触发 LayoutChange 事件时(在 Java 中,它可能将 a 附加到 ListView 的 OnLayoutChangeListener)。
To make it really easy I store the view of the selected item as Adapter's SelectedItemView. In the ListView's LayoutChange eventhandler I just set the adapter's SelectedItemView.Selected to true.
为方便起见,我将所选项目的视图存储为适配器的 SelectedItemView。在 ListView 的 LayoutChange 事件处理程序中,我只是将适配器的 SelectedItemView.Selected 设置为 true。
Here's the code from my Activity where I set the Adapter for the ListView and also subscribe to LayoutChange (or in Java attach an OnLayoutChangeListener)
这是我的 Activity 中的代码,我在其中为 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;
};
Here's my code for the Adapter:
这是我的适配器代码:
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; }
}
回答by vovkab
This should help:
这应该有帮助:
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();
}
}
});
}