安卓第八夜 玛丽莲梦露

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

时间: 2024-10-02 17:13:41

安卓第八夜 玛丽莲梦露的相关文章

安卓第四夜 概念漫游(下)

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢!   在安卓第三夜 概念漫游(上)中,我介绍了安卓最基本的功能单元和Intent的连接方式.在这个骨架之上,我们可以进一步增加一些与开发密切相关的重要概念.   Context 一个应用是由多个Activity和Service这样的功能单元组成.一个应用共享有一个Application Context对象.在功能单元内部,可以用特定的方法来调用该对象.正如名字所表示的,Contex

安卓第五夜 维纳斯的诞生

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢!   之前各讲中,分别讲解了安卓的开发环境.架构和基本概念.从这一讲开始,我将制作一个简单的应用,并通过逐步升级它的功能,连带出安卓开发的多个情境. <维纳斯的诞生>是文艺复兴早期名画.相传美神维纳斯从海的泡沫中诞生,波提切利用大胆的笔触描绘这一古典神话.画面洋溢着对人体和美的热爱.如果在中世纪或者西班牙宗教审判时期,这幅画足够波提切利上火刑架了.    任务描述 我将制作一个简

安卓第六夜 凡高的自画像

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢!   在上一讲中,我已经制作了一个简单的Android应用.项目的主要文件包括: MainActivity.java activity_main.xml 在这一讲,我将拓展应用的功能,从而允许用户输入个人信息.   <自画像>,凡高.凡高一生不得志,精神更是越来越差.在割掉自己的耳朵一部分后,画家给自己留下了这幅自画像.在当时,这幅画依然是无人问津.   描述 我将创建一个新的A

安卓第十夜 亚当的诞生

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢!   上一讲介绍了用WebView来抓取一个网页内容.这一讲我将介绍如何在安卓内部直接进行HTTP通信. <亚当的诞生>,西斯廷礼拜堂的吊顶画,米开朗基罗之作.当时的教皇强迫沉迷于雕塑的米开朗基罗画巨幅壁画.米开朗基罗认为这是在浪费自己的才华,充满愤怒的作画.当然,他又成功了.   描述 这一讲中,我将使用JSON,将数据库备份到远程的数据库中.反过来我也将从远程数据库中抓取条目

安卓第七夜 雅典学院

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢!    我之前只使用了一种持续保存数据的方法,即SharedPreferences.然而,SharedPreferences只能存储少量松散的数据,并不适合大量数据的存储.安卓带有SQLite数据库,它是一个简单版本的关系型数据库,可以应对更复杂的数据存取需求.我将在这里说明安卓中该数据库的使用方法.这里只专注于安卓中SQLite数据库的接口使用,并没有深入关系型数据库和SQL语言

安卓第三夜 概念漫游(上)

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢!    拿起一个手机,总是满屏的应用图标.点击图标,选择应用,玩一把游戏,刷一下微博,写一篇日记."叮咚"一声,邮件应用提醒有一封新来的邮件.在安卓里,眼花缭乱的应用承载着各种有趣的功能.欢迎来到安卓应用的世界. 每一个应用都是存储在手机中的一段小程序.编写这段小程序,是每个安卓开发者的主要工作.程序运行后成为一个进程后,在进程的内存空间中新建一个Dalvik虚拟机.程序

安卓第九夜 狂风

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢!   我们经常需要在安卓应用中包含简易的网页显示功能.我将在这一讲中实现网页的显示. <狂风>,来自小Willem,荷兰画派黄金时代的作品.作为当时海上马车夫的荷兰,对航海题材的画情有独钟. 这种倾斜的船身,是当时的画家常用的手法,用于表现很强的风.   描述 上一讲实现了一个类别条目页面.现在,我希望点击某个类别后,能再次以条目的方式显示所有的联系人.在这个新的条目页面中,点击

Java快速教程

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢!      Java是面向对象语言.这门语言其实相当年轻,于1995年才出现,由Sun公司出品.James Gosling领导了Java的项目小组.该项目的最初只想为家电设计一门容易移植的语言.然而,在获得了Netscape浏览器支持后,Java快速推广,应用广泛. Java受到C和C++的强烈影响.Java与C++相近,都是静态类型,但移除了C++中容易出错的一些特征,比如指针和

小米科技CEO雷军:最后的风口

雷军最后的"风口" □文/本刊记者 樊力 摄影记者 雷辉 这世上有一种人.他天生便智勇双全,一出道即天下无敌,他自小就建立起改变世界的梦想,直至真正成为一代神仙. --晕倒,有这种神仙吗?有的,<西游记>里的孙悟空,以及如今被神话包围以致自己都笃信了这种神话的企业家. 雷军(微博)当不了"神仙",也不喜欢被塑造,他清楚地记得自己的来时路.作为中国互联网界的"活化石",他也曾经相信"不怕苦,不怕累,人定胜天".只是,