Listview應該是最爲常見的控件。對於大多數規則排列的界面,幾乎均可以用ListView進行編寫。對於單一界面來講,ListView既是最難的控件,又是使用最爲頻繁的控件。ListView 一般用於展現大量的數據,好比讀取數據庫中的數據。ListView優點也較爲明顯,好比顯示大量數據時節約內存,自帶ScrollView 的功能能夠實現界面滾動等。ListView 控件的設計正好遵循MVC 設計模式。mode 數據數據模型,在ListView中具體能夠指要顯示到ListView 上的數據集合;view 視圖用於展現數據,數據顯示在每個條目上;controller 控制層,數組中的數據是沒法直接傳遞給 ListView 的,須要藉助適配器把數據展現到控件上。所以有必要對ListView的使用進行一下總結。java
Listview使用步驟並不複雜,不管是哪一種場景下使用均可以概括爲如下幾步:
1. 佈局文件裏申明ListView ,設置id號和其餘相關參數
2. 在代碼中經過findViewById方法找到listView控件
3. 設置適配器,能夠單獨定義出一個類繼承相關的適配器,也能夠建立一個適配器的匿名內部類。關鍵取決於代碼的複雜程度。
4. 重寫適配器中的方法。主要有兩個,一個是getCount()表示listview的條目數。另外一個是getView返回顯示的View ,表示每個條目的視圖。getView()方法在每一個子項被滾動到屏幕內的時候會被調用。
5. ListView列表項的點擊事件 listView.setOnItemClickLinstener()android
知道Listview的使用步驟後,還有不少須要注意的細節問題。接下來一一列舉出來。sql
首先是適配器的類型選擇。Android 中提供了不少適配器的實現類,一般狀況下使用的都是BaseAdapter,而其餘的適配器都是繼承自BaseAdapter。但有些界面控件不多,利用ArrayAdapter或者SimpleAdapter就能夠知足需求。因此在這裏仍是稍微介紹一下ArrayAdapter和SimpleAdapter的使用。文章最後給出一個學生信息管理系統的案例,用的適配器是BaseAdapter,因此這裏就再也不對BaseAdapter的使用再作講解。數據庫
1. ArrayAdapter的使用
ArrayAdapter一般用於顯示較爲簡單的數組和集合數據,界面較爲簡單,直接向ArrayAdapter添加相關的參數便可。
設計模式
public ArrayAdapter(Context context, int resource, T[] objects) { init(context, resource, 0, Arrays.asList(objects)); }
第一個參數context表示上下文。 resource是佈局文件的ID號,objects表示要顯示的數組或List集合。數組
2. SimpleAdapter
使用SimpleAdapter 的數據通常都是HashMap構成的List集合,List 的每個對象對應ListView 的每一行。HashMap 的每一個鍵值數據映射到佈局文件中對應id 的組件上。這樣能夠方便的顯示圖文顯示的條目,一般設置界面就是這種形式。
緩存
public SimpleAdapter(Context context, List<? extends Map<String, ?>> data,int resource, String[] from, int[] to) { mData = data; mResource = mDropDownResource = resource; mFrom = from; mTo = to; mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); }
context表示上下文。data表示要顯示的數據,用鍵值對的形式暫時存放數據。resource是佈局文件的ID號。from表示Map 集合中key 的數組,to表示item 佈局中控件的id。後兩個參數均是字符轉數組,表示的是多個Item條目。app
以上兩種適配器一般用於顯示較爲簡單的條目內容,好比純文本的顯示、簡單的文本和圖片組合。複雜的條目結構須要用藉助BaseaAadpter進行顯示。dom
接着是對代碼的優化。一般使用ListView時涉及到的數據不會太少,測試的時候幾十上百條的數據可能看不出影響,但當數據達到成千上萬條的時候,未優化的listView頗有可能出現異常。所以代碼優化是十分有必要的。這裏介紹兩處代碼的優化處理,一個是convertView的複用,另外一個是控件緩存機制。ide
優化一 listview的複用
convertView用於將以前加載好的佈局進行緩存,以便以後能夠進行復用。經過使用convertView 對建立的視圖對象進行復用,能夠節約減小內存消耗。對於少許相同形式的數據能夠用LinearLayout代替顯示,可是當數據增長到上千條、上萬條的時候,快速滾動滑動條就會不斷地生成新的TextView,對於內存的消耗過大,容易形成內存溢出。Listview自帶上下滑動的功能,所以能夠將滑出屏幕的convertView進行回收利用,每當一個item看不見的時候,那個item就能夠被複用起來了,這樣,ListView 始終保持建立的對象個數爲屏幕顯示的條目的個數加一。事實上,item的view對象沒有真正的被垃圾回收器回收掉,而是從新將身上的數據給換掉了,看起來好像是出現了一個新的item。這樣視覺上就是連續的滾動條了。
listview複用具體的作法是在 getView()方法中首先進行了判斷convertView 的內容是否爲空,若是convertView的內容爲空則用layoutInflater 去加載佈局,若是不爲空則直接對 convertView 進行復用。這樣就大大提升了ListView 的運行效率,在快速滾動的時候也能夠表現出更好的性能,不再用擔憂內存溢出了。
優化二 緩存控件的實例
獲取佈局文件中的控件,每次都要在getView()方法中調用 View的findViewById()方法來獲取一次控件的實例,當須要屢次關心控件時,就會建立屢次,所以能夠定義一個內部類,用於存放控件,最後將該類的對象存放在view的對象中。當 convertView 爲空時,對控件的實例進行緩存,當 convertView 不爲空的時候,從內部類中取出控件的實例,這樣就減小了調用findViewById()方法的次數了,一樣提升了代碼的運行效率。
優化後的代碼:
/** * 兩點優化 * 1. 判斷convertView是否爲空 * 2. 內部類緩存控價 */ @Override public View getView(final int position, View convertView, ViewGroup parent) { View view; Viewholder viewholder; Student student = list.get(position); if (convertView == null) { view = View.inflate(context, R.layout.item, null); viewholder = new Viewholder(); viewholder.tv_name = (TextView) view.findViewById(R.id.tv_name); viewholder.iv_sex = (ImageView) view.findViewById(R.id.iv_sex); viewholder.iv_delete = (ImageView) view.findViewById(R.id.iv_delete); view.setTag(viewholder); } else { view = convertView; viewholder = (Viewholder) view.getTag(); } String sex = student.getSex(); if ("male".equals(sex)) { viewholder.iv_sex .setImageResource(R.drawable.nan); } else { viewholder.iv_sex .setImageResource(R.drawable.nv); } viewholder.tv_name.setText(student.getName()); viewholder.iv_delete.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { Student student = list.get(position); String name = student.getName(); dao.delete(name); Toast.makeText(context, "數據被刪除了", 0).show(); refreshData1(); } }); return view; } class Viewholder{ TextView tv_name; ImageView iv_sex,iv_delete; }
接下來說一下ListView的界面優化。
對於ArrayAdapter來講可使用Android 系統提供的簡單佈局,好比android.R.layout.simple_list_item_1。但這種方式較爲固定,沒法知足實際開發多樣化的須要。所以能夠從新建立一個佈局文件,對界面進行自定義,根據本身的須要添加相應的控件。這樣能夠豐富界面的效果,提高軟件美感,適應不一樣的開發需求。View對象自己或者任何繼承View對象的控件都有一個方法,那就是inflate。經過inflate方法能夠將一個佈局文件轉換成一個View對象,佈局裏面的全部控件均可以經過這個View對象來進行查找。Inflate的使用方式有如下幾種。
1. 直接用View.inflate()建立View對象,這是View自帶的方法。
public static View inflate(Context context, int resource, ViewGroup root) { LayoutInflater factory = LayoutInflater.from(context); return factory.inflate(resource, root); }
2. 參考inflate源碼,能夠獲取LayoutInflater 對象,而後調用inflate 方法
LayoutInflater.from(context).inflate(resource, null)
3.經過上下文提供的getSystemService 方法獲取LayoutInfater 對象,而後調用inflate 方法
view=getSystemService(Context.LAYOUT_INFLATER_SERVICE).inflate(R.resource, null);
三種方式差異並不大,可任意選擇。獲得View對象後,能夠經過View對象找到佈局文件中的控件,佈局文件能夠單獨定義一個xml文件用來表示每個Item條目的顯示效果。
接下來要說明的是條目點擊。 Listview 主要有兩種交互方式,一個是條目滾動,另外一是條目點擊。前者用於顯示,後者用於界面交互,點擊以後能夠進入其餘界面或相應的對話框。點擊條目歸根究竟是點擊事件,使用 setOnItemClickListener()方法來爲 ListView 註冊了一個監聽器,當用戶點擊了 ListView 中的任何一個子項時就會回調 onItemClick()方法, 在這個方法中能夠經過 position 參數判斷出用戶點擊的是哪個子項,而後執行接下來的邏輯。條目點擊大大地提高了界面的交互性,能夠經過點擊條目來執行更爲複雜的任務。
/** * * 刷新數據,建立條目點擊事件 */ private void refreshData() { list = dao.findAll(); if(adapter == null ){ adapter = new Myadapter(MainActivity.this,list); lv.setAdapter(adapter); lv.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { AlertDialog.Builder builder = new Builder(MainActivity.this); builder.setTitle("詳細信息:"); builder.setMessage("姓名:" + list.get(position).getName() +" 性別:"+list.get(position).getSex() ); builder.show(); } }); }else{ adapter.refreshData1(); } }
最後一個小細節不要忘了,那就是數據更新。不少人在編寫listview過程當中常常會忘了數據更新,這樣致使的結果就是,對數據進行增刪改操做時界面不能及時刷新,只有重啓程序,才能夠看到界面上的數據發生改變。Listview的界面刷新有兩種方式。一種是在getview中直接進行刷新,另外一種則是單獨定義一個方法對數據進行刷新。這裏介紹第二種。在單獨定義的數據方法中,一般要作的是,首先獲得要在listview中顯示的數據,能夠將數據存放在List集合中。而後判斷適配器對象是否爲空,若爲空則定義一個新的適配器出來,不然直接調用適配器的notifyDataSetChanged()方法進行刷新。數據刷新是Listview中的一個小細節,稍微一步注意就會影響到界面的顯示效果,所以要引發格外的注意。
至此,ListView的所有內容講解完畢。接下來用一個實例來鞏固以上的知識點。
設計一個學生信息管理系統用於記錄學生姓名和性別,用到的知識點包括數據庫、listview顯示。首先搭建軟件界面,須要一個輸入框和單選框用於編輯學生的姓名和性別。還須要一個按鈕用於保存數據,提交數據。而後須要一個listview用於顯示全部學生的信息。對於每個條目來講,能夠用圖片來區別男女,用文本顯示學生姓名。最後還能夠添加一個刪除的圖片對數據進行刪除操做。接下來是邏輯部分。首先經過設置點擊事件,當按鈕按下時,將文本框和單選框裏面內容寫入數據庫,同時通知Listview刷新界面,這樣就能夠看到listview中的數據添加了一行。一樣的將刪除圖標設置爲一個點擊事件,點擊刪除圖標時刪除數據庫中的相關信息,同時通知Listview刷新界面,這樣界面上的listview就會減小一行。最後能夠爲每個條目添加一個條目點擊事件。當條目被點擊時,彈出一個對話框,列出學生的詳細信息。至此,用到listview的大部分知識點。
適這裏適配器用的是BaseAdapter,單獨定義一個類出來繼承BaseAdapter,配器程序編寫以下:
package com.example.studentsysten; import java.util.List; import com.example.studentsysten.db.dao.StudentDao; import com.example.studentsysten.db.domain.Student; import android.content.Context; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; public class Myadapter extends BaseAdapter { private List<Student> list; private Context context; private StudentDao dao; private Myadapter adapter; public Myadapter(Context context, List<Student> list) { this.list = list; this.context = context; dao = new StudentDao(context); } /** * * 在getview中刷新數據 */ public void refreshData1() { list = dao.findAll(); notifyDataSetChanged(); } @Override public int getCount() { return list.size(); } @Override public long getItemId(int position) { return 0; } @Override public Object getItem(int position) { return null; } /** * 兩點優化 * 1. 判斷convertView是否爲空 * 2. 內部類緩存控價 */ @Override public View getView(final int position, View convertView, ViewGroup parent) { View view; Viewholder viewholder; Student student = list.get(position); if (convertView == null) { view = View.inflate(context, R.layout.item, null); viewholder = new Viewholder(); viewholder.tv_name = (TextView) view.findViewById(R.id.tv_name); viewholder.iv_sex = (ImageView) view.findViewById(R.id.iv_sex); viewholder.iv_delete = (ImageView) view.findViewById(R.id.iv_delete); view.setTag(viewholder); } else { view = convertView; viewholder = (Viewholder) view.getTag(); } String sex = student.getSex(); if ("male".equals(sex)) { viewholder.iv_sex .setImageResource(R.drawable.nan); } else { viewholder.iv_sex .setImageResource(R.drawable.nv); } viewholder.tv_name.setText(student.getName()); viewholder.iv_delete.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { Student student = list.get(position); String name = student.getName(); dao.delete(name); Toast.makeText(context, "數據被刪除了", 0).show(); refreshData1(); } }); return view; } class Viewholder{ TextView tv_name; ImageView iv_sex,iv_delete; } }
程序的主邏輯:
package com.example.studentsysten; import java.util.List; import android.app.Activity; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.content.DialogInterface; import android.os.Bundle; import android.text.TextUtils; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.BaseAdapter; import android.widget.EditText; import android.widget.ImageView; import android.widget.ListView; import android.widget.RadioGroup; import android.widget.TextView; import android.widget.Toast; import com.example.studentsysten.db.dao.StudentDao; import com.example.studentsysten.db.domain.Student; public class MainActivity extends Activity { private EditText et_name; private RadioGroup rg_sex; private ListView lv; private StudentDao dao; private Myadapter adapter; private List<Student> list; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); et_name = (EditText) findViewById(R.id.et_name); rg_sex = (RadioGroup) findViewById(R.id.rg_sex); lv = (ListView) findViewById(R.id.lv); dao = new StudentDao(this); refreshData(); //初次加載時的刷新數據,每次修改數據時刷新數據 } /** * * 添加數據 * @param view */ public void save(View view){ String name = et_name.getText().toString().trim(); if(TextUtils.isEmpty(name)){ Toast.makeText(MainActivity.this, "姓名不能爲空", 0).show(); return; } int id = rg_sex.getCheckedRadioButtonId(); String sex = "male"; if(id==R.id.male){ sex = "male"; }else{ sex = "female"; } long result = dao.add(name, sex); Toast.makeText(this, "數據添加到第"+result+"行成功", 0).show(); refreshData(); } /** * * 刷新數據,建立條目點擊事件 */ private void refreshData() { list = dao.findAll(); if(adapter == null ){ adapter = new Myadapter(MainActivity.this,list); lv.setAdapter(adapter); lv.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { AlertDialog.Builder builder = new Builder(MainActivity.this); builder.setTitle("詳細信息:"); builder.setMessage("姓名:" + list.get(position).getName() +" 性別:"+list.get(position).getSex() ); builder.show(); } }); }else{ adapter.refreshData1(); } } }
另外還用到數據庫的知識,須要建立三個類出來,分別是建立數據庫、操做數據庫、學生工具類。
建立數據庫:
package com.example.studentsysten.db; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; public class StudentDBOpenhelper extends SQLiteOpenHelper { public StudentDBOpenhelper(Context context) { super(context, "stu.db", null, 1); // TODO Auto-generated constructor stub } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("create table student (_id integer primary key autoincrement,name varchar(30),sex varchar(10))"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub } }
操做數據庫,數據庫的增刪改查:
package com.example.studentsysten.db.dao; import java.util.ArrayList; import java.util.List; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import com.example.studentsysten.db.StudentDBOpenhelper; import com.example.studentsysten.db.domain.Student; /** * 每一種數據操做都用了兩種方法 * */ public class StudentDao { private StudentDBOpenhelper helper; public StudentDao(Context context) { helper = new StudentDBOpenhelper(context); } public long add(String name, String sex) { SQLiteDatabase db = helper.getWritableDatabase(); // db.execSQL("insert into student (name,sex) values(?,?)",new // Object[]{name,sex}); ContentValues values = new ContentValues(); values.put("name", name); values.put("sex", sex); long result = db.insert("student", null, values); db.close(); return result; } public int delete(String name) { SQLiteDatabase db = helper.getWritableDatabase(); // db.execSQL("delete from student where name = ?",new Object[]{name}); int result = db.delete("student", "name = ?", new String[] { name }); db.close(); return result; } public int update(String name, String sex) { SQLiteDatabase db = helper.getWritableDatabase(); // db.execSQL("update student set sex = ? where name = ?",new // Object[]{sex,name}); ContentValues values = new ContentValues(); values.put("sex", sex); int result = db.update("student", values, "name = ?", new String[] { name }); db.close(); return result; } // 根據姓名查性別 // public String find(String name){ // SQLiteDatabase db = helper.getReadableDatabase(); // Cursor cursor = db.rawQuery("select sex from student where name = ?", new // String[]{name}); // // Cursor cursor = db.query("student", new String[]{"sex"}, "name = ?", // new String[]{name}, null, null, null); // String sex = null; // if(cursor.moveToNext()){ // sex = cursor.getString(0); // } // return sex; // } public String find(String name) { String sex = null; SQLiteDatabase db = helper.getReadableDatabase(); // Cursor cursor = db.rawQuery("select sex from student where name=?", // new String[]{name}); Cursor cursor = db.query("student", new String[] { "sex" }, "name=?", new String[] { name }, null, null, null); boolean result = cursor.moveToNext(); if (result) { sex = cursor.getString(0); } cursor.close();// 釋放資源 db.close(); return sex; } public List<Student> findAll() { List<Student> list = new ArrayList<Student>(); SQLiteDatabase db = helper.getReadableDatabase(); // Cursor cursor = db.rawQuery("select name,sex from student", null); Cursor cursor = db.query("student", new String[] { "name", "sex" }, null, null, null, null, null); while (cursor.moveToNext()) { String name = cursor.getString(0); String sex = cursor.getString(1); Student stu = new Student(); stu.setName(name); stu.setSex(sex); list.add(stu); } cursor.close(); db.close(); return list; } }
學生工具類,存放學生信息:
package com.example.studentsysten.db.domain; public class Student { private String name; private String sex; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public String toString() { return "Student [name=" + name + ", sex=" + sex + "]"; } }
工程目錄結構: