前言:此篇是学习笔记,知识内容学习自:《第一行代码》、《android群英传》、《疯狂android讲义》。
ListView是最常用的控件之一,它以垂直列表的形式显示所有列表项,是比较难用好,也非常重要的。
ListView本身只是一个容器,而Adapter负责把内容添加到这个容器中,通过调用setAdapter()方法来实现。
基本使用的话很简单,第一步:在布局文件中加入ListView控件:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.app.test.MainActivity">
<ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent" />
</FrameLayout>
第二步:在Activity中调用setAdapter()给ListView添加内容:
public class MainActivity extends AppCompatActivity {
private ListView listView;
//列表内容data
private String[] data = new String[20];
//适配器
private ArrayAdapter<String> adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.listView);
//给data赋值
for (int i = 0; i < 20; i++) {
data[i] = "第" + i + "项";
}
//创建adapter,其中三个参数依次是:上下文,子布局id,内容
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data);
listView.setAdapter(adapter);
}
}
这样就完成了!
ListView的界面可以通过自定义布局来实现自定义的效果,接下来就来创建一个自定义ListView界面。
首先创建一个item的布局文件,我们仿造微信显示的内容。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
<ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:src="@drawable/a" />
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_weight="1" android:orientation="vertical">
<TextView android:id="@+id/title" android:layout_width="match_parent" android:layout_height="30dp" android:gravity="center_vertical" android:paddingLeft="10dp" android:text="item1" />
<TextView android:id="@+id/body" android:layout_width="match_parent" android:layout_height="30dp" android:gravity="center_vertical" android:paddingLeft="10dp" android:text="boooooooooody1" />
</LinearLayout>
</LinearLayout>
接着我们要新建一个Msg类用于管理item的信息:
public class Msg {
private int imageId;
private String title;
private String body;
public Msg(int imageId, String title, String body) {
this.imageId = imageId;
this.title = title;
this.body = body;
}
public int getImageId() {
return imageId;
}
public void setImageId(int imageId) {
this.imageId = imageId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
非常简单,就是3个成员变量:图片的资源id,title,body,还有构造器和各自的get,set方法。
接着是自定义适配器,我们继承自BaseAdapter:
public class MyAdapter extends BaseAdapter {
private Context mContext;
private List<Msg> msgLsit;
private LayoutInflater inflater;
private ImageView imageView;
private TextView title;
private TextView body;
public MyAdapter(Context context, List<Msg> msgLsit) {
this.msgLsit = msgLsit;
mContext = context;
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return msgLsit.size();
}
@Override
public Object getItem(int position) {
return msgLsit.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Msg msg = (Msg) getItem(position);
View view = inflater.inflate(R.layout.layout_item, null);
imageView = (ImageView) view.findViewById(R.id.imageView);
title = (TextView) view.findViewById(R.id.title);
body = (TextView) view.findViewById(R.id.body);
imageView.setImageResource(msg.getImageId());
title.setText(msg.getTitle());
body.setText(msg.getBody());
return view;
}
}
重写了4个方法,重点看getView()这个方法,此方法会在子项被滚动到屏幕是调用,因此在这个方法里我们加载刚刚新建的子布局,并给控件附上内容。
最后就是在Activity中调用:
public class MainActivity extends AppCompatActivity {
private ListView listView;
private List<Msg> msgList = new ArrayList<Msg>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for (int i = 0; i < 20; i++) {
msgList.add(new Msg(R.drawable.a, "第" + i + "项", "内容:吧啦吧啦~"));
}
MyAdapter adapter = new MyAdapter(this, msgList);
listView = (ListView) findViewById(R.id.listView);
listView.setAdapter(adapter);
}
}
前面我们已经基本可以自由自在使用ListView了,但是那上述方法其实效率是很低下的。因为每次调用getView()方法就会去执行finViewById()方法,实际上我们只要调用一次就可以了。因此,我们可以使用ViewHolder来提高效率。
只需在我们自定义的adapter中加一个内部类ViewHolder,用来保存子布局的控件:
class ViewHolder {
private ImageView imageView;
private TextView title;
private TextView body;
}
然后修改getView()方法:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Msg msg = (Msg) getItem(position);
ViewHolder viewHolder;
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = inflater.inflate(R.layout.layout_item, null);
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.imageView);
viewHolder.title = (TextView) convertView.findViewById(R.id.title);
viewHolder.body = (TextView) convertView.findViewById(R.id.body);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.imageView.setImageResource(msg.getImageId());
viewHolder.title.setText(msg.getTitle());
viewHolder.body.setText(msg.getBody());
return convertView;
}
这里的convertView是getView传进来的参数,用于将之前加载好的布局进行缓存,以便之后使用。第一次传进来的时候肯定是null,我们就用LanyoutInflater加载布局,然后调用setTag()保存viewHoler,第二次传进来就不是null了,因此我们可以直接使用。
布局文件中还可以设置一些其他属性,例如:
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
//设置分割线,可以是颜色,也可以是图片资源
android:divider="@android:color/holo_blue_dark"
//设置好分割线高度
android:dividerHeight="1dp"
//设置隐藏滚动条
android:scrollbars="none"
//设置点击效果(无)
android:listSelector="#00000000"
/>
有些app的列表向上滑动时会有一个按钮,点击后可以回到顶部,其实用的就是ListView的一个方法,调用此方法可以将选定的item列为视图顶部。例如在上述第一个Activity中添加:
public class MainActivity extends AppCompatActivity {
...//省略
listView.setAdapter(adapter);
listView.setSelection(10);
}
}
再次运行后会发现是从第10项开始显示。
此方法是瞬间定位的,还有另外几个方法可以平滑地定位到指定位置。
还是上述Activity,添加一个Button和点击事件,调用listView的smoothScrollToPosition()方法,就可以实现平滑地定位到顶部:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listView.smoothScrollToPosition(0);
}
});
下列两个方法同样可以实现平滑定位:
smoothScrollByOffset(int offset);
smoothScrollBy(int distance,int duration);
可以自己尝试下,看看效果。
ListView中已经显示的内容,在某些情况下可能需要发生变化,如果通过重新设置adapter来更新,这样可以实现,但是效率不会太高。因此,还有一种更简便的方法来实现动态修改:
adapter.notifyDataSetChanged();
修改上述Activity的button点击事件:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
data[n] = "修改的第" + n + "项";
adapter.notifyDataSetChanged();
listView.setSelection(n);
n++;
}
});
运行一下就可以看到,每次点击按钮实现修改item,并定位到修改的item。
最常用的方法就是:
for(int i=0;i<listView.getChildCount();i++){ View view = listView.getChildAt(i); }
当ListView的内容为空时,看不会显示任何内容,其实如果显示一些文字告诉用户“没有任何信息”显得会获得更好地用户体验。而我们也有方法——setEmptyView()可以实现这一功能:
修改xml布局文件:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.app.test.MainActivity">
<ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent" />
<TextView android:id="@+id/t" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="抱歉!没有任何内容可以显示!" android:textSize="40dp" />
</FrameLayout>
其次修改Activity的onCreate方法:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//注掉,相当于msgList是空
// for (int i = 0; i < 20; i++) {
// msgList.add(new Msg(R.drawable.a, "第" + i + "项", "内容:吧啦吧啦~"));
// }
MyAdapter adapter = new MyAdapter(this, msgList);
listView = (ListView) findViewById(R.id.listView);
listView.setEmptyView(findViewById(R.id.t));
listView.setAdapter(adapter);
}
当ListView传入内容为空时,则显示TextView,有内容时不显示:
ListView的滑动监听可以使用onTouchListener方法:
listView.setOnTouchListener(new View.OnTouchListener() {
@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;
}
});
通过手指的动作来绑定相应的事件,此方法是很多View共同的。
另一种是onScrollListener,通过set方法设置:
listView.setOnScrollListener()
并可以在匿名内部类OnScrollListener中重写OnScrollStateChanged()和OnScroll()方法:
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
//当滑动状态改变时调用
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case SCROLL_STATE_IDLE:
Log.d("测试", "停止滑动");
break;
case SCROLL_STATE_TOUCH_SCROLL:
Log.d("测试", "正在滑动");
break;
case SCROLL_STATE_FLING:
Log.d("测试", "手指抛动后的惯性滑动");
break;
}
}
//滑动时不断调用
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem + visibleItemCount == totalItemCount && totalItemCount > 0) {
Log.d("测试", "滑动到底部");
} else if (firstVisibleItem == 0) {
Log.d("测试", "滑动到顶部");
}
int lastVisibleItem = 0;
if (firstVisibleItem < lastVisibleItem) {
Log.d("测试", "下滑");
} else if (firstVisibleItem > lastVisibleItem) {
Log.d("测试", "上滑");
}
lastVisibleItem = firstVisibleItem;
}
});
可以复制上面的代码,运行感受下滑动的几种状态。