通过自定义选择器设置ListView项的背景

99

是否可以通过列表选择器为每个ListView项应用自定义背景?

默认选择器在state_focused =“false”情况下指定@android:color/transparent ,但将其更改为某些自定义绘图不会影响未选中的项目。Romain Guy似乎在这个回答中建议这是可能的。

我目前通过在每个视图上使用自定义背景并在选择/聚焦/其他时隐藏它以显示选择器来实现相同的效果,但将所有内容定义在一个地方将更加优雅。

供参考,这是我用于尝试使其工作的选择器:

<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_focused="false"
        android:drawable="@drawable/list_item_gradient" />

    <!-- Even though these two point to the same resource, have two states so the drawable will invalidate itself when coming out of pressed state. -->
    <item android:state_focused="true" android:state_enabled="false"
        android:state_pressed="true"
        android:drawable="@drawable/list_selector_background_disabled" />
    <item android:state_focused="true" android:state_enabled="false"
        android:drawable="@drawable/list_selector_background_disabled" />

    <item android:state_focused="true" android:state_pressed="true"
        android:drawable="@drawable/list_selector_background_transition" />
    <item android:state_focused="false" android:state_pressed="true"
        android:drawable="@drawable/list_selector_background_transition" />

    <item android:state_focused="true"
        android:drawable="@drawable/list_selector_background_focus" />
        
</selector>

这是我设置选择器的方式:

<ListView
    android:id="@android:id/list"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:listSelector="@drawable/list_selector_background" />    
10个回答

129

我自己也为此感到沮丧,最终解决了这个问题。正如Romain Guy所暗示的那样,还有另一个状态"android:state_selected",你必须使用它。为列表项的背景使用一个状态的drawable,为你的列表的listSelector使用不同的状态的drawable:

list_row_layout.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:background="@drawable/listitem_background"
    >
...
</LinearLayout>

listitem_background.xml:

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

包含 ListView 的 layout.xml 文件:

...
<ListView 
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   android:listSelector="@drawable/listitem_selector"
   />
...

listitem_selector.xml:

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

1
感谢您深入的回答。正如我在问题中提到的那样,这正是我目前正在做的,而且效果相当不错。 - shilgapira
很遗憾,我仍然有定制列表视图的问题。我在这里发布了我的具体问题:http://stackoverflow.com/questions/5426385/listview-with-nine-patch-item-background-issues;如果您有任何建议,我将不胜感激。 - LostNomad311
这适用于ICS之后的版本,但对于Gingerbread来说,整个列表都会被按下或选定的可绘制对象所着色。 - gunar
有没有针对ICS之前版本的解决方案? - Lonli-Lokli
它在4.2上工作正常,但在2.3上无法正常运行,listitem_selector.xml覆盖整个listview项目。 - Ram
显示剩余2条评论

79

如果您使用带有填充的9-patch drawable作为背景,则dglmtn提出的解决方案无法正常工作。会出现奇怪的问题,我甚至不想谈论它们,如果您遇到这样的问题,您就知道了。

现在,如果您想要具有不同状态和9-patch drawable(我认为它可以适用于任何drawable和颜色)的列表视图,您需要做两件事:

  1. 设置列表项的选择器。
  2. 消除列表的默认选择器。

您应该首先设置row_selector.xml:

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

不要忘记 android:state_selected。它的作用类似于列表的android:state_focused,但是它适用于列表项。

现在将选择器应用于项目(row.xml):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/row_selector"
>
...
</RelativeLayout>

创建一个列表的透明选择器:

<ListView
    android:id="@+id/android:list"
    ...
    android:listSelector="@android:color/transparent"
    />

这应该能够完成任务。


1
+1,我之前没有看到你的答案,我自己也得出了相同的结果。但是感谢你提到了9-patch可绘制对象,我相信在我的未来实现中它会很有用。这应该是最好的答案,因为这种方法更加清晰和可维护。 - IsaacCisneros
28
抱歉,但这种事情有时会让人想知道这些该死的用户界面API为什么要如此糟糕。安卓还有很长的路要走。这些基本的东西不应该是那么麻烦的。 - Gubatron
2
+1 谢谢你,谢谢你,谢谢你。为了实现如此基本的东西而跳跃所有这些障碍真是太麻烦了。所有这些状态都非常令人困惑。 - Moritz
3
将android:listSelector="@drawable/list_selector"替换为android:listSelector="@android:color/transparent",无需额外的list_selector.xml。请注意保持原意并将翻译内容易于理解。 - Sofi Software LLC
1
正确的做法是,非破坏性列表选择器只需使用@android:color/transparent而不是您自己的xml选择器。我刚试过了。不能正常工作的是@ null,这让我感到惊讶,因为它应该表示“没有列表选择器”,并且由于列表选择器在项目后面绘制,所以不应该绘制任何内容。但是这样不起作用,那么默认选择器就会回来... - Zordid
显示剩余2条评论

17

绝对不要对列表视图行使用“背景颜色”……

这会阻止每个选择器操作(这就是我的问题!)

祝好运!


3
这不是真的。只需将选择器用作背景,而不是静态颜色或可绘制对象。 - Michał Klimczak
这里指的是“背景颜色”。我完全接受你的话。 - cV2
我来这里是因为现在我需要一个选择器,因为我已经用带有“颜色”的 setBackgroundColor 了。叹气... 根据您的 API 版本使用 setBackgroundsetBackgroundDrawable 来配合选择器。 - EpicPandaForce

5
如果您在list_row_layout.xml中添加以下内容,就足够了:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:background="@drawable/listitem_background">... </LinearLayout>

listitem_selector.xml:

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

2
太棒了...最简单的例子 - Shihab Uddin
1
并且使ListView选择器透明化。 - Abdul Saleem

5

1
一个出色的简单但非常有效。感谢Romain Guy... <ListView ... android:cacheColorHint="#00000000" > </ListView> 就是这样! - Eric JOYÉ
不要感谢Romain Guy。他是问题的一部分。像这样的操作系统中不应该有太多“隐藏技巧”或其他黑客技巧。尽管我喜欢Android,但与其他SDK(甚至是更新/不成熟的SDK)相比,它是一堆大便。 - StackOverflowed

4

改为:

android:drawable="@color/transparent" 

写入

android:drawable="@android:color/transparent"

4
你可以编写一个主题:
<pre>

    android:name=".List10" android:theme="@style/Theme"

theme.xml

<style name="Theme" parent="android:Theme">
        <item name="android:listViewStyle">@style/MyListView</item>
</style>

styles.xml

 <style name="MyListView" parent="@android:style/Widget.ListView">
<item name="android:listSelector">@drawable/my_selector</item>

my_selector是您想要自定义的选择器。 很抱歉,我不知道如何编写我的代码。


谢谢您的帮助,但这并不能直接帮助设置选择器的默认列表项背景。 - shilgapira

3
我总是使用同样的方法,每次都有效,无论何时何地: 我只需使用像这样的选择器

<item android:state_activated="true"  android:color="@color/your_selected_color" />
<item android:state_pressed="true" android:color="@color/your_pressed_color" />
<item android:color="@color/your_normal_color"></item>

并在ListView上设置属性(这非常重要以使其工作)

android:choiceMode="singleChoice"

为了textColor,只需将这个选择器放在color文件夹中(重要提示,不是drawable文件夹!):
<item android:state_activated="true"  android:color="@color/your_selected_textColor" />
<item android:state_pressed="true" android:color="@color/your_pressed_textColor" />
<item android:color="@color/your_normal_textColor"></item>

这是一个样例行模板。

<ImageView
    android:id="@+skinMenu/lblIcon"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:src="@drawable/menu_catalog" />

<TextView
    android:id="@+skinMenu/lblTitle"
    style="@style/superlabelStyle"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_marginLeft="20dp"
    android:gravity="center_vertical"
    android:text="test menu testo"
    android:textColor="@color/menu_textcolor_selector"
    android:textSize="20dp"
    android:textStyle="bold" />

希望一切都能顺利进行,无需繁琐的解决方案。希望这可以帮到你。


2

我不确定如何通过选择器本身来实现您想要的效果--毕竟,根据定义,整个列表只有一个选择器。

但是,您可以控制选择更改并绘制任何您想要的内容。在这个示例项目中,我将选择器设置为透明,并在选定的项上绘制了一条线。


确实,最多只有一个选择器是完全合理的。我想我(或曾经)对默认选择器中存在的<item android:state_focused="false" android:drawable="@android:color/transparent" />子句感到有些困惑。 - shilgapira
标记,和吉尔遇到同样的问题,我尝试了你的解决方案,只适用于轨迹球,当你仅使用触摸来触摸项目时,不会调用nothingSeelected,并且将触发onItemClicked。如果你想要那种类型的东西,最好在ListView内跟踪所选和按下的位置,并在Item本身(制作自定义视图)和ListView的dispatchDraw中绘制所需的内容。 - codeScriber
@codeScriber:"nothingSelected" 在你只使用触摸操作并触摸项目时被调用,而 "onItemClicked" 则会被触发。这是完全正常的行为,因为触摸与选择无关。 - CommonsWare

-3

FrostWire 团队在这里。

所有选择器 API 都不能按预期工作。在尝试了本主题中提出的所有解决方案都无效后,我们只是在填充 ListView 项时解决了问题。

  1. 确保您的项目保持其状态,我们将其作为MenuItem的成员变量(布尔值selected)

  2. 在填充时,请询问底层项目是否已选择,如果是,则只需设置您想要作为背景的可绘制资源(无论是9patch还是其他)。确保您的适配器知道这一点,并在选择某些内容时调用notifyDataChanged()。

        @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View rowView = convertView;
        if (rowView == null) {
            LayoutInflater inflater = act.getLayoutInflater();
            rowView = inflater.inflate(R.layout.slidemenu_listitem, null);
            MenuItemHolder viewHolder = new MenuItemHolder();
            viewHolder.label = (TextView) rowView.findViewById(R.id.slidemenu_item_label);
            viewHolder.icon = (ImageView) rowView.findViewById(R.id.slidemenu_item_icon);
            rowView.setTag(viewHolder);
        }
    
        MenuItemHolder holder = (MenuItemHolder) rowView.getTag();
        String s = items[position].label;
        holder.label.setText(s);
        holder.icon.setImageDrawable(items[position].icon);
    
        //这里就是魔法所在
        rowView.setSelected(items[position].selected);
    
        rowView.setBackgroundResource((rowView.isSelected()) ? R.drawable.slidemenu_item_background_selected : R.drawable.slidemenu_item_background);
    
        return rowView;
    }
    

如果选择器真的可以工作,那就太好了,在理论上这是一个不错的而且优雅的解决方案,但是它似乎已经坏了。KISS。


我想尝试这个解决方案,你能否给出更详细的解释或完整的解决方案?谢谢。 - Andrew Lam

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