您的位置:首页 > 移动开发 > Android开发

android 自学日记(五) ——ListView

2016-04-28 12:11 411 查看
前言:此篇是学习笔记,知识内容学习自:《第一行代码》、《android群英传》、《疯狂android讲义》。

使用基础ListView

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的界面可以通过自定义布局来实现自定义的效果,接下来就来创建一个自定义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"
/>


item定位

有些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内容

ListView中已经显示的内容,在某些情况下可能需要发生变化,如果通过重新设置adapter来更新,这样可以实现,但是效率不会太高。因此,还有一种更简便的方法来实现动态修改:

adapter.notifyDataSetChanged();


修改上述Activity的button点击事件:

button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
data
= "修改的第" + n + "项";
adapter.notifyDataSetChanged();
listView.setSelection(n);
n++;
}
});


运行一下就可以看到,每次点击按钮实现修改item,并定位到修改的item。

遍历item

最常用的方法就是:

for(int i=0;i<listView.getChildCount();i++){
View view = listView.getChildAt(i);
}


处理空内容的ListView

当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滑动监听

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;
}
});


可以复制上面的代码,运行感受下滑动的几种状态。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: