一、ListView
作用:该控件是一类列表控件,允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据则会滚出屏幕
二、定制ListView的界面
以定制一个球星姓名的列表为例:
1、由于ListView这一控件是由一个个item组成,因此我们首先需要定义一个实体bean类。类中的数据成员包含着每一个item所要展示的元素。这个实体类还是作为ListView适配器的适配类型。代码如下图所示:
public class soccerName {
private String name;
public soccerName(String name){
this.name = name;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
}
2、然后需要为ListView的子项指定一个我们自定义的布局,在layout目录下新建name_item.xml,代码如下图所示:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="100px"
android:gravity="center"
android:layout_margin="10px"
/>
</LinearLayout>
3、接下来创建一个自定义的适配器,该适配器能将我们要展示的数据传入到ListView控件当中。这里我们适配器要继承自ArrayAdapter,并将泛型指定为类。新建一个FruitAdapter类,代码如下:
public class SoccerAdapter extends ArrayAdapter<Soccer> {
private int resourceId;
//1、SoccerAdapter重写了父类的一组构造函数,用于将上下文、ListView的子项布局文件的id和数据都传进来
public SoccerAdapter(@NonNull Context context, int resource, int textViewResourceId, @NonNull List<Soccer> objects) {
super(context, resource, textViewResourceId, objects);
resourceId = textViewResourceId;
}
//2、重写了getView()方法,这个方法在每个子项被滚动到屏幕内的时候会被调用
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
//3、通过getItem()方法得到当前项的Soccer实例
Soccer soccer = getItem(position);
//4、使用LayoutInflater来为这个子项加载我们传入的布局,inflate()方法中传入的第三个参数表示只
//让我们在父布局中声明的layout属性生效,但不会为这个View添加父布局
View view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
//5、获取实例,并且赋值,最后返回
TextView name = view.findViewById(R.id.name);
name.setText(soccer.getName());
return view;
}
}
4、在我们要引用ListView的布局文件中添加ListView这个控件
<ListView
android:id="@+id/list_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="MissingConstraints" />
5、最后在java中的代码如下:
private List<Soccer> soccerList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initName();
SoccerAdapter soccerAdapter = new SoccerAdapter(MainActivity.this,R.layout.name_item,soccerList);
ListView listView = findViewById(R.id.list_name);
listView.setAdapter(soccerAdapter);
}
public void initName(){
for(int i=0;i<2;i++){
Soccer name1 = new Soccer("c罗");
soccerList.add(name1);
Soccer name2 = new Soccer("本泽马");
soccerList.add(name2);
Soccer name3 = new Soccer("贝尔");
soccerList.add(name3);
Soccer name4 = new Soccer("莫德里奇");
soccerList.add(name4);
Soccer name5 = new Soccer("卡塞米罗");
soccerList.add(name5);
Soccer name6 = new Soccer("克罗斯");
soccerList.add(name6);
Soccer name7 = new Soccer("马塞洛");
soccerList.add(name7);
Soccer name8 = new Soccer("拉莫斯");
soccerList.add(name8);
Soccer name9 = new Soccer("瓦拉内");
soccerList.add(name9);
Soccer name10 = new Soccer("卡瓦哈尔");
soccerList.add(name10);
Soccer name11 = new Soccer("纳瓦斯");
soccerList.add(name11);
Soccer name12 = new Soccer("齐达内");
soccerList.add(name12);
}
}
结果如下:
三、 提升ListView 的运行效率
- (一)在正常情况下getView()方法中,每次都会将布局重新加载一遍,而当ListView快速滚动时,这就会成为性能的瓶颈。而getView()方法中convertView参数,用于将之前加载好的布局进行缓存,便于之后进行重新使用。以下是优化方法:
Soccer soccer = getItem(position);
View view;
if(convertView == null)
view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
else
view = convertView;
TextView name = view.findViewById(R.id.name);
name.setText(soccer.getName());
return view;
- (二)解决重复加载布局之后,我们还发现现在getView()方法每加载一次都会调用view.findById()方法获取一次控件的实例,以下是优化方法:
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Soccer soccer = getItem(position);
View view;
ViewHolder viewHolder;
if(convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
viewHolder = new ViewHolder();
viewHolder.name = view.findViewById(R.id.name);
view.setTag(viewHolder);
}else {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.name.setText(soccer.getName());
return view;
}
class ViewHolder{
TextView name;
}
我们定义了一个ViewHolder的内部类,用于存放所需要实例化控件。第一次加载布局时,实例化控件并且通过调用View对象的setTag()方法将ViewHolder的对象存入到view的Tag当中;再次加载布局时,调用getTag()方法找回实例化的控件
四、ListView其他设定
(一)设置项目间分割线
android:divider="@color/black" //设置分割线颜色
android:dividerHeight="3dp" //设置分割线高度
(二)影藏ListView的滚动条
android:scrollbars="none"
(三)取消ListView的Item点击效果
android:listSelector="#00000000"
(四)设置ListView需要显示在第几项
listView.setSelection(N); //N即需要显示的第N个Item
(五)动态修改ListView
adapter.notifyDataSetChanged();
(六)遍历ListView中所有的Item
for(int i=0;i<listView.getChildCount();i++){
View view = listView.getChildAt();
}
(七)处理空ListView
首先在ListView布局后面放一个控件,用于展示ListView为空时要显示的内容
<android.support.constraint.ConstraintLayout
..... >
<ListView
android:id="@+id/list_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="MissingConstraints" />
<TextView
android:id="@+id/empty"
android:text="hello world"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="MissingConstraints" />
</android.support.constraint.ConstraintLayout>
然后在代码中实现如下语句,即可实现调用:
listView.setEmptyView(findViewById(R.id.empty));
(八)ListView滑动监听
- 1)OnTouchListener
OnTouchListener是View中的监听事件,通过监听如下几个事件发生时坐标,就可以判断用户滑动操作,并进行相应的逻辑处理
listView.setOnTouchListener(new View.OnTouchListener() {
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
//触摸时操作
break;
case MotionEvent.ACTION_MOVE:
//移动时操作
break;
case MotionEvent.ACTION_UP:
//离开时操作
break;
}
return false;
}
});
- 2)OnScrollListener
OnScrollListener是ABSListView中的监听事件,它封装了许多与ListView相关的信息,使用模板如下:
listView.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState){
case OnScrollListener.SCROLL_STATE_IDLE:
//滑动停止时
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
//正在滚动
break;
case OnScrollListener.SCROLL_STATE_FLING:
//手指抛动时,即手指用力滑动时,在离开后ListView由于惯性继续滑动时调用
break;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//滚动时一直调用
//这里传入四个参数
//firstVisibleItem:当前能看见的第一个Item的ID
//visibleItemCount:当前能看见的Item总数(这里能看见的包括了只显示一半的Item)
//totalItemCount:整个ListView的Item总数
}
});
通过上述的onScroll()方法里的几个参数,我们可以通过代码判断是否滚动到了最后一行,如:
if(firstVisibleItem + visibleItemCount == totalItemCount && totalItemCount >0 ){
//滚动到最后一行
}
此外还可以通过几个参数判断滚动的方向,如:
if(firstVisibleItem > lastVisiblePosition){
//上滑
}else if (firstVisibleItem < lastVisiblePosition){
//下滑
}
lastVisiblePosition = firstVisibleItem;
其中:lastVisiblePosition用于记录上次第一个可视的Item的ID
同时,ListView也提供了一些封装的方法来获取当前可视的Item的位置等
listView.getFirstVisiblePosition();
listView.getLastVisiblePosition();
五、 ListVIew的点击事件
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initName();
SoccerAdapter soccerAdapter = new SoccerAdapter(MainActivity.this,R.layout.name_item,soccerList);
ListView listView = findViewById(R.id.list_name);
listView.setAdapter(soccerAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Soccer soccer = soccerList.get(position);
Toast.makeText(MainActivity.this,soccer.getName(),Toast.LENGTH_LONG).show();
}
});
}
使用setOnItenClickListener()方法为ListView注册了一个监听器,当用户点击了ListView中的任何一个子项时,都会回调onItemClick()方法,在该方法中通过判断用户点击了哪一个子项然后获取到相应的名字,并通过Toast将水果名字显现出来