8

Ich weiß, dass ich Kinder in onMeasure() messen und sie in onLayout() Layout. Die Frage ist in welcher dieser Methoden sollte ich Ansichten hinzufügen/recyceln, so dass ich alle Kinder zusammen mit einem Auge auf, wie sie gegenseitig (d. H. Gitter, Liste oder was auch immer) gemessen werden könnte?Korrekter Weg zur Implementierung onMeasure() und onLayout() in benutzerdefinierten AdapterView

Mein erster Ansatz war hinzufügen/recyceln Ansichten in onLayout() aber von diesem Punkt kann ich meine Kinder nicht messen, weil sie nicht zu AdapterView hinzugefügt werden noch und getChildCount() kehrt 0 in onMeasure(). Und ich kann AdapterView selbst nicht messen, ohne dass die Kinder bereits layouted sind, weil es wirklich von ihren gegenseitigen Positionen abhängt, richtig?

Ich bin wirklich verwirrt mit Android Layout-Prozess in AdapterView, wenn Kinder dynamisch hinzugefügt/entfernt werden.

+0

Ich habe keine Ahnung, wie Sie das angehen würden. Persönlich würde ich von 'RecyclerView' arbeiten. Dort wird die Arbeit des Kinderaufbaus von einer dedizierten, steckbaren Managerklasse übernommen. Es gibt eine klarere API, und es gibt bereits Drittanbieter-Manager, die Sie verwenden können (zusammen mit den drei, die in "recyclerview-v7" versenden), um zu sehen, wie Sie das Problem angehen. – CommonsWare

Antwort

1

Ich kann keinen Kommentar posten, weil ich ein neuer Benutzer bin, aber kannst du beschreiben, WAS du versuchst zu tun, im Gegensatz zu WIE du es versuchst? Häufig werden Sie feststellen, dass dies im Gegensatz zur Codierung ein Problem des Designs ist. Vor allem, wenn Sie von einer anderen Plattform kommen (zB iOS). Aus Erfahrung habe ich festgestellt, dass Messungen und manuelle Layouts in Android meist unnötig sind, wenn Sie Ihr Layout entsprechend Ihren geschäftlichen Anforderungen richtig entwerfen.

EDIT: Wie bereits erwähnt, kann dies mit einigen Design-Entscheidungen gelöst werden. Ich werde Ihr Nodes/List-Beispiel verwenden (in der Hoffnung, dass dies Ihr tatsächlicher Anwendungsfall ist, aber die Lösung kann für ein allgemeineres Problem erweitert werden).

Also, wenn wir in einem Forum, und die Liste als Antworten auf Ihre Kommentare über Ihren Kopf als Kommentar denken, können wir die folgende Annahme machen:

  1. Eine Liste ist genug, nicht zwei. Jeder Eintrag in der Liste kann entweder ein Header (Kommentar) oder ein Listeneintrag (Antwort) sein. Jede Antwort ist ein Kommentar, aber nicht alle Kommentare sind Antworten.

  2. Für Artikel n, ich weiß, ob es ein Kommentar oder eine Antwort (d. H. Ist es eine Überschrift oder ein Element in Ihrer Liste).

  3. Für Element n habe ich ein boolesches Member isVisible (Standard false; View.GONE).

Jetzt können Sie die folgenden Komponenten verwenden:

  1. Eine erweiterte Adapterklasse
  2. Zwei-Layout XMLs: Eine für Ihren Kommentar, eine für Ihre Antwort. Sie können unbegrenzte Kommentare haben und jeder Kommentar kann unbegrenzte Antworten haben. Beide erfüllen Ihre Anforderungen.
  3. Ihre Fragment- oder Aktivitätscontainerklasse, die OnItemClickListener implementiert, um Ihre Liste ein-/auszublenden.

Also schauen wir uns einen Code an, sollen wir?

Zuerst Ihre XML-Dateien:

Kommentar Zeile (Ihr Header)

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
android:id="@+id/overall" 
android:layout_width="match_parent" 
android:layout_height="wrap_content" 
android:animateLayoutChanges="true"> 

<TextView 
    android:id="@+id/comment_row_label" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content"/> 
</RelativeLayout> 

Jetzt Ihre Antwort Zeile (ein Element in der Liste)

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
android:id="@+id/overall" 
android:layout_width="match_parent" 
android:layout_height="wrap_content"> <!-- this is important --> 
<TextView 
    android:id="@+id/reply_row_label" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:visibility="gone"/> <!-- important --> 
</RelativeLayout> 

Ok, jetzt Ihr Adapter Klasse

public class CommentsListAdapter extends BaseAdapter implements OnClickListener 
{ 

public static String TAG = "CommentsListAdapter"; 

private final int NORMAL_COMMENT_TYPE = 0; 
private final int REPLY_COMMENT_TYPE = 1; 

private Context context = null; 
private List<Comment> commentEntries = null; 
private LayoutInflater inflater = null; 

//All replies are comments, but not all comments are replies. The commentsList includes all your data. (Remember that the refresh method allows you to add items to the list at runtime. 
public CommentsListAdapter(Context context, List<Comment> commentsList) 
{ 
    super(); 

    this.context = context; 
    this.inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
    this.commentEntries = commentsList; 

} 
//For our first XML layout file 
public static class CommentViewHolder 
{ 
    public RelativeLayout overall; 
    public TextView label; 
} 

//For our second XML 
public static class ReplyViewHolder 
{ 
    public RelativeView replyOverall; 
    public TextView replyLabel; 
} 

@Override 
public int getViewTypeCount() 
{ 
    return 2; //Important. We have two views, Comment and reply. 
} 

//Change the following method to determine if the current item is a header or a list item. 
@Override 
public int getItemViewType(int position) 
{ 
    int type = -1; 
    if(commentEntries.get(position).getParentKey() == null) 
     type = NORMAL_COMMENT_TYPE; 
    else if(commentEntries.get(position).getParentKey() == 0L) 
     type = NORMAL_COMMENT_TYPE; 
    else 
     type = REPLY_COMMENT_TYPE; 

    return type; 
} 

@Override 
public int getCount() 
{ 
    return this.commentEntries.size(); //all data 
} 

@Override 
public Object getItem(int position) 
{ 
    return this.commentEntries.get(position); 
} 

@Override 
public long getItemId(int position) 
{ 
    return this.commentEntries.indexOf(this.commentEntries.get(position)); 
} 

@Override 
public View getView(int position, View convertView, ViewGroup parent) 
{ 
    CommentViewHolder holder = null; 
    ReplyViewHolder replyHolder = null; 

    int type = getItemViewType(position); 

    if(convertView == null) 
    { 
     if(type == NORMAL_COMMENT_TYPE) 
     { 
      convertView = inflater.inflate(R.layout.row_comment_entry, null); 
      holder = new CommentViewHolder(); 
      holder.label =(TextView)convertView.findViewById(R.id.comment_row_label); 
      convertView.setTag(holder); 
     } 
     else if(type == REPLY_COMMENT_TYPE) 
     { 
      convertView = inflater.inflate(R.layout.row_comment_reply_entry, null); 
      replyHolder = new ReplyViewHolder(); 
      replyHolder.replyLable = (TextView)convertView.findViewById(R.id.reply_row_label); 
      convertView.setTag(replyHolder); 
     } 
    } 
    else 
    { 
     if(type == NORMAL_COMMENT_TYPE) 
     { 
      holder = (CommentViewHolder)convertView.getTag(); 
     } 
     else if(type == REPLY_COMMENT_TYPE) 
     { 
      replyHolder = (ReplyViewHolder)convertView.getTag(); 
     } 
    } 
    //Now, set the values of your labels 
    if(type == NORMAL_COMMENT_TYPE) 
    { 
     holder.label.setTag((Integer)position); //Important for onClick handling 

     //your data model object 
     Comment entry = (Comment)getItem(position); 
     holder.label.setText(entry.getLabel()); 
    } 
    else if(type == REPLY_COMMENT_TYPE) 
    { 
     replyHolder = (ReplyViewHolder)convertView.getTag(); //if you want to implement onClick for list items. 

     //Or another data model if you decide to use multiple Lists 
     Comment entry = (Comment)getItem(position); 
     replyHolder.replyLabel.setText(entry.getLabel())); 

     //This is the key 
     if(entry.getVisible() == true) 
      replyHolder.replyLabel.setVisibility(View.VISIBLE); 
     else 
      replyHolder.replyLabel.setVisibility(View.GONE); 
    } 

    return convertView; 

} 

//You can use this method to add items to your list. Remember that if you are using two data models, then you will have to send the correct model list here and create another refresh method for the other list. 
public void refresh(List<Comment> commentsList) 
{ 
    try 
    { 
     this.commentEntries = commentsList; 
     notifyDataSetChanged(); 
    } 
    catch(Exception e) 
    { 
     e.printStackTrace(); 
     Log.d(TAG, "::Error refreshing comments list.");   
    } 
} 

//Utility method to show/hide your list items 
public void changeVisibility(int position) 
{ 
    if(this.commentEntries == null || this.commentEntries.size() == 0) 
     return; 
    Comment parent = (Comment)getItem(position); 
    for(Comment entry : this.commentEntries) 
    { 
     if(entry.getParent().isEqual(parent)) 
      entry.setVisible(!entry.getVisible()); //if it's shown, hide it. Show it otherwise. 
    } 
    notifyDataSetChanged(); //redraw 
} 

} 

Ok, jetzt haben wir eine Liste von Kopfzeilen mit versteckten Kindern (denken Sie daran, wir setzen die Standardsichtbarkeit von Kindern auf 'weg'). Nicht was wir wollten, also lasst uns das reparieren.

Ihre Container-Klasse (Fragment oder Aktivität) wird die folgende XML-Definition haben

<!-- the @null divider means transparent --> 
<ListView 
    android:id="@+id/comments_entries_list" 
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:divider="@null" 
    android:dividerHeight="5dp" /> 

Und Ihre onCreateView wird OnItemClickListener implementieren und haben die folgende

private ListView commentsListView = null; 
private List<Comment>comments = null; 
private static CommentsListAdapter adapter = null; 
.... 
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 
{ 
... 
//comments list can be null here, and you can use adapter.refresh(data) to set the data 
adapter = new CommentsListAdapter(getActivity(), comments); 
this.commentsListView.setAdapter(adapter); 
this.commentsListView.setOnClickListener(this); //to show your list 
} 

nun Ihre Liste zu zeigen, wenn Sie Klicken Sie auf eine Überschrift

@Override 
public void onItemClick(AdapterView<?> parent, View view, int position, 
     long id) 
{ 

    adapter.changeVisibility(position); 

} 

Jetzt, wenn ein Artikel c geleckt und dieser Gegenstand hat einen Elternteil (d. h. Listenelement), wird entsprechend seinem aktuellen Status angezeigt/ausgeblendet.

Einige Kommentare über den Code:

  1. Ich schrieb dies auf WordPad als ich praktisch keine dev Umwelt haben. Entschuldigung für irgendwelche Kompilierungsfehler.

  2. Dieser Code kann optimiert werden: Wenn Sie einen sehr großen Datensatz haben, wäre dieser Code langsam, da Sie die gesamte Liste bei jedem Aufruf von changeVisibility() neu zeichnen. Sie können zwei Listen verwalten (eine für Kopfzeilen, eine für Listenelemente) und in changeVisibility können Sie nur die Listenelemente abfragen.

  3. Ich bekräftige diese Idee, dass einige Designentscheidungen Ihr Leben viel einfacher machen würden. Wenn Ihre Listenelemente beispielsweise nur eine Liste von Labels sind, können Sie eine benutzerdefinierte XML-Datei (für Ihre Kopfzeile) und eine darin enthaltene ListView-Ansicht verwenden, die Sie auf View.GONE setzen können. Dadurch werden alle anderen Ansichten so aussehen, als wären sie nicht vorhanden und Ihr Layout funktioniert ordnungsgemäß.

Hoffe, das hilft.

+0

Danke für die Antwort. Mein Ziel ist es, 'wrap_content' für mein benutzerdefiniertes AdapterView arbeiten zu lassen, d. H. Wenn die Anzahl der vom Adapter angegebenen Elemente klein ist, dann versucht AdapterView, NICHT den gesamten verfügbaren Platz zu bekommen. – vganin

+0

Ein besonderer Anwendungsfall: Zwei-Ebenen-Menü, wobei Knoten zusammengesetzte Ansichten bestehen aus Header (immer sichtbar) und Liste (erweitert auf Header-Click-Ereignis). Liste ist mein benutzerdefiniertes AdapterView. Liste kann eine beliebige Anzahl von Elementen enthalten. Das Problem ist, dass die Liste immer versucht, alle Höhe zu bekommen. – vganin

+0

EDIT: Vielen Dank für die Mühe, die Sie mir gemacht haben. Eigentlich habe ich schon darüber nachgedacht, wie du es gemacht hast und es gibt Hindernisse, die mich davon abhalten genau das zu tun. Sie sehen, Hauptmerkmal meiner benutzerdefinierten AdapterView ist, dass es 'Drawable' Selektor animiert hat und ich brauche, dass dieser Selektor von Kommentarknoten bewegt, um Knoten glatt zu antworten UND Antwortknoten müssen auf Gruppen organisiert werden, damit ich noch eine andere Animation verwenden konnte, um zu erweitern/kollabiere sie zusammen. Aber ich habe es geschafft, dieses Ziel auf sehr komplexe Weise zu erreichen. – vganin