Android,列表适配器在getView中返回错误的位置

Posted

技术标签:

【中文标题】Android,列表适配器在getView中返回错误的位置【英文标题】:Android, List Adapter returns wrong position in getView 【发布时间】:2013-05-03 10:24:01 【问题描述】:

我发现了一个可能是错误的神秘问题! 我的片段中有一个列表。每行都有一个按钮。列表不应响应点击,但按钮是可点击的。

为了获取单击了哪个按钮,我创建了一个侦听器并在我的片段中实现它。这是我的适配器的代码。

public class AddFriendsAdapter extends BaseAdapter 

    public interface OnAddFriendsListener 
        public void OnAddUserClicked(MutualFriends user);
    

    private final String TAG = "*** AddFriendsAdapter ***";

    private Context context;
    private OnAddFriendsListener listener;
    private LayoutInflater myInflater;
    private ImageDownloader imageDownloader;
    private List<MutualFriends> userList;

    public AddFriendsAdapter(Context context) 
        this.context = context;
        myInflater = LayoutInflater.from(context);

        imageDownloader = ImageDownloader.getInstance(context);
    

    public void setData(List<MutualFriends> userList) 
        this.userList = userList;

        Log.i(TAG, "List passed to the adapter.");
    

    @Override
    public int getCount() 
        try 
            return userList.size();
         catch (Exception e) 
            e.printStackTrace();
            return 0;
        
    

    @Override
    public Object getItem(int position) 
        return null;
    

    @Override
    public long getItemId(int position) 
        return position;
    

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) 
        ViewHolder holder;

        if (convertView == null) 
            convertView = myInflater.inflate(R.layout.list_add_friends_row, null);
            holder = new ViewHolder();

            Typeface font = Typeface.createFromAsset(context.getAssets(), "fonts/ITCAvantGardeStd-Demi.ttf");
            holder.tvUserName = (TextView) convertView.findViewById(R.id.tvUserName);
            holder.tvUserName.setTypeface(font);
            holder.ivPicture = (ImageView) convertView.findViewById(R.id.ivPicture);
            holder.btnAdd = (Button) convertView.findViewById(R.id.btnAdd);
            holder.btnAdd.setOnClickListener(new View.OnClickListener() 
                @Override
                public void onClick(View v) 
                    Log.e(TAG, "Item: " + position);
                    listener.OnAddUserClicked(userList.get(position));
                
            );

            convertView.setTag(holder);
         else 
            holder = (ViewHolder) convertView.getTag();
        

        holder.tvUserName.setText(userList.get(position).getName());
        imageDownloader.displayImage(holder.ivPicture, userList.get(position).getPhotoUrl());

        return convertView;
    

    public void setOnAddClickedListener(OnAddFriendsListener listener) 
        this.listener = listener;
    

    static class ViewHolder 
        TextView tvUserName;
        ImageView ivPicture;
        Button btnAdd;
    

当我运行应用程序时,我可以看到我的行但是因为我的列表很长并且有超过 200 个项目,当我转到列表中间并单击一个项目然后返回的位置是错误的(它类似于 7,有时是 4 等等.).

现在有什么奥秘? 如果我从我的片段中激活列表的项目侦听器并单击行,则将显示正确的行位置,而如果我单击按钮,则会在该行上显示错误的位置。

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() 
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) 
                Log.e(TAG, "item " + position + " clicked.");
            
        );

logcat 中的结果:

05-09 10:22:25.228: E/AddFriendsFragment(20296): item 109 clicked.
05-09 10:22:34.453: E/*** AddFriendsAdapter ***(20296): Item: 0

任何建议将不胜感激。谢谢

【问题讨论】:

你在哪里实现 OnAddUserClicked() ?此外,如果您使用 BaseAdapter 和 List&lt;T&gt;,则可以使用 ArrayAdapter 来简化适配器,因为它本质上 是一个 List 并且旨在解决加载数组或AdapterView 中的数据对象列表 【参考方案1】:

因为 convertView 和持有人将被回收使用,所以将您的 setOnClickListener 移出 if else 语句:

    if (convertView == null) 
        convertView = myInflater.inflate(R.layout.list_add_friends_row, null);
        holder = new ViewHolder();

        Typeface font = Typeface.createFromAsset(context.getAssets(), "fonts/ITCAvantGardeStd-Demi.ttf");
        holder.tvUserName = (TextView) convertView.findViewById(R.id.tvUserName);
        holder.tvUserName.setTypeface(font);
        holder.ivPicture = (ImageView) convertView.findViewById(R.id.ivPicture);
        holder.btnAdd = (Button) convertView.findViewById(R.id.btnAdd);
        convertView.setTag(holder);
     else 
        holder = (ViewHolder) convertView.getTag();
    
    holder.btnAdd.setOnClickListener(new View.OnClickListener() 
         @Override
         public void onClick(View v) 
                Log.e(TAG, "Item: " + position);
                listener.OnAddUserClicked(userList.get(position));
            
        );

这不是最好的解决方案,因为会有一些性能问题。我建议您为您的视图创建一个地图并为您的项目创建一个新视图,然后为每个视图使用相对视图。

我认为这将是一个性能最好的更好的解决方案:

@Override
public View getView(final int position, View convertView, ViewGroup parent) 
    ViewHolder holder;

    if (convertView == null) 
        convertView = myInflater.inflate(R.layout.list_add_friends_row, null);
        holder = new ViewHolder();

        Typeface font = Typeface.createFromAsset(context.getAssets(), "fonts/ITCAvantGardeStd-Demi.ttf");
        holder.tvUserName = (TextView) convertView.findViewById(R.id.tvUserName);
        holder.tvUserName.setTypeface(font);
        holder.ivPicture = (ImageView) convertView.findViewById(R.id.ivPicture);
        holder.btnAdd = (Button) convertView.findViewById(R.id.btnAdd);
        holder.btnAdd.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                Integer pos = (Integer)v.getTag();
                Log.e(TAG, "Item: " + pos);
                listener.OnAddUserClicked(userList.get(pos));
            
        );

        convertView.setTag(holder);
     else 
        holder = (ViewHolder) convertView.getTag();
    

    holder.tvUserName.setText(userList.get(position).getName());
    imageDownloader.displayImage(holder.ivPicture, userList.get(position).getPhotoUrl());
    holder.btnAdd.setTag(position);
    return convertView;

您也可以自行管理视图。为您的项目创建每个独特的视图,不要回收视图。

//member various
private Map<Integer, View> myViews = new HashMap<Integer, View>(); 

@Override
public View getView(final int position, View convertView, ViewGroup parent) 
    ViewHolder holder;
    View view = myViews.get(position);
    if (view == null) 
        view = myInflater.inflate(R.layout.list_add_friends_row, null);
        //don't need use the holder anymore.

        Typeface font = Typeface.createFromAsset(context.getAssets(), "fonts/ITCAvantGardeStd-Demi.ttf");
        holder.tvUserName = (TextView) convertView.findViewById(R.id.tvUserName);
        holder.tvUserName.setTypeface(font);
        holder.ivPicture = (ImageView) convertView.findViewById(R.id.ivPicture);
        holder.btnAdd = (Button) convertView.findViewById(R.id.btnAdd);
        holder.btnAdd.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                Integer pos = (Integer)v.getTag();
                Log.e(TAG, "Item: " + pos);
                listener.OnAddUserClicked(userList.get(pos));
            
        );

       holder.tvUserName.setText(userList.get(position).getName());
       imageDownloader.displayImage(holder.ivPicture,  
                userList.get(position).getPhotoUrl());
       myViews.put(position, view);

    
    return view;

【讨论】:

我认为你在哪里设置点击监听器并不重要,因为它应该为position 获得不同的值是后续调用。因此它会做同样的事情,只是根据传递给 getView() 的position 传递不同的用户对象。 没错,buptcoder 是对的。当我将其移出时,列表项侦听器和我的侦听器都返回相同的位置。再次感谢 :) 如果您对您的建议有任何意见,请给我一个样品。 因为我们在语句if (convertView == null) 中写了set position,所以如果convertView不为null,则不会设置位置。每次listview回收convertview时都会发生这种情况。至少我们需要把set Position放到is else语句之外,这样才能得到正确的位置。 感谢 buptcoder,关于“我建议您为您的视图创建地图...”,因为它对我来说是新的,如果您知道参考请与我分享。因为您这样说(将那部分代码移到 if/elss 之外)存在性能问题。我想调查它可能有什么问题。再次感谢;) 我的意思是你可以自己管理视图以使用回收机制。我会将示例代码放在我的帖子中。【参考方案2】:

您是否尝试过这样做:

holder.btnAdd.setTag(Integer.valueOf(position));

然后在按钮的回调中检索点击了哪一行,如下所示:

public void btnAddClickListener(View view)
    
        position = (Integer)view.getTag();
        Foo foo = (Foo)foos_adapter.getItem(position);  //get data of row(position)
        //do some
    

【讨论】:

奇怪,经过三年和两个类似的决定,它是这个答案的第一个赞成票。这也是通用的解决方案,因为我们可以创建一个对象 onClickListener = new View.OnClickListener(...) 并将其分配给任何按钮(如果有的话)。【参考方案3】:

我发现另一种有用的方法(如果你当然使用 ViewHolder 模式)是在调用 getView() 时将索引设置在单独的属性上,然后在你的 onClickListener 中你只需要引用你的持有者的位置属性,一些像这样:

@Override
public View getView(int position, View convertView, ViewGroup parent) 

    final ViewHolder holder;

    if(convertView == null)

        convertView = View.inflate(mContext, R.layout.contact_picker_row,null);

        holder = new ViewHolder();

        holder.body = (RelativeLayout)convertView.findViewById(R.id.numberBody);

        convertView.setTag(holder);

    else

        holder = (ViewHolder)convertView.getTag();

    

    holder.position = position;

    holder.body.setOnClickListener(new View.OnClickListener() 
        @Override
        public void onClick(View v) 

            Toast.makeText(mContext,"Clicked on: "+holder.position,Toast.LENGTH_LONG).show();

        
    );

    return convertView;


private class ViewHolder

    RelativeLayout body;
    int position;


【讨论】:

以上是关于Android,列表适配器在getView中返回错误的位置的主要内容,如果未能解决你的问题,请参考以下文章

Android ListView 不调用适配器的类 getView() 函数

适用于Android ListView的适配器类getView()函数

在列表视图中制作视图不可见的android

Listview 没有被填充,getView() 没有被调用

android 分享一个处理BaseAdapter,getView()多次加载的方法

未调用自定义适配器 getView() 方法