MVC模式:
MVC的基本原理就是通过Controller连接View和Model。当View中所显示的数据发生变化时,会通知Controller,然后由Controller调用Model中的相关方法执行相应的数据修改操作。反之,当Model中的数据发生变化时,也会通知Controller,由Controller通知View更新显示内容。如此一来,就使得数据部分与视图部分相分离,任何一方发生改变都不会影响到另一方。
而在android中,MVC的一个常见应用就是ListView显示数据。V代表的就是显示控件;M代表的是各种数据源,可以是自己定义的List或者数组,也可以是数据库,文件等;C代表的是Adapter类,android中比较常见的adapter有:BaseAdapter,ArrayAdapter,SimpleAdapter等。
ListView通过setAdapter方法实现了其和一个Adapter对象的绑定,Adapter一般通过getView()方法返回当前列表项所要显示的View对象,完成了对Model中数据的读取。
当Model发生变化时,会调用BaseAdapter.notifyDataSetChanged()方法通知组件数据已然变化,此时Adapter就会调用getView()方法重新显示组件内容。
ListView:间接继承自抽象类AdapterView。
常见方法:
void setOnItemClickListener(AdapterView.OnItemClickListener listener)
void setOnItemLongClickListener(AdapterView.OnItemLongClickListener listener)
void setAdapter(ListAdapter adapter)
Adapter:是一个接口
主要的方法:
abstract int getCount() 返回要显示的item总数
abstract Object getItem(int position) 根据item索引返回该item
abstract long getItemId(int position) 返回item的id。
abstract View getView(int position, View convertView, ViewGroup parent)返回一个用来展示数据源中索引为position的View对象。
BaseAdapter是一个间接实现了Adapter接口的抽象类
自定义Adapter继承BaseAdapter时,需要提供上面四个方法的实现。主要要实现的是getCount()和geView()方法。
ArrayAdapter<T>, CursorAdapter, SimpleAdapter则直接继承自BaseAdapter,实现了其抽象方法。
ListView示例1:展示一个字符串数组中的各个字符串。
1 main_layout.xml: 2 3 <?xml version="1.0" encoding="utf-8"?> 4 5 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 6 7 android:layout_width="match_parent" 8 9 android:layout_height="match_parent" 10 11 android:orientation="vertical" > 12 13 <ListView 14 15 android:id="@+id/listview" 16 17 android:layout_width="wrap_content" 18 19 android:layout_height="wrap_content" 20 21 /> 22 23 </LinearLayout>
MainActivity.java:
1 public class MainActivity extends Activity { 2 3 4 5 private String[] strs; 6 7 @Override 8 9 protected void onCreate(Bundle savedInstanceState) { 10 11 // TODO Auto-generated method stub 12 13 super.onCreate(savedInstanceState); 14 15 setContentView(R.layout.main_layout); 16 17 strs = new String[]{"aaa","bbb","ccc","ddd","eee","fff","ggg","hhh","iii"}; 18 19 ListView lv = (ListView) findViewById(R.id.listview); 20 21 lv.setAdapter(new MyBaseAdapter()); 22 23 24 25 } 26 27 class MyBaseAdapter extends BaseAdapter{ 28 29 @Override 30 31 public int getCount() { 32 33 return strs.length; 34 35 } 36 37 @Override 38 39 public Object getItem(int position) { 40 41 // TODO Auto-generated method stub 42 43 return null; 44 45 } 46 47 @Override 48 49 public long getItemId(int position) { 50 51 return 0; 52 53 } 54 55 @Override 56 57 public View getView(int position, View convertView, ViewGroup parent) { 58 59 TextView tv = null; 60 61 tv = new TextView(MainActivity.this); 62 63 Log.i("listview", position+"get view"); 64 65 tv.setText(strs[position]); 66 67 tv.setTextSize(40); 68 69 tv.setTextColor(Color.RED); 70 71 return tv; 72 73 } 74 75 } 76 77 }
运行结果:
注意到,每次滚动屏幕显示新的item,或将滚出屏幕上方的item重新显示出来都会调用getItem()方法。而上面的代码中每次都会新建一个TextView,这样做是存在问题的。
getView(int position, View convertView, ViewGroup parent),其中参数convertView在允许的情况下,会保存旧的View实例,以便拿来复用。所以,可以利用该参数来优化上面的getView()实现。
代码修改如下:
1 public View getView(int position, View convertView, ViewGroup parent) { 2 3 4 5 TextView tv = null; 6 7 Log.i("listview", position+"get view"); 8 9 if(convertView == null){ 10 11 tv = new TextView(MainActivity.this); 12 13 } 14 15 else{ 16 17 tv = (TextView) convertView; 18 19 } 20 21 tv.setText(strs[position]); 22 23 tv.setTextSize(40); 24 25 tv.setTextColor(Color.RED); 26 27 return tv; 28 29 }
其实,若只是显示数组中的内容,直接使用ArrayAdapter会比较方便:
MainActivity.java修改如下:
1 private String[] strs; 2 3 @Override 4 5 protected void onCreate(Bundle savedInstanceState) { 6 7 // TODO Auto-generated method stub 8 9 super.onCreate(savedInstanceState); 10 11 setContentView(R.layout.main_layout); 12 13 strs = new String[]{"aaa","bbb","ccc","ddd","eee","fff","ggg","hhh","iii"}; 14 15 ListView lv = (ListView) findViewById(R.id.listview); 16 17 ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, strs); 18 19 lv.setAdapter(adapter); 20 21 22 23 }
运行结果:
其中android.R.layout.simple_list_item_1是系统自带的一个布局id。
一般来说,在ListView中显示的东西可能更加复杂点,只靠一个TextView肯定解决不了,这时,就可以为ListView中的item根据需要编写一个布局文件。
如:item_layout.xml:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 3 android:layout_width="match_parent" 4 5 android:layout_height="match_parent" 6 7 android:orientation="horizontal" > 8 9 <ImageView 10 11 android:layout_width="wrap_content" 12 13 android:layout_height="wrap_content" 14 15 android:src="@drawable/hero" 16 17 /> 18 19 <TextView 20 21 android:id="@+id/tv_name" 22 23 android:layout_width="wrap_content" 24 25 android:layout_height="wrap_content" 26 27 android:textSize="35sp" 28 29 /> 30 31 </LinearLayout>
用于在线性布局中水平显示一个图片和一个字符串。
修改MyAdapter内部类的getView():
1 public View getView(int position, View convertView, ViewGroup parent) { 2 3 4 5 View view = null; 6 7 Log.i("listview", position+"get view"); 8 9 if(convertView == null){ 10 11 view = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_layout, null); 12 13 } 14 15 else{ 16 17 view = convertView; 18 19 } 20 21 TextView tv = (TextView) view.findViewById(R.id.tv_name); 22 23 tv.setText(strs[position]); 24 25 return view; 26 27 }
运行结果:
另一种可能常用的Adapter是SimpleAdapter。
构造函数:SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)
context指定上下文
data指定存放数据的list对象,list中存放的是Map对象。
resource指定item的布局文件id
from和to是有序的,两者中的元素必须一一对应,from中存放的是list中每个map对象中的key值,to存放的是显示对应key获取的值的控件的id。
如:item_layout.xml:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 3 android:layout_width="match_parent" 4 5 android:layout_height="match_parent" 6 7 android:orientation="horizontal" > 8 9 <ImageView 10 11 android:id="@+id/iv" 12 13 android:layout_width="wrap_content" 14 15 android:layout_height="wrap_content" 16 17 /> 18 19 <TextView 20 21 android:id="@+id/tv_name" 22 23 android:layout_width="wrap_content" 24 25 android:layout_height="wrap_content" 26 27 android:textSize="35sp" 28 29 /> 30 31 </LinearLayout>
MainActivity.java中修改如下:
1 protected void onCreate(Bundle savedInstanceState) { 2 3 // TODO Auto-generated method stub 4 5 super.onCreate(savedInstanceState); 6 7 setContentView(R.layout.main_layout); 8 9 ListView lv = (ListView) findViewById(R.id.listview); 10 11 List<Map<String,Object>> data = new ArrayList<Map<String,Object>>(); 12 13 Map<String,Object> map = new HashMap<String, Object>(); 14 15 map.put("img", R.drawable.hero); 16 17 map.put("str", "aaa"); 18 19 data.add(map); 20 21 map = new HashMap<String, Object>(); 22 23 map.put("img", R.drawable.hero); 24 25 map.put("str", "bbb"); 26 27 data.add(map); 28 29 map = new HashMap<String, Object>(); 30 31 map.put("img", R.drawable.hero); 32 33 map.put("str", "ccc"); 34 35 data.add(map); 36 37 map = new HashMap<String, Object>(); 38 39 map.put("img", R.drawable.hero); 40 41 map.put("str", "ddd"); 42 43 data.add(map); 44 45 map = new HashMap<String, Object>(); 46 47 map.put("img", R.drawable.hero); 48 49 map.put("str", "eee"); 50 51 data.add(map); 52 53 54 55 SimpleAdapter sa = new SimpleAdapter(this, data, R.layout.item_layout, new String[]{"img","str"}, new int[]{R.id.iv,R.id.tv_name}); 56 57 lv.setAdapter(sa); 58 59 }
显示结果:
关于ListView的事件监听:
AdapterView中定义了几个设置事件监听的方法,如:
void setOnItemClickListener(AdapterView.OnItemClickListener listener)
void setOnItemLongClickListener(AdapterView.OnItemLongClickListener listener)
ListView间接继承了AdapterView,所以,要处理事件监听时,可以调用ListView中相关的方法。
注:如果调用了ListView的setOnClickListener()方法,会报如下错误:
java.lang.RuntimeException: Don‘t call setOnClickListener for an AdapterView. You probably want setOnItemClickListener instead
setOnItemClickListener()方法示例:
1 ListView lv = (ListView) findViewById(R.id.listview); 2 3 lv.setOnItemClickListener(new OnItemClickListener() { 4 5 @Override 6 7 public void onItemClick(AdapterView<?> parent, View view, 8 9 int position, long id) { 10 11 Log.i("listview","onClick"); 12 13 Log.i("listview",view.toString()); 14 15 Log.i("listview",position+""); 16 17 Log.i("listview",id+""); 18 19 TextView tv = (TextView) view.findViewById(R.id.tv_name); 20 21 Toast.makeText(MainActivity.this, tv.getText(), Toast.LENGTH_LONG).show(); 22 23 } 24 25 });
运行结果:
ListView先学到这,等学了数据库操作,也许会用到CursorAdapter等,用到时再学。