在自定义AdapterView中实现onMeasure()和onLayout()的正确方法

时间:2015-07-19 12:16:32

标签: android android-layout android-custom-view android-adapterview

我知道我应该在onMeasure()中衡量儿童,并将其排列在onLayout()。问题是我应该在这些方法中添加/回收视图,以便我可以一起测量所有孩子,看看它们是如何相互定位的(即网格,列表或其他)?

我的第一种方法是在onLayout()中添加/回收视图,但从那时起我无法衡量我的孩子,因为他们尚未添加到AdapterView而getChildCount()返回0onMeasure()。我无法测量AdapterView本身没有孩子已经布局,因为它真的取决于他们的共同立场,对吗?

当动态添加/删除子项时,我对AdapterView中的android布局过程感到困惑。

1 个答案:

答案 0 :(得分:1)

我不能发表评论,因为我是新用户,但你能描述一下你想要做什么,而不是你想要做什么?通常,您会发现这是一个设计问题而不是编码问题。特别是如果你来自不同的平台(例如,iOS)。根据经验,如果您根据业务需要正确设计布局,我发现Android中的测量和手动布局大多是不必要的。

编辑: 正如我所提到的,这可以通过一些设计决策来解决。我将使用您的节点/列表示例(希望这是您的实际用例,但可以扩展解决方案以解决更普遍的问题)。

因此,如果我们将您的标题视为论坛中的评论,并将列表视为对评论的回复,我们可以做出以下假设:

  1. 一个列表就足够了,而不是两个。列表中的每个项目可以是标题(注释)或列表项目(回复)。每个回复都是评论,但并非所有评论都是回复。

  2. 对于项目n,我知道它是评论还是答复(即它是标题中的标题或项目)。

  3. 对于项目n,我有一个布尔成员isVisible(默认为false; View.GONE)。
  4. 现在,您可以使用以下组件:

    1. 一个扩展适配器类
    2. 两个布局XML:一个用于您的评论,一个用于您的回复。您可以拥有无​​限的评论,每条评论都可以无限回复。两者都满足您的要求。
    3. 实现OnItemClickListener以显示/隐藏列表的片段或活动容器类。
    4. 那么让我们看看一些代码,不管吗?

      首先,您的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);
      
      }
      

      现在,如果单击某个项目并且该项目具有父项(即列表项),则将根据其当前状态显示/隐藏该项目。

      关于代码的一些评论:

      1. 我在写字板上写了这个,因为我没有方便的开发环境。对不起任何编译错误。

      2. 可以优化此代码:如果您的数据集非常大,则此代码会很慢,因为您在每次调用changeVisibility()时都会重绘整个列表。您可以维护两个列表(一个用于标题,一个用于列表项),在changeVisibility中,您只能查询列表项。

      3. 我强化了这个想法,即一些设计决定会让你的生活变得更轻松。例如,如果您的列表项实际上只是一个标签列表,那么您可以拥有一个自定义XML文件(用于标题)和一个可以设置为View.GONE的ListView视图。这将使所有其他视图假装它甚至不存在,并且您的布局将正常工作。

      4. 希望这有帮助。