文章出處

作者: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快速教程”系列文章  


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()