滚动项目时,Android listSelector仍然部分可见

Ser*_*erg 12 android listview

图像显示问题:Venus项目已滚动但其选择是可见的.

金星项目已滚动,但它的选择是可见的

在ListView中查看了Hanged listSelector,我正在使用形状/渐变,但是仍然没有运气.

设备:适用于Android的MS VS Emulator,Android 4.2 XHDPI API-17
IDE:适用于Windows 7 WM的Android Studio 2.

布局,主要活动

<?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" >

    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:choiceMode="singleChoice"
        android:drawSelectorOnTop="false"
        android:scrollingCache="false"
        android:animationCache="false"
        android:listSelector="@drawable/list_selector">
    </ListView>

</LinearLayout>
Run Code Online (Sandbox Code Playgroud)

列表选择器

<?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/item_pressed" />
    <item
        android:drawable="@drawable/item_selected" />
</selector>
Run Code Online (Sandbox Code Playgroud)

drawables,item_pressed

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >
    <gradient
        android:angle="90"
        android:endColor="#0000ff"
        android:startColor="#0000ff" />

</shape>
Run Code Online (Sandbox Code Playgroud)

item_selected

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >
    <gradient
        android:angle="90"
        android:endColor="#00ff00"
        android:startColor="#00ff00" />

</shape>
Run Code Online (Sandbox Code Playgroud)

和活动代码

package com.example.listdemo;

import android.app.ListActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;

public class MainActivity extends ListActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ArrayAdapter adapter = ArrayAdapter.createFromResource(this, R.array.Planets, R.layout.myitem);
        setListAdapter(adapter);
    }
}
Run Code Online (Sandbox Code Playgroud)

在哪里Planets只是一个字符串数组(太阳,水星,金星,地球,火星,木星...),myitem只是一个TextView自定义高度.

我哪里错了,拜托?

编辑 为了澄清问题,它是关于默认列表选择行为.这是关于没有android:state_xxx属性的列表选择器项.不要太注意相应的可绘制名称.我准备重命名@drawable/item_selected@drawable/item_default.让我知道它是否有助于澄清问题,我将重命名.

idu*_*olz 4

tl;dr不要在列表选择器上设置默认的 Drawable。

当您为列表选择器提供默认的 Drawable 时,就会出现此问题。我的意思是,在您的列表选择器定义中,您有一个item没有状态要求的标签,这会item无意中使其成为默认的Drawable。您可以在此处阅读有关选择器的更多信息。

您的列表选择器代码:

<?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/item_pressed" />
    <item
        android:drawable="@drawable/item_selected" /> <-- this is what I'm referring to
</selector>
Run Code Online (Sandbox Code Playgroud)

修复

您遇到的问题是由始终绘制列表选择器引起的(即使选择器不在屏幕上)。通常这不是问题,因为列表选择器是透明的(因此不可见)。然而,由于您为列表选择器提供了默认背景,这意味着每当列表选择器出现在屏幕上时,它就会可见,从而导致您观察到的奇怪行为。相反,您真正想要的是仅在实际选择项目时显示此背景。

为此,首先我们必须从列表选择器中删除默认背景。那么我们需要一种新的方式来指示所选项目。由于您在 ListView 中指定android:choiceMode="singleChoice",因此 ListView 会将您的列表项视为复选框列表。因此,当用户检查其中一项时,其激活状态将设置为 true。但是,默认激活时 TextView 不会显示任何视觉效果。要在选择时显示特定背景,我们需要使用可以显示激活状态的列表项布局。实现此目的的一种方法是将 ListView 项视图的背景更改为选择器,并定义要用于激活状态的 Drawable。

例如:

适配器代码:

ArrayAdapter adapter = ArrayAdapter.createFromResource(this, 
    R.array.Planets, R.layout.myitem);
Run Code Online (Sandbox Code Playgroud)

myitem.xml:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    style="?android:attr/spinnerItemStyle"
    android:singleLine="true"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/item_background"
    android:paddingTop="16dp"
    android:paddingBottom="16dp"/>
Run Code Online (Sandbox Code Playgroud)

item_background.xml:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/item_selected" android:state_activated="true" />
</selector>
Run Code Online (Sandbox Code Playgroud)

或者如果你很懒:

ArrayAdapter adapter = ArrayAdapter.createFromResource(this, 
    R.array.planets_array, android.R.layout.simple_list_item_activated_1);
Run Code Online (Sandbox Code Playgroud)

进一步阅读

从Android的文档来看,并不完全明显我说的是真的,所以你可能会问我的答案是否可信。本节专门针对那些寻求可靠答案的人和非常好奇的人。

要了解列表选择器的工作原理及其编程方式,我们需要深入研究 Android 源代码。首先,逻辑的核心ListView实际上保存在一个名为的类中AbsListView(如果您没有下载源代码,您可以参考这个)。深入研究此类的源代码,我们会发现一些与选择器相关的有用字段/函数:

  • mSelector:这是选择器的可绘制对象(您用 指定的选择器android:listSelector
  • mSelectorRect:该字段决定选择器绘制的位置以及选择器的大小
  • mSelectedPosition:存储所选项目的索引(该字段实际上是在类的更深处声明的AdapterView
  • positionSelector(...):更新应绘制选择器的位置
  • drawSelector(...):绘制选择器
  • trackMotionScroll(...)ListView: 包含的滚动行为的逻辑

现在我们已经了解了环境,我们终于可以理解列表选择器行为的核心逻辑了。这一切都归结为以下几行代码trackMotionScroll(...)

boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
    ...
    if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
        // if we are not in touch mode and there is a selected item then
        // we do a quick check if the selected item is on screen
        final int childIndex = mSelectedPosition - mFirstPosition;
        if (childIndex >= 0 && childIndex < getChildCount()) {
            // if the selected item is on screen, we move the selector to
            // where the selected item is
            positionSelector(mSelectedPosition, getChildAt(childIndex));
        }
    } else if (mSelectorPosition != INVALID_POSITION) {
        // if we are in touch mode and there is a selected item then
        // we do a quick check if the selected item is on screen
        final int childIndex = mSelectorPosition - mFirstPosition;
        if (childIndex >= 0 && childIndex < getChildCount()) {
            // if the selected item is on screen, we move the selector to
            // where the selected item is
            positionSelector(INVALID_POSITION, getChildAt(childIndex));
        }
    } else {
        // otherwise, if nothing is selected, hide the selector (don't draw it)
        mSelectorRect.setEmpty();
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

上面的源代码片段已根据原始内容进行了编辑,以包含注释。

在这里,我们最终找到了解释所观察到的行为的逻辑:列表选择器mSelectorPosition == INVALID_POSITION在没有选定项目时隐藏。否则,如果项目在屏幕上,则它位于所选项目处,否则不会更改其位置。

因此,当您滚动ListView并且所选项目离开屏幕时,列表选择器只会保留在所选项目解释观察到的幽灵列表选择器的最后位置。

最后的想法

从ListViews 推出以来的使用过程中,我不得不说整个事情的设计不是很好,并且可能存在极大的错误。RecyclerView我强烈建议您尽可能使用它的后继者。