2010-01-11 14 views
8

Hallo zusammen,Android Listview-Selection Problem

ich für die folgende lange Frage entschuldigen ...

Ich habe ein Linearlayout, die ein Listview und einige andere Elemente enthält. Wie bei der ListView ist jedes seiner Zeilen ein LinearLayout, das 3 Ansichten enthält - Checkbox, ImageView und TextView (von links nach rechts - horizontal). Da ich wollte, dass die ganze Reihe ausgewählt wurde, wenn der Trackball verwendet wurde (um mit einer Hintergrundfarbe hervorgehoben zu werden), stellte ich alle drei Ansichten innerhalb der LinearLayout Reihe als nicht fokussierbar ein und es funktionierte.

Jetzt habe ich 2 Probleme mit diesem ListView. Zuerst möchte ich, dass, wenn ich eine Zeile in der ListView (mit dem Finger) berühren, um das gleiche Verhalten wie bei der Verwendung des Trackballs zu erreichen - bedeutet, dass die Zeile ausgewählt (hervorgehoben) werden soll. Was gerade passiert, ist, dass wenn ich die Zeile berühre, diese wirklich ausgewählt wird, aber wenn ich meinen Finger loslasse, ist die Auswahl weg (ähnlich wie in der Kontaktliste des Geräts).

Zweitens - aus einem Menü, kann ich ein neues LinearLayout statt der anzeigen, die die ListView enthält (Bildschirm der anderen Anwendung). Wenn das passiert, speichere ich noch das Objekt des LinearLayout, das die ListView enthält, weil ich es später wieder anzeigen möchte, ohne es neu zu erstellen. Das Problem ist, dass, wenn ich das LinearLayout mit dem ListView wieder disaply, keine Zeile der ListView ausgewählt wird, auch wenn eine Zeile ceratin ausgewählt wurde, wenn der LinearLayout mit der ListView den Bildschirm "verließ".

Entschuldigung nochmal für die lange Post.

Danke!

Antwort

3
  1. Dies ist das Standardverhalten und das erwartete Verhalten. Manipulieren mit diesem ist stark vorgeschlagen gegen.
  2. Dies folgt aus 1. in gewissem Maße. Der ausgewählte Status ist nicht persistent. Weisen Sie stattdessen einen OnItemClickListener zu und lassen Sie die ID des ausgewählten Elements in einer Variablen speichern. Wenn Sie das Element erneut auswählen müssen, wenn Sie zurückkommen, können Sie setSelection()
+0

Ich habe bereits versucht, setSelection() zu verwenden, wenn Sie zurückkommen, aber es ist nicht gut - zuerst wird die Zeile überhaupt nicht hervorgehoben (keine Hintergrundfarbe), und zweitens befindet sich die Zeile am oberen Rand des Bildschirms obwohl Es war irgendwo in der Mitte, als ich ging. Danke. – WhiteTigerK

+0

Eine andere Sache - in der Kontaktliste des Geräts, wenn Sie den Trackball verwenden, um einen Kontakt auszuwählen und dann den Trackball drücken, um seine Details anzuzeigen, wird der Status der Kontaktliste gespeichert, so dass der Kontakt immer noch ausgewählt ist Der Standort auf dem Bildschirm ist derselbe. Daher gehe ich davon aus, dass dieses Verhalten erreicht werden kann. Danke. – WhiteTigerK

+0

Ja, der Auswahlstatus wird gespeichert, aber wenn Sie den Bildschirm berühren, wird der * Berührungsmodus * umgeschaltet. Im Berührungsmodus gibt es, wie Sie bereits bemerkt haben, keine visuelle Rückmeldung für einen ausgewählten Gegenstand. nur kurzes Feedback, wenn der Gegenstand berührt wird, was nicht passiert, wenn Sie zur Listenansicht zurückkehren). Dies ist das Standardverhalten. ändere es nicht. –

0

verwenden Dies sollte helfen:

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

funktioniert nicht. in tritt die Methode aber nichts passiert –

1

ich gelöst habe dieses Problem eine Nachricht nach notifyDataSetChanged() ; Senden und ruft setSelection(0) in meine HandleMessage-Funktion. Es scheint hässlich, aber half mir mehrmals, wenn SDK seltsam verhält.

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

Oben hilft mir, eine Zeile fokussiert in der Liste zu setzen.

+2

Hmm das ist nicht korrekt. 'setSelection' wählt das i-te Element der Liste, aber 'getChildAt' gibt * visible * child zurück, d. h. es hängt vom aktuellen Bildlauf ab. Wenn die Liste gescrollt wird, funktioniert das nicht. – smok

+0

Stimmen Sie mit @smok – Divers

19

Ja, Aus Sicht des iOS-Entwickler, finde ich, dass es extrem schwierig ist, Funktionen anwenden wie „Satz Standardauswahl, wenn Start“ und „erinnern Auswahlstatus nach Benutzer Zeile geklickt“ zu Listview.

Lassen Sie uns also mit beginnen „erinnern Auswahl“ first.The Problem ist, dass selbst wenn Sie wissen, dass Sie Selektor xml verwenden können highlight/gedrückt/Fokus style.but diesem Stil zu definieren, wird nicht nach Benutzer gehalten werden Diese Zeile wurde angeklickt.Zum Beispiel habe ich eine Hervorhebung Wähler xml (list_selector.xml unter res/ziehbar Ordner) wie folgt aus (aber Sie können auch andere Felder haben müssen wie die Textfarbe von Textview in Reihe markieren):

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

und list_selector_pressed. xml, die die Hervorhebung Stil definiert - die Hintergrundfarbe zu einer grauen Farbe:

<?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 wie @ David Hedlund vorgeschlagen:

Vielmehr weisen Sie einen OnItem ClickListener und speichert die ID des ausgewählten Elements in einer Variablen.

benötigen Sie eine Instanz-Variable auf Ihre Klasse zu erstellen:

private View currentSelectedView; 

gehen dann zu

@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 
    } 

Ganz einfach: wir überprüfen, ob currentSelectedView null oder aktuelle geklickt Ansicht ist oder nicht. Wir heben zuerst jeden Stil auf, indem wir die Methode unhighlightCurrentRow aufrufen (currentSelectedView) --- Sie fragen sich vielleicht, warum wir die momentane Variable currentSelectedView als Parameter übergeben, ich werde es später erklären. Dann weisen wir currentSelectedView eine Ansicht zu und markieren die aktuelle Zeile; damit der Stil beibehalten wird, nachdem der Benutzer geklickt hat.

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, das ist es. So implementieren wir "remember selection" für die Listenansicht. Wie Sie sehen, müssen wir die Codes duplizieren sowohl in XML und Java-Code Styling - ziemlich dumm :(

Weiter über „set Standardauswahl“ Sie mögen denken, dass Sie dies tun können

.
listView.setAdapter(adatper) 
listView.setSelection(0); 
currentSelectedView = listView.getChildAt(0); 
highlightCurrentRow(currentSelectedView); 

in onCreate() in der Aktivität oder onActivityCreated() in Fragmente.
Aber wenn Sie es ausführen, werden Sie Nullpointer Ausnahme und warum? bekommen zu diesem Zeitpunkt, da wird die Listenansicht noch nicht gerendert und Android nicht wie iOS, die ViewWillAppear haben, SO müssen Sie eine Instant-Variable erstellen, um zu erinnern, ob es ist

So unter currentSelectedView Erklärung:

private Boolean firstTimeStartup = true; 

fügen Sie dann Methoden: zum ersten Mal Listview-Zelle und in onListItemClick unscharf zu schalten diese Variable zu machen nehmen wir die erste Zeile in der Listenansicht hervorheben möchten:

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

Ziemlich einfach. Aber Sie müssen einige Änderungen in onListItemClick Methode machen:

@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 
} 

Dort gehen Sie! Enjoy Android :)

+0

meine Erfahrung hier in Bezug auf die Erinnerung/Hervorhebung Teil des Codes war, dass der eine muss nicht die Xml und die programmatischen Einstellungen duplizieren. Es ist eher so, dass die XML-Dateien nicht funktionierten und die programmatischen nicht. In diesem Sinne könnte man die Erstellung von 'list_selector.xml' und' list_selector_pressed.xml' überspringen und einfach den von Ihnen beschriebenen Code schreiben. – Thomas

0

Ich benutze einen Adapter und wollte nicht benutzerdefinierte Hintergrundfarben, sondern verwenden Sie die Android: state_selected in Zeichnungs-XML. SetSelection funktionierte nicht für mich, aber vielleicht auch, weil ich SetNotifyDataChanged brauchte, was zeigt, dass der ausgewählte Status nicht persistent ist.

Ich fand auch, dass der Selected-Status für ein Element in einer ListView nicht persistent ist, da SetNotifyDataChanged das Aktualisieren des ListView-Layouts bewirkt, das sie alle löscht. Das Festlegen des Elements auf Ausgewählt in der GetView des Adapters ist auch zu früh.

Schließlich habe ich den Selected-Status für die Ansicht des ausgewählten Elements festgelegt, nachdem das Layout der Listenansicht geändert wurde, wenn das LayoutChange-Ereignis ausgelöst wird (in Java ist es wahrscheinlich ein An OnLayoutChangeListener der ListView anhängen).

Um es wirklich einfach zu machen, speichere ich die Ansicht des ausgewählten Elements als SelectedItemView des Adapters. In der LayoutChange Eventhandler ListView ich nur die SelectedItemView.Selected des Adapters auf True.

Hier ist der Code aus meiner Tätigkeit, wo ich den Adapter für das Listview gesetzt und abonnieren auch LayoutChange (oder in Java fügen Sie eine 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; 
     }; 

für den Adapter mein Code hier:

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

Eine viel sauberere Lösung für mich drehte sich um die Tatsache, dass, wenn Sie entweder android:choiceMode="singleChoice" oder android:choiceMode="multipleChoice" in Ihrem ListView, dann ListView wird versuchen, den Status für entweder als überprüft zu halten einzelne oder mehrfache Auswahl ListView. Die gotcha ist, dass es auf der Liste Zelle hängt also Checkable

Implementierung starten Sie durch eine Implementierung CheckableLinearLayout (oder FrameLayout könnte besser sein), so dass Android wird den Überprüfungsstatus für Sie halten und Sie können synchron die Auslosungs des halten. Der Schlüssel hier ist refreshDrawableState und onCreateDrawableState Sie aktualisieren die Drawable, so dass der Hintergrund markiert markiert wird oder nicht.

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

Dann wird eine einfache selector für den Hintergrund ziehbar, die den Überprüfungsstatus zeigen:

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

Jetzt kann ich CheckableLinearLayout in jeder Liste verwenden, die ich persistent Auswahlzustand (mehrere oder einzelne zeigen wollen):

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

ich im Fragment einen besonderen Code brauche nicht den Überprüfungsstatus zu aktualisieren, und ich kann die gewählten halten Position um eine Drehung mit:

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

und

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

ebenfalls, kann ich setItemChecked verwenden, um ein anfänglich ausgewähltes Element zu setzen, oder getCheckedItemPosition (oder getCheckedItemPositions() für Mehrfachauswahl) verwenden, um die aktuelle Auswahl zu bekommen.

+0

Arbeitete in meinem Anwendungsfall. Vielen Dank. – MCLLC