作者:Vamei 出處:http://www.cnblogs.com/vamei 歡迎轉載,也請保留這段宣告。謝謝!
上一講說明了資料庫中存取資料的方法。這一講將以條目的檢視方式,來以相似的檢視方式,顯示多個資料物件。這種方式特別適合於顯示從資料庫中取出的多個結構相似的資料,比如多個聯絡人,或者多個聯絡人分類。
《瑪麗蓮夢露》,這是一副現代藝術作品。聽到瑪麗蓮夢露自殺的訊息後,現代藝術家沃霍爾深為震驚。他通過重複瑪麗蓮夢露的形象,創作了這幅波普藝術的名作。每一個形象既是重複,又有變化。
描述
多個條目的檢視方式在應用中很常見,比如聯絡人目錄。我們經常會根據資料的數量,動態的調整顯示條目的個數。譬如一個社交應用顯示好友資訊。當好友數目增加或減少時,安卓需要動態的增加或減少顯示好友條目。我將介紹ListView和ListAdapter,兩者結合,可以動態的顯示條目。我將利用它們,建立一個條目頁面,顯示所有的聯絡人類別。相關知識點:
- onClickListener介面。實現點選監聽的一種新方式。
- ListView。這是一個View Group,用於包含多個條目。
- ArrayAdapter。它讓資料以特定的條目檢視格式顯示出來。
Activity實施OnClickListener介面
我將修改MainActivity,增加一個按鈕,通向新的頁面。新的頁面中將包含條目檢視。在activity_main.xml中增加按鈕元素:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/welcome"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/author"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Edit Profile" />
<Button
android:id="@+id/category"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Contact Categories" />
</LinearLayout>
上面id為category的元素為新增按鈕。
在MainActivity中監聽新的按鈕。之前的事件監聽方式,是將新建的OnClickListener物件傳遞給檢視元素。實際上,OnClickListener只是一個介面(interface)。我讓MainActivity實施OnClickListener介面,並讓MainActivity物件負責監聽:
package me.vamei.vamei;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity implements OnClickListener {
private SharedPreferences sharedPref;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sharedPref = this.getSharedPreferences("me.vamei.vamei",
Context.MODE_PRIVATE);
Button btn1 = (Button) findViewById(R.id.author);
btn1.setOnClickListener(this);
Button btn2 = (Button) findViewById(R.id.category);
btn2.setOnClickListener(this);
}
@Override
protected void onResume() {
super.onResume();
TextView nameView = (TextView) findViewById(R.id.welcome);
// retrieve content from shared preference, with key "name"
String welcome = "Welcome, " + sharedPref.getString("name", "unknown") + "!";
nameView.setText(welcome);
}
// method for interface OnClickListener
@Override
public void onClick(View v) {
Intent intent;
// Routing to different view elements
switch(v.getId()) {
case R.id.author:
intent = new Intent(this,
SelfEditActivity.class);
startActivity(intent);
break;
case R.id.category:
intent = new Intent(this,
CategoryActivity.class);
startActivity(intent);
break;
}
}
}
MainActivity實施了OnClickListener介面,因此也是一個OnClickListener型別的物件。OnClickListener介面有一個規定的方法onClick()。事件發生後,安卓將呼叫的該方法。我們用setOnClickListener的方法,讓MainActivity同時監聽兩個按鈕的點選事件。當事件觸發後,安卓呼叫onClick()方法。通過switch結構,安卓瞭解到底是哪個按鈕被點選,並針對不同的情況,啟動了不同的下游Activity。
我們當然也可以用之前的new OnClickListener()的方法,為兩個按鈕分別建立監聽物件,但會相對比較繁瑣。
可以看到,點選id為category的按鈕後,安卓將啟動CategoryActivity按鈕。這就是我們下一步將要編寫的。
使用ArrayAdapter
CategoryActivity將以條目的方式來顯示資料庫中儲存的所有Category,即聯絡人的類別。我在上一講中,已經將資料儲存到了SQLite資料庫中。我需要把資料取出,並放入到CategoryActivity的檢視中。
困難的地方在於,我無法預知資料庫中有多少個Category,因此,我沒法在設計佈局的時候靜態的說明所有的檢視元素。這個問題可以通過動態佈局的方式,用addView()方法,把檢視元素加到檢視樹中。檢視元素的動態新增,會導致安卓本身的效率會變慢。
我將使用ListView來重複利用構圖方式。ListView是一個View Group,用於管理多條佈局相似的檢視元素。例如:
可以看到,在ListView中,雖然每個條目的具體資料不同,但它們的構圖方式都相同。這樣,我不用微觀的操作每個條目,就可以把注意力放在資料的變更上。
我們建立CategoryActivity將要使用的佈局檔案activity_category.xml:
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/categoryList"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
這裡只有一個ListView便籤,作為高層框架,用於容納多個條目。至於每個條目的具體內容和顯示格式,將在下面的CategoryActivity中說明。
使用ArrayAdapter
現在,有了檢視,我們要考慮資料。當我們取出多個資料後,最自然的方式是記錄為一個表或陣列。我們需要根據小條目的佈局,為資料賦予顯示格式。最後,再把影象化的多個條目合成到ListView上。安卓提供了ArrayAdapter類,可以綜合以上功能。它可以為每個資料元素賦予相同的檢視格式。將ListView與ArrayAdapter繫結後,安卓就可以動態的調整條目了。
為資料賦予檢視格式
我在CategoryActivity.java中使用ArrayAdapter:
package me.vamei.vamei;
import java.util.ArrayList;
import java.util.List;
import me.vamei.vamei.model.Category;
import me.vamei.vamei.model.ContactsManager;
import android.os.Bundle;
import android.app.Activity;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class CategoryActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_category);
ListView listview = (ListView) findViewById(R.id.categoryList);
// retrieve data from the database
ContactsManager cm = new ContactsManager(this);
List<Category> categories = cm.getAllCategories();
// transform data to a list of strings
ArrayList<String> list = new ArrayList<String>();
for (int i = 0; i < categories.size(); ++i) {
list.add(categories.get(i).getName());
}
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, list);
// bind the listview and the adapter
listview.setAdapter(adapter);
}
}
程式碼中新建了一個ArrayAdapter物件。ArrayAdapter構造器接收三個引數,第一個為Context,第二個說明了條目的具體構圖,第三個為包含有資料的表。由於資料是字串型別的表,ArrayAdapter也有一個String的型別引數。一個ArrayAdapter中包含了資料和條目的具體格式。
需要注意的是第二個引數android.R.layout.simple_list_item_1,它是安卓框架自己提供的一個簡單的XML佈局,包含了一個TextView元素。未來的字串型資料按照該檢視元素規定的格式顯示。這個佈局的原始碼可參考連結。安卓還提供了其它一些簡易的佈局,參考連結。我們當然可以用自己的佈局來替代它。
最後,通過ListView的setAdapter()方法,把ArrayAdapter所形成的多個條目檢視(包含檢視格式和資料),放置在ListView這個大容器中:
繼承ArrayAdapter
我上面從Category型別的表中,提取出一個字串型別的表,作為資料傳遞給ArrayAdapter。ArrayAdapter隨後自動的把字串資料加工為simple_list_item_1格式。我也可以通過繼承ArrayAdapter,來建立一個新的Adapter型別。在該過程中,我可以更自由的控制對資料和ListView的繫結。下面的CategoryAdapter繼承了ArrayAdapter。它將允許我:
- 使用Category表中的資料。資料不用提前轉換為字串型別的表。
- 使用更復雜的檢視格式。控制Category物件中的多個屬性的顯示方式。
我在me.vamei.vamei中新增CategoryActivity.java。它包含了類CategoryAdapter:
package me.vamei.vamei;
import java.util.List;
import me.vamei.vamei.model.Category;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
public class CategoryAdapter extends ArrayAdapter<Category> {
private int viewId;
private Context context;
private List<Category> objects;
// Constructor
public CategoryAdapter(Context context, int viewId, List<Category> objects) {
super(context, viewId, objects);
this.context = context;
this.viewId = viewId;
this.objects = objects;
}
// For each row, control the view assigned to the data
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
// Inflate the row view, if it doesn't exist.
// ViewList is capable of reusing the views.
if(v == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(viewId, parent, false);
}
Category category = objects.get(position);
// Add data to views
if(category != null) {
TextView tv1 = (TextView) v.findViewById(R.id.seq);
if(tv1 != null) {
tv1.setText(Integer.toString(category.getId()));
}
TextView tv2 = (TextView) v.findViewById(R.id.name);
if(tv2 != null) {
tv2.setText(category.getName());
}
}
return v;
}
}
這是一個ArrayAdapter的子類。我通過編寫getView()方法,來說明每個Category物件和對應條目檢視的繫結方式。該方法的第一個引數代表了條目的編號,第二個引數是條目的檢視,第三個引數代表了母檢視,也就是整個ListView。需要注意的是第二個引數,即convertView。隨著使用者上下滑動螢幕,ListView的條目可能消失。安卓會重複利用消失條目的檢視樹,以節省重新建立條目檢視所需要的時間。convertView中就包含了這樣一個重複利用的條目檢視。如果沒有可以重複利用的條目檢視,那麼該引數就為null。此時,我們需要如if結構中那樣,重建新的條目檢視。
我將要賦予給條目的檢視佈局儲存在list_category.xml中。它在位於一行中包含了兩個TextView:
<?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="wrap_content"
android:orientation="horizontal" >
<TextView
android:id="@+id/seq"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
我們在CategoryActivity.java,來利用新建的CategoryAdapter類。在建立物件時,我把上面的條目佈局,即R.layout.list_category作為引數傳給構造器:
package me.vamei.vamei;
import java.util.ArrayList;
import java.util.List;
import me.vamei.vamei.model.Category;
import me.vamei.vamei.model.ContactsManager;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
public class CategoryActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_category);
ListView listview = (ListView) findViewById(R.id.categoryList);
ContactsManager cm = new ContactsManager(this);
final List<Category> categories = cm.getAllCategories();
CategoryAdapter adapter = new CategoryAdapter(this,
R.layout.list_category, categories);
listview.setAdapter(adapter);
listview.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,int position, long id) {
// When clicked, show a Toast text
Toast.makeText(getApplicationContext(),
"id:" + categories.get(position).getId(), Toast.LENGTH_SHORT).show();
}
});
}
}
通過新的CategoryAdapter類物件,並借用setAdapter()方法,我就把Category表中的資料和條目檢視組織到了ListView中。此後,我還通過setOnItemClickListener()方法,監聽每個條目的點選事件。
使用setTag()優化CategoryAdapter
上面已經提到,ArrayAdapter可以通過重複利用條目檢視,來優化安卓應用的效率。在ArrayAdapter中,我還可以用setTag()的方式,儲存條目中具體檢視元素的引用,從而減少使用findViewId()方法的次數。這也能提高應用的執行效率。
setTag()用於把物件“粘附”在某個檢視元素上。由於ListView中消失的條目會通過convertView引數來重複利用,我們可以為convertView附加兩個TextView元素(R.id.seq, R.id.name)的引用。當convertView被重複利用時,粘附於其上的兩個檢視元素的引用也會被重複利用,從而減少了呼叫findViewById()進行檢索的次數。
為了實踐上面的想法,我修改CategoryAdapter.java如下:
package me.vamei.vamei;
import java.util.List;
import me.vamei.vamei.model.Category;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
public class CategoryAdapter extends ArrayAdapter<Category> {
private int viewId;
private Context context;
private List<Category> objects;
// Constructor
public CategoryAdapter(Context context, int viewId, List<Category> objects) {
super(context, viewId, objects);
this.context = context;
this.viewId = viewId;
this.objects = objects;
}
private class Holder {
TextView tv1;
TextView tv2;
public Holder(TextView tv1, TextView tv2) {
this.tv1 = tv1;
this.tv2 = tv2;
}
}
// For each row, control the view assigned to the data
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
Holder holder;
// inflate the row view, if it doesn't exist.
// ViewList is capable of reusing the views.
if(v == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(viewId, parent, false);
holder = new Holder((TextView) v.findViewById(R.id.seq),
(TextView) v.findViewById(R.id.name));
v.setTag(holder);
} else {
holder = (Holder) v.getTag();
}
Category category = objects.get(position);
// add data to views
if(category != null) {
holder.tv1.setText(Integer.toString(category.getId()));
holder.tv2.setText(category.getName());
}
return v;
}
}
上面程式碼中的Holder型別的物件用於儲存兩個TextView型別的引用。在if(convertView == null)的結構中可以看出,如果條目被重複利用,粘附在條目上的Holder物件將藉助getTag()方法取出。我們可以重複利用該Holder物件中包含的兩個TextView引用,從而減少了findViewById()的呼叫次數。
總結
ArrayAdapter, getView()
setAdapter()
setOnItemClickListener()
setTag(), getTag()
歡迎繼續閱讀“Java快速教程”系列文章