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

时间:2016-04-18 14:36:41

标签: android listview

图像显示问题:Venus项已滚动但其选择可见。

Venus item is scrolled out but it's selection is visible

我看过Hanged listSelector in ListView并且我正在使用形状/渐变,但仍然没有运气。

设备:适用于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>

列表选择器

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

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>

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>

活动代码

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);
    }
}

其中Planets只是一个字符串数组(太阳,水星,金星,地球,火星,木星......)而myitem只是一个TextView的自定义高度。< / p>

我哪里错了,拜托?

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

2 个答案:

答案 0 :(得分:4)

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

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

您的列表选择器代码:

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

修复

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

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

例如:

适配器代码:

ArrayAdapter adapter = ArrayAdapter.createFromResource(this, 
    R.array.Planets, R.layout.myitem);

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

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>

或者如果你很懒:

ArrayAdapter adapter = ArrayAdapter.createFromResource(this, 
    R.array.planets_array, android.R.layout.simple_list_item_activated_1);

进一步阅读

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

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

  • mSelector:这是选择器的Drawable(您使用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();
    }
    ...
}

上面的源代码段已经从原版进行了编辑,以包含评论。

在这里,我们终于找到解释观察到的行为的逻辑:列表选择器在mSelectorPosition == INVALID_POSITION时仅 隐藏,或者在英语中没有选定项目时隐藏。否则,如果项目在屏幕上,它将定位在所选项目上,否则不会对其位置进行任何更改。

因此,当您滚动ListView并且所选项目离开屏幕时,列表选择器只会保留在所选项目解释 ghost 列表选择器的最后位置。< / p>

最后的想法

从使用ListViews开始,我不得不说整个事情设计得不是很好而且可能非常错误。我强烈建议您尽可能使用它的继任者RecyclerView

答案 1 :(得分:0)

也许你可以尝试添加android:layerType =&#34;软件&#34;在ListView中。

<#import "/spring.ftl" as spring />

<#include "../layout/header.ftl">

    <div class="container page-content">
        <h2>Registrati ai servizi</h2>
        <!-- steps indicator -->
        <!-- inserire sempre classe "last" su ultimo elemento -->
        <!-- classi valide: "active" su elemento attivo e "done" su elementi precedenti ad "active" -->
        <ul id="progress" class="clearfix">
            <li class="step1 done">
                <p><span>Passaggio</span> 1</p>
            </li>
            <li class="step2 done">
                <p><span>Passaggio</span> 2</p>
            </li>
            <li class="step3 active">
                <p><span>Passaggio</span> 3</p>
            </li>
            <li class="step4 last">
                <p><span>Passaggio</span> 4</p>
            </li>
        </ul>
        <!-- steps indicator -->
        <div class="form-container summary">
            <h3>Riepilogo dati</h3>
            <div class="data-show">
                <p class="data-text">Codice fiscale:</p>
                <p class="data-value">${iscrizioneStep3Form.iscrizioneStep1Form.codiceFiscale}</p>
            </div>
            <div class="data-show">
                <p class="data-text">Nome:</p>
                <p class="data-value">${iscrizioneStep3Form.iscrizioneStep2Form.nome}</p>
            </div>
            <div class="data-show">
                <p class="data-text">Cognome:</p>
                <p class="data-value">${iscrizioneStep3Form.iscrizioneStep2Form.cognome}</p>
            </div>
            <div class="data-show">
                <p class="data-text">Data di nascita:</p>
                <p class="data-value">${iscrizioneStep3Form.dataNascita}</p>
            </div>
            <div class="data-show">
                <p class="data-text">Nazione:</p>
                <p class="data-value">${iscrizioneStep3Form.iscrizioneStep2Form.codiceNazione?substring(3)}</p>
            </div>
            <div class="data-show">
                <p class="data-text">Provincia:</p>
                <p class="data-value">${iscrizioneStep3Form.iscrizioneStep2Form.codiceProvincia?substring(4)}</p>
            </div>
            <div class="data-show">
                <p class="data-text">Comune:</p>
                <p class="data-value">${iscrizioneStep3Form.iscrizioneStep2Form.codiceComune?substring(6)}</p>
            </div>
            <div class="data-show">
                <p class="data-text">Sesso:</p>
                <p class="data-value">${iscrizioneStep3Form.iscrizioneStep2Form.sesso}</p>
            </div>

            <div class="data-show">
                <p class="data-text">Email:</p>
                <p class="data-value">${iscrizioneStep3Form.iscrizioneStep2Form.email}</p>
            </div>
            <div class="data-show">
                <p class="data-text">Termini e condizioni:</p>
                <p class="data-value">${iscrizioneStep3Form.iscrizioneStep2Form.condizioniGenerali?string("Acconsento", "Non acconsento")}</p>
            </div>
                <div class="data-show">
                <p class="data-text">Trattamento dei dati personali:</p>
                <p class="data-value">${iscrizioneStep3Form.iscrizioneStep2Form.informativaPrivacy?string("Acconsento", "Non acconsento")}</p>
            </div>
            <form id="reg-form" name="reg-form" action="<@spring.url '/registrazione/step4' />" method="post">
                <fieldset>
                    <input type="hidden" name="codiceFiscale" value="${iscrizioneStep3Form.iscrizioneStep1Form.codiceFiscale}">
                    <input type="hidden" name="nome" value="${iscrizioneStep3Form.iscrizioneStep2Form.nome}">
                    <input type="hidden" name="cognome" value="${iscrizioneStep3Form.iscrizioneStep2Form.cognome}">
                    <input type="hidden" name="email" value="${iscrizioneStep3Form.iscrizioneStep2Form.email}">
                    <input type="hidden" name="dataNascita" value="${iscrizioneStep3Form.dataNascita}">
                    <input type="hidden" name="nazione" value="${iscrizioneStep3Form.iscrizioneStep2Form.codiceNazione}">
                    <input type="hidden" name="provincia" value="${iscrizioneStep3Form.iscrizioneStep2Form.codiceProvincia}">
                    <input type="hidden" name="comune" value="${iscrizioneStep3Form.iscrizioneStep2Form.codiceComune}">
                    <input type="hidden" name="sesso" value="${iscrizioneStep3Form.iscrizioneStep2Form.sesso}">
                    <input type="hidden" name="condizioniGenerali" value="${iscrizioneStep3Form.iscrizioneStep2Form.condizioniGenerali?c}">
                    <input type="hidden" name="informativaPrivacy" value="${iscrizioneStep3Form.iscrizioneStep2Form.informativaPrivacy?c}">

                    <button type="submit" class="btn btn-block submit-btn" aria-label="conferma i tuoi dati" tabindex="10">Conferma i tuoi dati</button>

                </fieldset>
            </form>
            <div class="footer-form">
                <a href="javascript:history.back()"><span class="glyphicon glyphicon-menu-left"></span> Indietro</a> 
            </div>
        </div>
    </div>

<#include "../layout/footer.ftl">