无条件布局,来自视图适配器的膨胀:应该使用 View Holder 模式

Posted

技术标签:

【中文标题】无条件布局,来自视图适配器的膨胀:应该使用 View Holder 模式【英文标题】:Unconditional layout, inflation from view adapter: Should use View Holder pattern 【发布时间】:2014-10-12 10:28:46 【问题描述】:

我在 Eclipse 中收到以下警告:

来自视图适配器的无条件布局膨胀:应使用 View Holder 模式(使用传递给此方法的回收视图作为第二个参数)以实现更平滑的滚动。

开:

convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false);

我有一个实现了 CheckBox 的基本适配器,并且我添加了一个标签来使 CheckBox 工作。

代码如下:

 public View getView(final int position, View convertView, ViewGroup parent) 
  

    ViewHolder mViewHolder;
    mViewHolder = new ViewHolder();
    LayoutInflater vi = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false);

    mViewHolder.cb = (CheckBox) convertView.findViewById(R.id.checkBox);

    convertView.setTag(mViewHolder);

    if (InviteFriends.isChecked[position] == true)
    
        mViewHolder.cb.setChecked(true);
    
    else
    
        mViewHolder.cb.setChecked(false);
    

    mViewHolder.cb.setOnCheckedChangeListener(new OnCheckedChangeListener() 
    
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean ischecked) 
        
            if (buttonView.isChecked())
            
                InviteFriends.isChecked[position] = true;

            
            else
            
                InviteFriends.isChecked[position] = false;
            
        
    );

    TextView friendsname  = (TextView) convertView.findViewById(R.id.friendsName); // title
    ImageView thumb_image = (ImageView) convertView.findViewById(R.id.list_image); // thumb image

    HashMap<String, String> song = new HashMap<String, String>();
    song = data.get(position);

    // Setting all values in listview
    friendsname.setText(song.get(InviteFriends.KEY_DISPLAY_NAME));
    imageLoader.DisplayImage(song.get(InviteFriends.KEY_IMAGEPROFILE_URL), thumb_image);


    return convertView;

结果正常出现。如何解决此警告?我还没有办法解决这个问题?

谢谢!

【问题讨论】:

【参考方案1】:

试试这个

static class ViewHolder 

    private TextView friendsname;
    private ImageView thumb_image;
    private CheckBox cb;


public View getView(final int position, View convertView, ViewGroup parent) 

    ViewHolder mViewHolder = null;
    HashMap<String, String> song = null;

    if (convertView == null) 

        song = new HashMap <String, String>();
        mViewHolder = new ViewHolder();

        LayoutInflater vi = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false);
        mViewHolder.friendsname = (TextView) convertView.findViewById(R.id.friendsName); // title
        mViewHolder.thumb_image = (ImageView) convertView.findViewById(R.id.list_image); // thumb image


        mViewHolder.cb = (CheckBox) convertView.findViewById(R.id.checkBox);

        convertView.setTag(mViewHolder);
        mViewHolder.cb.setTag(data.get(position));

        mViewHolder.cb.setOnCheckedChangeListener(new OnCheckedChangeListener() 
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean ischecked) 

                InviteFriends.isChecked[position] = buttonView.isChecked();

            
        );

     else 

        mViewHolder = (ViewHolder) convertView.getTag();

    

    song = mViewHolder.cb.getTag();

    mViewHolder.friendsname.setText(song.get(InviteFriends.KEY_DISPLAY_NAME));
    mViewHolder.imageLoader.DisplayImage(song.get(InviteFriends.KEY_IMAGEPROFILE_URL), thumb_image);
    mViewHolder.cb.setChecked(InviteFriends.isChecked[position]);

    return convertView;

【讨论】:

@ user88 :这解决了这个问题,只需进行少量更改即可满足我的要求。但是,如果我有刚刚显示警告的解决方案会发生什么......我仍然得到了结果。 如果很难说,上面代码的关键是你需要在再次膨胀行之前检查“if (convertView == null)”。 请告诉我们如何解决问题以及做什么。 How to answer帮助中心。 有关此技术的一些官方文档:developer.android.com/training/improving-layouts/… 这个答案是不正确的,因为歌曲数据通过保存到标签总是相同的。请在我的回答中查看更正确的解决方案:***.com/a/51073730/4758255【参考方案2】:

只有当它为空时才应该初始化转换视图

这些行

LayoutInflater vi = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false);
// [...] the rest of initialization part
// [...] some changes that must be done at refresh
return convertView;

应该是这样的:

if (convertView == null) 
    LayoutInflater vi = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false);
    // [...] the rest of initialization part

// [...] some changes that must be done at refresh
return convertView;

目标是回收该列表中已经存在的视图,而不是在每次滚动列表时显示它时都初始化它。

【讨论】:

感谢分享。为了理解一些事情,当我向下滚动自定义列表视图时,它每次调用 getView?所以 getView 代表列表的每个自定义行...我遵循得好吗?【参考方案3】:

我的建议是尝试使用convertView = vi.inflate(R.layout.activity_friend_list_row, null); insted of convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false); 这可能对您有所帮助。

:- 好的 .. 像这样访问 TextView friendsname = (TextView) convertView.findViewById(R.id.friendsName); // title ImageView thumb_image = (ImageView) convertView.findViewById(R.id.list_image); // thumb image 你必须在适配器中使用 viewholder 类

例如

static class ViewHolder 
    public TextView text;
    public ImageView image;
  

  @Override
  public View getView(int position, View convertView, ViewGroup parent) 
    View rowView = convertView;
    // reuse views
    if (rowView == null) 
      LayoutInflater inflater = context.getLayoutInflater();
      rowView = inflater.inflate(R.layout.rowlayout, null);
      // configure view holder
      ViewHolder viewHolder = new ViewHolder();
      viewHolder.text = (TextView) rowView.findViewById(R.id.TextView01);
      viewHolder.image = (ImageView) rowView
          .findViewById(R.id.ImageView01);
      rowView.setTag(viewHolder);
    

    // fill data
    ViewHolder holder = (ViewHolder) rowView.getTag();
    String s = names[position];
    holder.text.setText(s);
    if (s.startsWith("Windows7") || s.startsWith("iPhone")
        || s.startsWith("Solaris")) 
      holder.image.setImageResource(R.drawable.no);
     else 
      holder.image.setImageResource(R.drawable.ok);
    

    return rowView;
  

【讨论】:

@Jithu:感谢您的快速回复。我仍然收到警告! vi.inflate(R.layout.activity_friend_list_row, null); 不应使用。阅读this【参考方案4】:

来自视图适配器的无条件布局膨胀:应使用 View Holder 模式(使用传递给此方法的回收视图作为第二个参数)以实现更平滑的滚动。

这意味着您需要在适配器中使用 View Holder 模式。使用 View Holder 的目的是重用视图,因为膨胀和使用 findViewById 很慢。

当您使用以下代码时:

public View getView(final int position, View convertView, ViewGroup parent) 

    ViewHolder mViewHolder;
    mViewHolder = new ViewHolder();
    LayoutInflater vi = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false);
    mViewHolder.cb = (CheckBox) convertView.findViewById(R.id.checkBox);
    convertView.setTag(mViewHolder);

    ...

    return convertView;

 

您没有重复使用视图,而是始终创建新视图。

你需要把你的代码改成这样(请查看评论):

// class for holding the cached view
static class ViewHolder 
   TextView tvFriendsName;
   ImageView imvThumbImage;
   CheckBox cbInviteFriend;


public View getView(final int position, View convertView, ViewGroup parent) 

    // holder of the views to be reused.
    ViewHolder viewHolder;

    // get data based on the position
    HashMap<String, String> song = data.get(position);

    // if no previous views found
    if (convertView == null) 
       // create the container ViewHolder
       viewHolder = new ViewHolder();

       // inflate the views from layout for the new row
       LayoutInflater inflater = LayoutInflater.from(parent.getContext());
       convertView = inflater.inflate(R.layout.rowlayout, parent, false);

       // set the view to the ViewHolder.
       viewHolder.cbInviteFriend = convertView.findViewById(R.id.checkBox);
       viewHolder.tvFriendsName  = convertView.findViewById(R.id.friendsName);
       viewHolder.imvThumbImage = convertView.findViewById(R.id.list_image); 

       // save the viewHolder to be reused later.
       convertView.setTag(viewHolder);
     else 
       // there is already ViewHolder, reuse it.
       viewHolder = (ViewHolder) convertView.getTag();
    

    // now we can set populate the data via the ViewHolder into views
    viewHolder.tvFriendsName.setText(song.get(InviteFriends.KEY_DISPLAY_NAME));
    imageLoader.DisplayImage(song.get(InviteFriends.KEY_IMAGEPROFILE_URL), viewHolder.imvThumbImage);
    viewHolder.cbInviteFriend.isChecked(InviteFriends.isChecked[position]);

    return convertView;

【讨论】:

您将如何使用这种方法实现 OnClickListener?因为我试图将侦听器附加到 ListView 和 ListView 项目视图以更改背景颜色,但是,当向下滚动未单击的项目时,它们也会更改其颜色。【参考方案5】:

我所做的是创建一个 BaseSpinnerAdapter 类并在需要时重用它

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import androidx.annotation.LayoutRes

/**
 * @param T Model class
 * @param VH ViewHolder class, which holds the view to reuse it
 * */
abstract class BaseSpinnerAdapter<T, VH>(context: Context?, private val items: ArrayList<T>) :
    BaseAdapter() 

    private var inflater: LayoutInflater = LayoutInflater.from(context)

    /** use viewHolder pattern to reusing the views
     * @param p0 current view position
     * @param p1
     * @param viewGroup
     * */
    override fun getView(p0: Int, p1: View?, viewGroup: ViewGroup?): View 
        var pClone = p1
        val viewHolder: VH

        if (pClone == null) 
        pClone = inflater.inflate(setSpinnerItemLayout(), viewGroup, false)
        viewHolder = createViewHolder(pClone)
        pClone?.tag = viewHolder
     else 
        viewHolder = pClone.tag as VH

    
        getView(viewHolder, p0)

        return pClone!!
    


    override fun getItem(p0: Int): T 
        return items[p0]
    

    override fun getItemId(p0: Int): Long 
        return p0.toLong()
    

    override fun getCount(): Int 
        return items.size
    

    @LayoutRes
    abstract fun setSpinnerItemLayout(): Int

    abstract fun getView(viewHolder: VH, position: Int)

    abstract fun createViewHolder(view: View?): VH



这是一个示例,您可以如何在您的实现中使用 BaseSpinnerAdapter。相信代码说明不用再细化了。

    class SpinnerImplAdapter(context: Context?, private val items: ArrayList<AnyModelClass>) : BaseSpinnerAdapter<AnyModelClass, SpinnerImplAdapter.ViewHolderImp>(context, items) 
    
    
        override fun setSpinnerItemLayout(): Int 
            return R.layout.spinner_item
        
    
        override fun createViewHolder(view: View?): ViewHolderImp 
            return ViewHolderImp(view)
        
    
        override fun getView(viewHolder: ViewHolderImp, position: Int) 
            val model = items[position]
            viewHolder.textView?.text = "" // model.etc get anything you want
        
    
    
/**
 * I have used kotlin extension to get the viewId, 
 * if you are not using then simply call the view.findViewById(R.id.yourView)
 * */
        class ViewHolderImp(view: View?) 
            val textView: TextView? = view?.textView
        
    
    
    

【讨论】:

以上是关于无条件布局,来自视图适配器的膨胀:应该使用 View Holder 模式的主要内容,如果未能解决你的问题,请参考以下文章

InvocationTargetException 在适配器类中膨胀视图

在android的片段中设置列表视图适配器

如何使用Android自定义视图中的属性设置调整膨胀布局的大小?

我可以在不膨胀/重新膨胀布局的情况下将视图添加到滚动视图吗?

具有多种布局的Android ListView

为膨胀的布局设置视图的可见性消失