ListView组件可以实现循环显示自定义组件的功能。
简单给出一个ListView的使用实例:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#dee9f7">
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
LinearLayout>
public class MainActivity extends AppCompatActivity {
//准备数据源
private String[] data = { "Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape",
"Pineapple", "Strawberry", "Cherry", "Mango", "Apple", "Banana", "Orange", "Watermelon",
"Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango",};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//将数据源添加到适配器
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
MainActivity.this, android.R.layout.simple_list_item_1,data);
ListView listView = (ListView) findViewById(R.id.list_view);
//将适配器中数据添加到ListView中
listView.setAdapter(adapter);
}
}
上面简单显示一行字符,多余比较复杂的ListView界面,可以自定义其界面,这里假定需要显示的界面是图片加文字
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/fruit_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp" />
LinearLayout>
public class Fruit {
private String name;
private int imageId;
public Fruit(String name, int imageId) {
this.name = name;
this.imageId = imageId;
}
public String getName() {
return name;
}
public int getImageId() {
return imageId;
}
}
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId;
public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects) {
super(context, textViewResourceId, objects);
resourceId = textViewResourceId;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position);
View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
fruitImage.setImageResource(fruit.getImageId());
fruitName.setText(fruit.getName());
return view;
}
}
getView()
这个方法在每个子项被滚动到屏幕内部都会被调用。首先通过getItem()
方法得到当前项的Fruit实例,然后使用LayoutInflater为这个子项加载我们传入的布局。
不能为View添加父局,否则不能再添加到ListView中。调用View的findViewById()
方法获取ImageView和TextView示例,调用其方法setImageResource()
和setText()
设置显示的图片和文字。最后将布局返回。
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits(); // 初始化水果数据
FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList);
ListView listView = (ListView) findViewById(R.id.list_view);
listView.setAdapter(adapter);
}
private void initFruits() {
for (int i = 0; i < 2; i++) {
Fruit apple = new Fruit("Apple", R.drawable.apple_pic);
fruitList.add(apple);
Fruit banana = new Fruit("Banana", R.drawable.banana_pic);
fruitList.add(banana);
Fruit orange = new Fruit("Orange", R.drawable.orange_pic);
fruitList.add(orange);
Fruit watermelon = new Fruit("Watermelon", R.drawable.watermelon_pic);
fruitList.add(watermelon);
Fruit pear = new Fruit("Pear", R.drawable.pear_pic);
fruitList.add(pear);
Fruit grape = new Fruit("Grape", R.drawable.grape_pic);
fruitList.add(grape);
Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic);
fruitList.add(pineapple);
Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic);
fruitList.add(strawberry);
Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic);
fruitList.add(cherry);
Fruit mango = new Fruit("Mango", R.drawable.mango_pic);
fruitList.add(mango);
}
}
}
如果需要定制更加复杂的界面,只需要修改展示的ListView的界面XML内容,修改对应的代码。
前述的FruitAdapter的getView()
方法,每次都将布局重新加载一遍,如果ListView快速滚动,则会成为性能瓶颈。
getView()
中还有一个convertView
参数,用于将之前加载好的布局进行缓存,之后可以进行重用
修改前述的getView()
方法:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
} else {
view = convertView;
}
ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
fruitImage.setImageResource(fruit.getImageId());
fruitName.setText(fruit.getName());
return view;
}
上述的代码在每次getView()
方法中还是会调用View的findViewById()
方法来获取一次控件的实例。可以借助ViewHolder进行优化。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position);
View view;
ViewHolder viewHolder;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
viewHolder = new ViewHolder();
viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name);
view.setTag(viewHolder);
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.fruitImage.setImageResource(fruit.getImageId());
viewHolder.fruitName.setText(fruit.getName());
return view;
}
class ViewHolder {
ImageView fruitImage;
TextView fruitName;
}
新增内部类ViewHolder对控件实例进行缓存。当convertView == null,创建该对象,存放控件的实例,然后调用View的setTag()
方法,将ViewHolder对象存储在View中。当convertView不为null,调用getTag()
方法,把ViewHoler重新取出来。
在主函数中修改代码:
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Fruit fruit = fruitList.get(position);
Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show();
}
});
使用setOnItemClickListener()
方法为ListView注册了一个监听器,当用户点击了ListView中任何一个子项,就会回调onItemClick()
方法。该方法中可以通过position参数判断出用户点击的是哪一个子项。
简单示例:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/img_icon"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:paddingLeft="8dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/txt_aName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:textColor="#1D1D1C"
android:textSize="20sp" />
<TextView
android:id="@+id/txt_aSpeak"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="8px"
android:textColor="#B4B4B9"
android:textSize="14sp" />
LinearLayout>
LinearLayout>
public class Animal {
private String aName;
private String aSpeak;
private int aIcon;
public Animal() {
}
public Animal(String aName, String aSpeak, int aIcon) {
this.aName = aName;
this.aSpeak = aSpeak;
this.aIcon = aIcon;
}
public String getaName() {
return aName;
}
public String getaSpeak() {
return aSpeak;
}
public int getaIcon() {
return aIcon;
}
public void setaName(String aName) {
this.aName = aName;
}
public void setaSpeak(String aSpeak) {
this.aSpeak = aSpeak;
}
public void setaIcon(int aIcon) {
this.aIcon = aIcon;
}
}
public class AnimalAdapter extends BaseAdapter {
private LinkedList<Animal> mData;
private Context mContext;
public AnimalAdapter(LinkedList<Animal> mData, Context mContext) {
this.mData = mData;
this.mContext = mContext;
}
@Override
public int getCount() {
return mData.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list_animal,parent,false);
ImageView img_icon = (ImageView) convertView.findViewById(R.id.img_icon);
TextView txt_aName = (TextView) convertView.findViewById(R.id.txt_aName);
TextView txt_aSpeak = (TextView) convertView.findViewById(R.id.txt_aSpeak);
img_icon.setBackgroundResource(mData.get(position).getaIcon());
txt_aName.setText(mData.get(position).getaName());
txt_aSpeak.setText(mData.get(position).getaSpeak());
return convertView;
}
}
public class MainActivity extends AppCompatActivity {
private List<Animal> mData = null;
private Context mContext;
private AnimalAdapter mAdapter = null;
private ListView list_animal;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = MainActivity.this;
list_animal = (ListView) findViewById(R.id.list_animal);
mData = new LinkedList<Animal>();
mData.add(new Animal("狗说", "你是狗么?", R.mipmap.ic_launcher));
mData.add(new Animal("牛说", "你是牛么?", R.mipmap.ic_launcher));
mData.add(new Animal("鸭说", "你是鸭么?", R.mipmap.ic_launcher));
mData.add(new Animal("鱼说", "你是鱼么?", R.mipmap.ic_launcher));
mData.add(new Animal("马说", "你是马么?", R.mipmap.ic_launcher));
mAdapter = new AnimalAdapter((LinkedList<Animal>) mData, mContext);
list_animal.setAdapter(mAdapter);
}
}
这里的流程与前述的都差不多,主要区别在于AnimalAdapter
,这里继承了BaseAdapter
,这是一个抽象类,需要重写其中的四个方法:
getCount()
适配器中数据集的数据个数getItem()
获取数据集中与指定索引对应的数据项getItemId()
获取指定行对应的IDgetView()
获取每一个Item的显示内容ListView可以自己设置表头,表尾以及分割线。
翻遍了了API发现并没有可以直接设置ListView表头或者表尾的属性,只能在Java中写代码 进行设置了,可供我们调用的方法如下:
对了,使用这个addHeaderView方法必须放在listview.setAdapter前面,否则会报错。
示例:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_height="48dp"
android:layout_width="match_parent"
android:textSize="18sp"
android:text="表头"
android:gravity="center"
android:background="#43BBEB"
android:textColor="#FFFFFF"/>
LinearLayout>
public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener{
private List<Animal> mData = null;
private Context mContext;
private AnimalAdapter mAdapter = null;
private ListView list_animal;
private LinearLayout ly_content;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = MainActivity.this;
list_animal = (ListView) findViewById(R.id.list_animal);
//动态加载顶部View和底部View
final LayoutInflater inflater = LayoutInflater.from(this);
View headView = inflater.inflate(R.layout.view_header, null, false);
View footView = inflater.inflate(R.layout.view_footer, null, false);
mData = new LinkedList<Animal>();
mData.add(new Animal("狗说", "你是狗么?", R.mipmap.ic_icon_dog));
mData.add(new Animal("牛说", "你是牛么?", R.mipmap.ic_icon_cow));
mData.add(new Animal("鸭说", "你是鸭么?", R.mipmap.ic_icon_duck));
mData.add(new Animal("鱼说", "你是鱼么?", R.mipmap.ic_icon_fish));
mData.add(new Animal("马说", "你是马么?", R.mipmap.ic_icon_horse));
mAdapter = new AnimalAdapter((LinkedList<Animal>) mData, mContext);
//添加表头和表尾需要写在setAdapter方法调用之前!!!
list_animal.addHeaderView(headView);
list_animal.addFooterView(footView);
list_animal.setAdapter(mAdapter);
list_animal.setOnItemClickListener(this);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(mContext,"你点击了第" + position + "项",Toast.LENGTH_SHORT).show();
}
}
如果想要列表一开始显示列表的最下面,可以使用:android:stackFromBttom
属性设置为true即可。
ListView焦点问题: