如何在ListView中点击item控制item中的控件变化(ListView篇)

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/cai554112503/article/details/50363758

ListView一直是Android里的一大难题,之前一段时间做项目碰到一个需求:点击item后控制item中的ImageView显示正在播放的图片,其他item中的imageView则都显示未播放的图片。下面给出一个例子来实现这种效果。
首先看下最后的效果图:


要实现这个需求有两个难点:1.如何获取item中的控件 2.如何避免item布局复用带来的负面效果

先抛开第二点不谈,看看第一点,如何获取item中的控件。在getView()中我们可以给button设置一个onClick监听事件,监听事件里对左侧的ImageView进行setBackground操作,但是这里有个问题,在匿名内部类中是只能使用final类型的变量的,而将ViewHolder设置成final类型并不是一种很好的做法,所以我们无法在onclick方法中直接调用holder.imageView。那么我们可不可以在if(conVertView==null)中当ImageView初始化之后将ImageView存储进一个全局的List<ImageView>,然后在Onclick方法中使用list.get(position)来调用这个ImageView(position可以改为final类型在onclick中使用),当然上述的想法都是在不考虑item布局复用的情况下提出的,让我们先来看看效果。

MyAdapter.java:

package com.test.listviewitemclicktest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;

public class MyAdapter extends BaseAdapter {
	private Context context;
	private ArrayList<ImageView> list;

	public MyAdapter(Context context) {
		this.context = context;
		list = new ArrayList<ImageView>();
	}

	@Override
	public int getCount() {
		// TODO Auto-generated method stub
		return 30;
	}

	@Override
	public Object getItem(int position) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public long getItemId(int position) {
		// TODO Auto-generated method stub
		return position;
	}

	@Override
	public View getView(final int position, View convertView, ViewGroup parent) {
		ViewHolder holder = null;
		if (convertView == null) {
			holder = new ViewHolder();
			convertView = LayoutInflater.from(context).inflate(R.layout.listview_item, parent, false);
			holder.bt = (Button) convertView.findViewById(R.id.bt_item);
			holder.iv = (ImageView) convertView.findViewById(R.id.iv_item);
			list.add(holder.iv);
			convertView.setTag(holder);
		} else {
			holder = (ViewHolder) convertView.getTag();
		}
		
		holder.bt.setText("第" + position + "个按钮");
		holder.bt.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				resetBackGround();
				list.get(position).setBackgroundResource(R.drawable.video);
			}

			public void resetBackGround() {
				for (int i = 0; i < list.size(); i++) {
					list.get(i).setBackgroundResource(R.drawable.playvideo);
				}
			}
		});
		return convertView;
	}

	class ViewHolder {
		Button bt;
		ImageView iv;
	}

}

这个例子里面没有用到数据源,所以我也没写数据源,只是getCount返回一个30,意思是有30个item。

运行起来后,一开始出现在视图中的几个item没有问题,但是一滚动问题就出现了,明明没有点击过的item,图标也变亮了,更恐怖的是,当点击了后面才出现的item时,程序直接崩溃,出现这种错误:


超出范围错误。也就是说list里面没有30个值,可能只有10个值,当我点击第13个item时,自然就会报出超出范围的错误。

这就涉及到了第二个难点:item布局复用的问题。

在if(convertView==null)和else中分别打上log就会发现:刚开始就出现在布局中的item,它的布局确实是通过初始化得到的(假定有10个item),当滚动后出现的下面的item则是完全复用上面被遮住的几个item的布局,所以说当点击第一个item后第一个item的图标亮了,滚动下去后,发现第十一个也是亮的,因为第十一个item和第一个item用的就是同一个布局!现在我们在将listView往上滚动,上面被遮住后重新出现的item再次复用下面item的布局。也就是说,一旦有新的item出现在视图中,他就会复用刚刚消失在视图中的item的布局,这样来回复用,其实布局的总数量也就开始时的那十个左右,换句话说,if(conVertView==null)里面的代码也就执行了十次左右,所以list中的数据的数量不可能达到30个。

原理了解清楚了,那我们要怎样避免item布局复用产生的负面效果。listView的复用机制是必需的,不用这种机制的话,视图中每出现一个新的item就要对布局重新初始化一次,这会极大的影响效率。在机制无法修改的情况下,我们就要使用其他办法来达到最初的需求。

首先我们要换一个容器来存储数据。List是一种有序可重复的容器,在这种环境下使用显然不能完成目标。我们可以换一个存储键值对的容器,比如Map,不管ListView是否复用,我们让position做key,imageView做value,当key重复时,新的value会取代旧的value,这样正好能满足我们的需求。下面是修改后的代码:

package com.test.listviewitemclicktest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;

public class MyAdapter extends BaseAdapter {
	private Context context;
	private HashMap<String, Object> map;

	public MyAdapter(Context context) {
		this.context = context;
		map = new HashMap<String, Object>();
	}

	@Override
	public int getCount() {
		// TODO Auto-generated method stub
		return 30;
	}

	@Override
	public Object getItem(int position) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public long getItemId(int position) {
		// TODO Auto-generated method stub
		return position;
	}

	@Override
	public View getView(final int position, View convertView, ViewGroup parent) {
		ViewHolder holder = null;
		if (convertView == null) {
			Log.i("info", "0");
			holder = new ViewHolder();
			convertView = LayoutInflater.from(context).inflate(R.layout.listview_item, parent, false);
			holder.bt = (Button) convertView.findViewById(R.id.bt_item);
			holder.iv = (ImageView) convertView.findViewById(R.id.iv_item);
			Log.i("info", "map.size>" + map.size());
			convertView.setTag(holder);
		} else {
			Log.i("info", "1");
			Log.i("info", "map.size>" + map.size());
			holder = (ViewHolder) convertView.getTag();
		}
		map.put("" + position, holder.iv);
		holder.bt.setText("第" + position + "个按钮");
		holder.bt.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				resetBackGround();
				((ImageView) (map.get("" + position))).setBackgroundResource(R.drawable.video);
			}

			public void resetBackGround() {
				for (int i = 0; i < map.size(); i++) {
					((ImageView) (map.get("" + i))).setBackgroundResource(R.drawable.playvideo);
				}
			}
		});
		return convertView;
	}

	class ViewHolder {
		Button bt;
		ImageView iv;
	}

}
错误是不报了,但是在点了一个item了还是会出现其他item也被点亮的问题,并且来回滚动几次后,原先点击的那个item的图标可能就暗掉了,了解了布局复用的原理,这个结果也在意料之中,我们要添加一个标识符,标记一下哪个item被点击了:

package com.test.listviewitemclicktest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;

public class MyAdapter extends BaseAdapter {
	private Context context;
	private HashMap<String, Object> map;
	private int[] tags = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

	public MyAdapter(Context context) {
		this.context = context;
		map = new HashMap<String, Object>();
	}

	@Override
	public int getCount() {
		// TODO Auto-generated method stub
		return 30;
	}

	@Override
	public Object getItem(int position) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public long getItemId(int position) {
		// TODO Auto-generated method stub
		return position;
	}

	@Override
	public View getView(final int position, View convertView, ViewGroup parent) {
		ViewHolder holder = null;
		if (convertView == null) {
			Log.i("info", "0");
			holder = new ViewHolder();
			convertView = LayoutInflater.from(context).inflate(R.layout.listview_item, parent, false);
			holder.bt = (Button) convertView.findViewById(R.id.bt_item);
			holder.iv = (ImageView) convertView.findViewById(R.id.iv_item);
			Log.i("info", "map.size>" + map.size());
			convertView.setTag(holder);
		} else {
			Log.i("info", "1");
			Log.i("info", "map.size>" + map.size());
			holder = (ViewHolder) convertView.getTag();
		}
		map.put("" + position, holder.iv);
		holder.bt.setText("第" + position + "个按钮");
		if (tags[position] == 0) {
			holder.iv.setBackgroundResource(R.drawable.playvideo);
		} else {
			holder.iv.setBackgroundResource(R.drawable.video);
		}
		holder.bt.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				resetBackGround();
				((ImageView) (map.get("" + position))).setBackgroundResource(R.drawable.video);
				tags[position] = 1;
			}

			public void resetBackGround() {
				for (int i = 0; i < map.size(); i++) {
					((ImageView) (map.get("" + i))).setBackgroundResource(R.drawable.playvideo);
					tags[i] = 0;
				}
			}
		});
		return convertView;
	}

	class ViewHolder {
		Button bt;
		ImageView iv;
	}

}

ok,问题解决!现在的效果就跟文章开头的那张图片里面一样了,很好的解决了这个问题。当然不同的需求可能需要不同的解决办法,上面这一种也只是我想的一种比较笨的办法,可能有些人会选择给imageView setTag,或者是在ViewHolder中添加一个ID字段,这个就因问题而异了~~~知道了原理要实现各种需求就容易很多了!

展开阅读全文

没有更多推荐了,返回首页