8

Я знаю, что я должен измерять детей в onMeasure() и размещать их в onLayout(). Вопрос в том, какие из этих методов следует добавлять/перерабатывать, чтобы я мог измерять всех детей вместе с тем, как они взаимно расположены (т. Е. Сетка, список или что-то еще)?Правильный способ реализации onMeasure() и onLayout() в пользовательском AdapterView

Мой первый подход, чтобы добавить/переработать взгляды в onLayout(), но с этого момента я не могу измерить моих детей, потому что они не будут добавлены AdapterView еще и getChildCount() возвращается 0 в onMeasure(). И я не могу измерить AdapterView самостоятельно, если дети уже не составлены, потому что это действительно зависит от их взаимных позиций, не так ли?

Я действительно запутался в процессе компоновки Android в AdapterView, когда дети добавляются/удаляются динамически.

+0

Я понятия не имею, как вы подходите к этому. Лично я работал бы с «RecyclerView». Там работа по выделению детей обрабатывается специальным, подключаемым классом менеджеров. Существует более понятный API, и есть уже сторонние менеджеры, которые вы можете использовать (вместе с тремя, отправляющими в 'recyclerview-v7'), чтобы увидеть, как подойти к проблеме. – CommonsWare

ответ

1

Я не могу оставлять комментарии, потому что я новый пользователь, но можете ли вы описать, ЧТО вы пытаетесь сделать, а не КАК вы пытаетесь это сделать? Часто вы обнаружите, что это проблема дизайна, а не кодирования. Особенно, если вы идете с другой платформы (например, iOS). По опыту я обнаружил, что измерение и мануалы в Android в большинстве случаев не нужны, если вы правильно планируете свой макет в свете потребностей своего бизнеса.

EDIT: Как я уже говорил, это может быть решено с использованием некоторых проектных решений. Я буду использовать пример Nodes/List (надеюсь, что это ваш фактический прецедент, но решение может быть расширено для более общей проблемы).

Так что, если мы думаем о своем заголовке как комментарий в форуме, и список в качестве ответов на ваш комментарий, мы можем сделать следующее предположение:

  1. Один список достаточно, не два. Каждый элемент в списке может быть либо заголовком (комментарием), либо элементом списка (ответ). Каждый ответ - это комментарий, но не все комментарии являются ответами.

  2. Для элемента n, я знаю, является ли это комментарием или ответом (т. Е. Это заголовок или элемент в вашем списке).

  3. Для элемента n у меня есть логический элемент isVisible (по умолчанию false; View.GONE).

Теперь вы можете использовать следующие компоненты:

  1. один дополнительный адаптер класса
  2. Два макета XMLs: Один за ваш комментарий, один для вашего ответа. У вас могут быть неограниченные комментарии, и каждый комментарий может иметь неограниченные ответы. Оба эти требования удовлетворяют вашим требованиям.
  3. Ваш фрагмент или класс контейнера активности, который реализует OnItemClickListener для отображения/скрыть ваш список.

Итак, давайте посмотрим на некоторый код, не так ли?

Во-первых, ваши XML файлы:

Комментарий строки (ваш заголовок)

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

Теперь ваш ответ строка (элемент в списке)

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

Хорошо, теперь ваш адаптер класс

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 
} 

} 

Хорошо, теперь у нас есть список заголовков со скрытыми детьми (помните, мы установили, что дети по умолчанию «ушли»). Не то, что мы хотели, поэтому давайте это исправим.

Ваш контейнер класса (фрагмент или активность) у вас будет следующее определение 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" /> 

И ваш onCreateView будет осуществлять OnItemClickListener и имеют следующие

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 
} 

Теперь, чтобы показать свой список, когда вам нажмите заголовок

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

    adapter.changeVisibility(position); 

} 

Теперь, если предмет клик ked, и этот элемент имеет родительский элемент (т.е. элемент списка), он будет отображаться/скрываться в соответствии с его текущим состоянием.

Некоторые комментарии о коде:

  1. Я написал это на WordPad, как у меня нет Dev среды под рукой. Извините за любые ошибки компиляции.

  2. Этот код может быть оптимизирован: Если у вас очень большой набор данных, этот код будет медленным, так как вы перерисовываете весь список при каждом вызове changeVisibility(). Вы можете поддерживать два списка (один для заголовков, один для элементов списка), а в changeVisibility вы можете запрашивать только элементы списка).

  3. Я вновь подтверждаю эту идею, что некоторые дизайнерские решения облегчат вашу жизнь. Например, если ваши элементы списка были всего лишь списком меток, тогда у вас может быть один пользовательский XML-файл (для вашего заголовка) и представление ListView в нем, которое вы можете установить в View.GONE. Это заставит все другие взгляды притворяться, что их даже нет, и ваш макет будет работать правильно.

Надеюсь, что это поможет.

+0

Спасибо за ответ. Моя цель - сделать работу 'wrap_content' для моего пользовательского AdapterView, т. Е. Если количество элементов, заданных адаптером, невелико, то AdapterView не пытается получить все свободное пространство. – vganin

+0

Один конкретный пример использования: двухуровневое меню, где узлы представляют собой составные представления, состоят из заголовка (всегда видимого) и списка (расширяется при событии щелчка заголовка). Список - это мой пользовательский адаптер. Список может содержать любое количество элементов. Проблема в том, что список всегда пытается получить всю высоту. – vganin

+0

EDIT: Спасибо вам за все усилия, которые вы оказали мне на помощь.На самом деле я уже думал о том, как вы это сделали, и есть препятствия, которые мешают мне делать это именно так. Понимаете, главная особенность моего адаптера AdapterView заключается в том, что он имеет анимированный селектор «Drawable», и мне нужно, чтобы этот селектор перемещался из узлов комментариев, чтобы отвечать на узлы, чтобы отвечать на узлы, а узлы ответа должны быть организованы по группам, чтобы я мог использовать еще одну анимацию для расширения/сворачивают их вместе. Однако мне удалось достичь этой цели очень сложным образом. – vganin