android 自学日记(五) ——ListView
前言:此篇是学习笔记,知识内容学习自:《第一行代码》、《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] = "修改的第" + 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; } });
可以复制上面的代码,运行感受下滑动的几种状态。
更多相关文章
- android DrawerLayout 实现侧滑菜单 知识整理(二)
- Android(安卓)context 文件模式
- 【Android】主线程调用Http请求无效
- Android(安卓)屏幕适配
- 利用Android两行代码真正杀死你的App
- Android批量图片加载经典系列——Volley框架实现多布局的新闻列
- 变更到Android4.4的问题
- android startActivityForResult的用法
- Android中隐式Intent的匹配规则