8

So che dovrei misurare i bambini in onMeasure() e disporli in onLayout(). La domanda è in che modo questi metodi dovrei aggiungere/riciclare le visualizzazioni in modo da poter misurare tutti i bambini insieme con un occhio su come sono posizionati reciprocamente (cioè griglia, elenco o altro)?Modo corretto per implementare onMeasure() e onLayout() in AdapterView personalizzato

Il mio primo approccio è stato quello di aggiungere/riciclare viste in onLayout() ma da quel punto non posso misurare i miei figli perché non sono ancora state aggiunte ai AdapterView e getChildCount() ritorna 0 in onMeasure(). E non posso misurare AdapterView stesso senza che i bambini siano già strutturati perché in realtà dipende dalle loro posizioni reciproche, giusto?

Sono davvero confuso con il processo di layout Android in AdapterView quando i bambini vengono aggiunti/rimossi dinamicamente.

+0

Non ho idea di come ci si avvicinerebbe a questo. Personalmente, vorrei lavorare su 'RecyclerView'. Lì, il lavoro di stendere i bambini è gestito da una classe manageriale dedicata e collegabile. È disponibile un'API più chiara e sono già disponibili gestori di terze parti che è possibile utilizzare (insieme ai tre in "recyclerview-v7") per vedere come affrontare il problema. – CommonsWare

risposta

1

Non riesco a pubblicare un commento perché sono un nuovo utente, ma puoi descrivere COSA stai cercando di fare, al contrario di COME stai provando a farlo? Spesso, scoprirai che si tratta di una questione di design piuttosto che di codice. Soprattutto se provieni da una piattaforma diversa (ad esempio, iOS). Dall'esperienza, ho scoperto che la misurazione e i layout manuali in Android sono per lo più inutili se si progetta il layout correttamente in base alle esigenze aziendali.

MODIFICA: Come ho già detto, questo può essere risolto utilizzando alcune decisioni di progettazione. Userò il tuo esempio Nodi/Elenco (sperando che questo sia il tuo caso d'uso reale, ma la soluzione può essere estesa per un problema più generale).

Quindi, se ci pensiamo bene la vostra intestazione come un commento in un forum, e la lista di risposte al tuo commento, possiamo fare la seguente ipotesi:

  1. Una lista è abbastanza, non due. Ogni elemento nell'elenco può essere un'intestazione (commento) o una voce di elenco (risposta). Ogni risposta è un commento, ma non tutti i commenti sono risposte.

  2. Per l'articolo n, so se si tratta di un commento o una risposta (vale a dire un'intestazione o un elemento nell'elenco).

  3. Per l'articolo n, ho un membro booleano isVisible (default false; View.GONE).

Ora, è possibile utilizzare i seguenti componenti:

  1. un adattatore estesa classe
  2. Due XMLs layout: uno per il tuo commento, uno per la risposta. Puoi avere commenti illimitati e ogni commento può avere risposte illimitate. Entrambi soddisfano le tue esigenze.
  3. La classe del contenitore di frammenti o attività che implementa OnItemClickListener per mostrare/nascondere l'elenco.

Quindi vediamo un po 'di codice, vero?

primo luogo, il file XML:

fila Commento (l'intestazione)

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

Ora la risposta di fila (un elemento nella lista)

<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, ora la scheda classe

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 bene, ora abbiamo una lista di intestazioni con bambini nascosti (ricorda, abbiamo impostato la visibilità predefinita dei bambini su "andato"). Non è quello che volevamo, quindi sistemiamolo.

vostro contenitore di classe (frammento o attività) si avrà la seguente definizione XML

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

E il tuo onCreateView attuerà OnItemClickListener e hanno i seguenti

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 
} 

Ora per visualizzare l'elenco quando si fare clic su un'intestazione

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

    adapter.changeVisibility(position); 

} 

Ora, se un elemento è c leccato e quell'elemento ha un genitore (ad es. voce dell'elenco), verrà mostrato/nascosto in base al suo stato corrente.

Alcuni commenti sul codice:

  1. Ho scritto questo WordPad come io non ho un ambiente dev a portata di mano. Ci scusiamo per eventuali errori di compilazione.

  2. Questo codice può essere ottimizzato: se si dispone di un set di dati molto grande, questo codice sarebbe lento poiché si sta ridisegnando l'intero elenco su ogni chiamata a changeVisibility(). È possibile mantenere due elenchi (uno per le intestazioni, uno per gli elementi dell'elenco) e in ChangeVisibility è possibile eseguire query solo sugli elementi dell'elenco).

  3. Ho rinforzato l'idea che alcune decisioni di progettazione avrebbero reso la vita molto più facile. Ad esempio, se gli elementi dell'elenco erano in realtà solo un elenco di etichette, è possibile avere un file XML personalizzato (per l'intestazione) e una vista ListView al suo interno che è possibile impostare su View.GONE. Ciò farà sì che tutte le altre visualizzazioni facciano finta di non essere nemmeno lì e che il tuo layout funzionerà correttamente.

Spero che questo aiuti.

+0

Grazie per la risposta. Il mio obiettivo è rendere il lavoro 'wrap_content' per il mio AdapterView personalizzato, cioè se il numero di elementi dati dall'adattatore è piccolo, quindi AdapterView cerca di NON ottenere tutto lo spazio disponibile. – vganin

+0

Un caso d'uso particolare: menu a due livelli, in cui i nodi sono viste composte costituiti da intestazione (sempre visibile) ed elenco (si espande sull'evento di intestazione click). Elenco è il mio AdapterView personalizzato. L'elenco può contenere qualsiasi numero di elementi. Il problema è che la lista cerca sempre di ottenere tutta l'altezza. – vganin

+0

EDIT: Grazie per lo sforzo che mi hai dedicato. In realtà ho già pensato a come l'hai fatto e ci sono degli ostacoli che mi impediscono di farlo esattamente in quel modo. Vedete, la caratteristica principale del mio AdapterView personalizzato è che ha animato il selettore 'Drawable' e ho bisogno che questo selettore si sposti dai nodi dei commenti per rispondere senza problemi ai nodi E i nodi di risposta devono essere organizzati sui gruppi in modo da poter usare un'altra animazione per espandere/collassali insieme. Tuttavia sono riuscito a raggiungere questo obiettivo in modo molto complesso. – vganin

Problemi correlati