一、ListView
1. 简单用法
在布局文件中创建一个ListView
,修改代码让其不满整个屏幕:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android: layout_height= "match_parent">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
在MainActivity
中为ListView
绑定数据:
这里为ListView
绑定了一个string
数组。
列表控件中的子项是一个TextView
,当然除了TextView
之外,我们还可以自定义列表的子项。
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" };
@0verride
protected void onCreate (Bundle savedInstanceState) {
super.onCreate (savedIns tanceState);
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.setAdapter(adapter);
}
}
2. 定制ListView
的界面
关键是需要设置好适配器Adapter
,我们可以自定义一个列表项的视图,将交给适配器,再将适配器与ListView
,ListView
根据需要从ListAdapter
请求视图,例如在用户向上或向下滚动时显示新视图。
常用的适配器类有: ArrayAdapter
、SimpleAdapter
和BaseAdapter
。他们之间的关系如下:
(1)ArrayAdapter
适配器
使用步骤:
- 在
xml
文件布局上实现ListView
- 在
Activity
中定义数据源(列表或者数组) - 构造
ArrayAdapter
对象,设置适配器 - 将
ListView
绑定到ArrayAdapter
上
构造函数:
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
MainActivity.this, android.R.layout.simple_list_item_1, data);
实例化ArrayAdapter
对象传入了三个参数,第一个参数是上下文对象,第二个参数是子项布局,这里调用了系统内容的布局,第三个参数是数据集,这里传入的是字符串数组。
(2)SimpleAdapter
适配器
ArrayAdapter
适用于显示信息比较单一的场景,若显示项中包含多种形式的数据,就不太适用了,而SimpleAdapter
可以适配多种数据类型。
使用步骤:
- 在
xml
中添加ListView
- 实现
item
布局(根据实际UI需求) - 创建数据源(数据源形式有要求
List<?extends Map<String,? >>
) - 创建
SimpleAdapter
适配器 - 将
SimpleAdapter
适配器绑定到ListView
中
构造函数:
SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)
context
:即上下文对象;data
:是一个包裹Map
集合的List
数据集;resource
:子项布局文件;from
:是一个字符串数组,字符串指的是Map
中的键值;to
:是一个int
型数组,表示子项布局中对应控件的id
。
示例:
子项布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="match_parent">
<ImageView
android:id="@+id/img"
android:src="@mipmap/ic_launcher"
android:layout_width="50dp"
android:layout_height="50dp" />
<TextView
android:id="@+id/tv"
android:text="hello"
android:gravity="center"
android:layout_marginLeft="50dp"
android:textSize="28sp"
android:layout_width="wrap_content"
android:layout_height="50dp" />
</LinearLayout>
主视图布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
代码:
public class MainActivity extends Activity {
private ListView listView;
private SimpleAdapter simpleAdapter;
private List<Map<String, Object>> datas=new ArrayList<Map<String, Object>>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView=(ListView)findViewById(R.id.listview);
initDatas();//初始化数据集
//实例化SimpleAdapter
simpleAdapter=new SimpleAdapter(this,datas,R.layout.animal_layout,new String[]{"img","name"},new int[]{R.id.img,R.id.tv}); listView.setAdapter(simpleAdapter);//设置配置器
}
private void initDatas() {
Map map1=new HashMap();
map1.put("img",R.drawable.fish);
map1.put("name","小金鱼");
Map map2=new HashMap();
map2.put("img",R.drawable.horse);
map2.put("name","千里马");
Map map3=new HashMap();
map3.put("img",R.drawable.mouse);
map3.put("name","米老鼠");
datas.add(map1);
datas.add(map2);
datas.add(map3);
}
}
(3) BaseAdapter
适配器
ArrayAdapter
一般适用于数据源数据种类比较单一的情形,若数据类型比较复杂,需要个性化定制布局,则可以采用BaseAdapter
适配器进行数据适配。
若要问
BaseAdapter
和SimpleAdapter
有什么不同,通过API文档可以获悉,SimpleAdapter
是BaseAdapter
的子类,BaseAdapter
较SimpleAdapter
来讲更为灵活,在开发中也更为常用。
实现步骤:
- 在布局中添加
ListView
- 实现
item
布局(根据 UI 设计的) - 创建数据源
- 创建自己的
Adapter
类继承BaseAdapter
- 创建自定义的
Adapter
类对象 - 将创建的适配器绑定到
ListView
上
构造函数:
构造自己的Adapter
,继承自BaseAdapter
类,必须需要覆写四个方法。
// 继承 BaseAdapter 必须要实现它的 4 个方法
class MyAdapter extends BaseAdapter{
private Context context;
private List<Animal> datas;
// 返回适配器中所代表的数据集合的条数
// 会首先执行这个方法(连续执行好几次),如果是 0 则后面的方法就不会执行了
@Override
public int getCount() {
return datas.size();
}
// 返回数据集合中指定索引 position 对应的数据项
// 手动调用才会执行
@Override
public Object getItem(int position) {
return datas.get(position);
}
// 返回列表中与指定索引对应的行 id
// 手动调用才会执行
@Override
public long getItemId(int position) {
return position;
}
// 返回指定索引对应的数据的视图,会多次调用
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Animal animal= (Animal) getItem(position);
View view;
ViewHolder viewHolder;
if(convertView==null){
view = LayoutInflater.from(context).inflate(R.layout.animal_layout,null);
viewHolder=new ViewHolder();
viewHolder.animalImage=(ImageView)view.findViewById(R.id.img);
viewHolder.animalName=(TextView)view.findViewById(R.id.tv);
view.setTag(viewHolder);
}else{
view=convertView;
viewHolder= (ViewHolder) view.getTag();
}
viewHolder.animalName.setText(animal.getAnimal());
viewHolder.animalImage.setImageResource(animal.getImgId());
return view;
}
//创建ViewHolder类
class ViewHolder{
ImageView animalImage;
TextView animalName;
}
}
3. 优化
优化方式一:
界面上有多少个Item
,那么getView
方法就会被调用多少次!使用 convertView
作为 View
缓存 ,将 convertView
作为 getView
的输入参数、返回参数借助 ListView
的缓存机制,实现 view
的复用。(如BaseAdapter上面的代码)
优化方式二:
getView()
会被调用多次,那么findViewById
不一样得调用多次,而我们的ListView
的Item
一般都是一样的布局,我们可以对这里在优化下,我们可以自己定义一个ViewHolder
类来对这一部分 进行性能优化!在方式一的基础上,进行优化,引入 ViewHolder
减少 findViewById
的次数。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Animal animal= (Animal) getItem(position);
View view;
ViewHolder viewHolder;
// 判断 convertView 是否已经存在
if(convertView==null){
view = LayoutInflater.from(context).inflate(R.layout.animal_layout,null);
viewHolder=new ViewHolder();
viewHolder.animalImage=(ImageView)view.findViewById(R.id.img);
viewHolder.animalName=(TextView)view.findViewById(R.id.tv);
view.setTag(viewHolder);
}else{
view=convertView;
viewHolder= (ViewHolder) view.getTag();
}
// 减少 findViewById 的次数
viewHolder.animalName.setText(animal.getAnimal());
viewHolder.animalImage.setImageResource(animal.getImgId());
return view;
}
参考:掘金文章
4. 点击事件
这个比较简单:
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@0verride
protected void onCreate ( Bundle savedInstanceState) {
super.onCreate ( savedInstanceState);
setContentView(R. layout . activity_ main) ;
initFruits();
FruitAdapter adapter = new F rui tAdapter (MainActivity.this, R.layout.fruit_item,fruitList);
ListView listView = (ListView) findViewById(R.id.list_ view) ;
listView. setAdapter(adapter);
listView. setOnItemClickListener(new AdapterView.OnItemClickListener() {
@0verride
public void onItemClick (AdapterView<?> parent, View view,
int position, Long id) {
// 获取点击的是哪一个 Item
Fruit fruit = fruitList.get(position) ;
Toast.makeText (MainActivity.this, fruit.getName(),
Toast.LENGTH_SHORT).show();
}
});
}
二、RecyclerView
ListView
有强大的功能,但如果我们不使用一些技巧来提升它的运行效率,那么ListView
的性能就会非常差。还有,ListView
的扩展性也不够好,它只能实现数据纵向滚动的效果,如果我们想实现横向滚动的话,ListView
是做不到的。
为此,Android
提供了一个更强大的滚动控件RecyclerView
。 它可以说是一个增强版的ListView
,不仅可以轻松实现和ListView
同样的效果,还优化了ListView
中存在的各种不足之处。
步骤:
- 添加
Recyclerview
依赖。 - 在
xml
文件中添加Recyclerview
控件。 - 创建
Recyclerview
的Adapter
(适配器)。 - 将适配器设置给
Recyclerview
。
1. 添加Recyclerview
依赖
想要使用RecyclerView
这个控件,首先需要在项目的build.gradle
中添加相应的依赖库才行。
implementation 'com.android.support:recyclerview-v7:28.0.0'
添加完之后记得要点击一下Sync Now
来进行同步。
2. 在xml
文件中添加Recyclerview
控件
Activity
布局文件activity_rv.xml
…
Item
的布局文件item_1.xml
...
3. 创建Adapter
标准实现步骤如下:
① 创建Adapter
:创建一个继承RecyclerView.Adapter
的Adapter
类(VH
是ViewHolder
的类名)
② 创建ViewHolder
:在Adapter
中创建一个继承RecyclerView.ViewHolder
的静态内部类,记为VH
。ViewHolder
的实现和ListView
的ViewHolder
实现几乎一样。
③ 在Adapter
中实现3个方法:
onCreateViewHolder()
这个方法主要生成为每个Item inflater
出一个View
,但是该方法返回的是一个**ViewHolder
**。该方法把View
直接封装在ViewHolder
中,然后我们面向的是ViewHolder
这个实例,当然这个ViewHolder
需要我们自己去编写。
需要注意的是在onCreateViewHolder()
中,映射**Layout
必须为**
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, parent, false);
而不能是:
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, null);
onBindViewHolder()
这个方法主要用于适配渲染数据到View
中。方法提供给你了一**viewHolder
**而不是原来的convertView
。getItemCount()
这个方法就类似于BaseAdapter
的**getCount
**方法了,即总共有多少个条目。
可以看出,RecyclerView
将ListView
中getView()
的功能拆分成了onCreateViewHolder()
和onBindViewHolder()
。
基本的Adapter实现如下:
// ① 创建Adapter
public class NormalAdapter extends RecyclerView.Adapter<NormalAdapter.VH>{
//② 创建ViewHolder
public static class VH extends RecyclerView.ViewHolder{
public final TextView title;
public VH(View v) {
super(v);
title = (TextView) v.findViewById(R.id.title);
}
}
private List<String> mDatas;
public NormalAdapter(List<String> data) {
this.mDatas = data;
}
//③ 在Adapter中实现3个方法
@Override
public void onBindViewHolder(VH holder, int position) {
holder.title.setText(mDatas.get(position));
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//item 点击事件
}
});
}
@Override
public int getItemCount() {
return mDatas.size();
}
@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
//LayoutInflater.from指定写法
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, parent, false);
return new VH(v);
}
}
4. 设置
创建完Adapter
,接着对RecyclerView
进行设置,一般来说,需要为RecyclerView
进行以下设置:
Layout Manager
:Item
的布局。Adapter
:为Item
提供数据。Item Decoration
:Item
之间的Divider
。Item Animator
:添加、删除Item
动画。
Layout Manager
RecyclerView
提供了三种布局管理器:
LinerLayoutManager
以垂直或者水平列表方式展示ItemGridLayoutManager
以网格方式展示ItemStaggeredGridLayoutManager
以瀑布流方式展示Item
如果你想用 RecyclerView
来实现自己自定义效果,则应该去继承实现自己的 LayoutManager
,并重写相应的方法,而不应该想着去改写 RecyclerView
。
5. 点击事件
RecyclerView
并没有像ListView
一样暴露出Item
点击事件或者长按事件处理的api
,也就是说使用RecyclerView
时候,需要我们自己来实现Item
的点击和长按等事件的处理。
实现方法有很多:
- 可以监听
RecyclerView
的Touch
事件然后判断手势做相应的处理, - 也可以通过在绑定
ViewHolder
的时候设置监听,然后通过Apater
回调出去
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>{
// 展示数据
private ArrayList<String> mData;
// 事件回调监听
private MyAdapter.OnItemClickListener onItemClickListener;
public MyAdapter(ArrayList<String> data) {
this.mData = data;
}
public void updateData(ArrayList<String> data) {
this.mData = data;
notifyDataSetChanged();
}
// 添加新的Item
public void addNewItem() {
if(mData == null) {
mData = new ArrayList<>();
}
mData.add(0, "new Item");
notifyItemInserted(0);
}
// 删除Item
public void deleteItem() {
if(mData == null || mData.isEmpty()) {
return;
}
mData.remove(0);
notifyItemRemoved(0);
}
// ① 定义点击回调接口
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
// ② 定义一个设置点击监听器的方法
public void setOnItemClickListener(MyAdapter.OnItemClickListener listener) {
this.onItemClickListener = listener;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 实例化展示的view
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_rv_item, parent, false);
// 实例化viewholder
ViewHolder viewHolder = new ViewHolder(v);
return viewHolder;
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
// 绑定数据
holder.mTv.setText(mData.get(position));
//③ 对RecyclerView的每一个itemView设置点击事件
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
if(onItemClickListener != null) {
int pos = holder.getLayoutPosition();
onItemClickListener.onItemClick(holder.itemView, pos);
}
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if(onItemClickListener != null) {
int pos = holder.getLayoutPosition();
onItemClickListener.onItemLongClick(holder.itemView, pos);
}
//表示此事件已经消费,不会触发单击事件
return true;
}
});
}
@Override
public int getItemCount() {
return mData == null ? 0 : mData.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView mTv;
public ViewHolder(View itemView) {
super(itemView);
mTv = (TextView) itemView.findViewById(R.id.item_tv);
}
}
}
设置Adapter的事件监听。
mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MDRvActivity.this,"click " + position + " item", Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(MDRvActivity.this,"long click " + position + " item", Toast.LENGTH_SHORT).show();
}
});
参考: