Android应用开发:数据存储和界面展现-2

    xiaoxiao2021-03-25  152

    1. pull解析XML文件

    Android推荐使用pull解析XML文件,与SAX解析XML文件类似,都是事件驱动类型的解析方式。

    示例:获取天气信息

    res\layout\activity_main.xml

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="获取天气信息" android:onClick="click"/> </RelativeLayout>

    src/weather.xml

    <?xml version="1.0" encoding="utf-8"?> <weather> <city> <name>西安</name> <temp>22°</temp> <pm25>100</pm25> </city> <city> <name>上海</name> <temp>24°</temp> <pm25>120</pm25> </city> <city> <name>北京</name> <temp>30°</temp> <pm25>800</pm25> </city> </weather>

    src/cn.itcast.pullParser.domain/City.java

    package cn.itcast.pullparser.domain; public class City { private String name; private String temp; private String pm25; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getTemp() { return temp; } public void setTemp(String temp) { this.temp = temp; } public String getPm25() { return pm25; } public void setPm25(String pm25) { this.pm25 = pm25; } @Override public String toString() { return "City [name=" + name + ", temp=" + temp + ", pm25=" + pm25 + "]"; } }

    src/cn.itcast.pullparser/MainActivity.java

    package cn.itcast.pullparser; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import org.xmlpull.v1.XmlPullParser; import android.app.Activity; import android.os.Bundle; import android.util.Xml; import android.view.View; import cn.itcast.pullparser.domain.City; public class MainActivity extends Activity { List<City> cityList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void click(View v){ //获取xml文件 InputStream is = getClassLoader().getResourceAsStream("weather.xml"); //获取pull解析器 //XmlPullParser是一个接口,使用Xml.newPullParser()获取XmlPullParser对象 XmlPullParser xp = Xml.newPullParser(); //初始化 //Android系统默认编码是“utf-8” try { xp.setInput(is,"utf-8"); //开始解析 //获取事件类型,通过对事件类型的判断,直到当前解析的是什么节点 int type = xp.getEventType(); City city = null; while(type != XmlPullParser.END_DOCUMENT){ switch(type){ case XmlPullParser.START_TAG: //获取当前节点的名字 if("weather".equals(xp.getName())){ cityList = new ArrayList<City>(); }else if("city".equals(xp.getName())){ city = new City(); }else if("name".equals(xp.getName())){ //获取当前节点的下一个节点的文本 String name = xp.nextText(); city.setName(name); }else if("temp".equals(xp.getName())){ String temp = xp.nextText(); city.setTemp(temp); }else if("pm25".equals(xp.getName())){ String pm25 = xp.nextText(); city.setPm25(pm25); } break; case XmlPullParser.END_TAG: if("city".equals(xp.getName())){ cityList.add(city); } break; } //把指针移动至下一个节点,并返回该节点的事件类型 type = xp.next(); } } catch (Exception e) { e.printStackTrace(); } for(City c:cityList){ System.out.println(c.toString()); } } }

    运行结果:

    1.1 用debug查看pull解析流程

    首先,双击代码左侧,打一个断点。

    点击debug “01_pull解析XML文件”。

    等待debug初始化,跳出对话框,不要点“Force close”!等待一会,对话框就会消失。

    点击“获取天气信息”按钮。

    点击“yes”,程序进入debug模式。

    通过“step over”即可以查看代码执行的每一步。

    2. 测试概念

    按岗位分:

    黑盒测试:测试业务逻辑白盒测试:测试逻辑方法

    按测试粒度分:

    方法测试 function 单元测试 unit:多个方法集成一个单元,测试集成测试 intergration:多个单元集成,测试系统测试 system:服务器端、客户端端联动,测试整个系统

    按暴力程度分:

    冒烟测试 smoke压力测试 pressure:针对服务器端的测试。

    目前的智能手机都有测试框架,例如,Android自带的monkey,不过需要在Android命令行中使用。

    示例:随机点击模拟器一千次

    进入测试状态…

    2.1 单元测试

    创建一个类,继承AndroidTestCase

    src/cn.itcast.junit/Test.java

    package cn.itcast.junit; import cn.itcast.junit.tools.Tools; import android.test.AndroidTestCase; public class Test extends AndroidTestCase { //定义在测试框架中的方法可以直接运行 public void test(){ int result = Tools.damage(8, 3); //断言,对比实际结果与预期是否一致 assertEquals(5, result); } }

    src/cn.itcast.junit.tools/Tools.java

    package cn.itcast.junit.tools; public class Tools { public static int damage(int i,int j){ return i + j; } }

    使用测试框架需要在清单文件中配置指令集和使用类库。

    两种方式启动测试:方法一:在代码区内选择需要测试的方法–>右击Run As–>Android JUnit Test。

    方法二:在Outline内选择需要测试的方法–>右击Run As–>Android JUnit Test。

    通过下图,可以看到,报错,结果与断言不相符。

    修改Tools.java中代码。

    通过下图,可以看到,测试运行成功,结果与断言相符。

    通过Console可以看到测试过程。

    虽然,测试过程中,Android项目并没有启动,整个测试过程可以看到模拟器上根本没有前台界面。但是,测试需要模拟器运行测试框架。因为,测试的项目是Android代码,必须运行在Android设备上,可以是模拟器也可以是真机。没有返回值的方法测试步骤相同,只是不需要断言。

    测试后,报错,提示出现异常。

    修改代码,测试成功。

    3. 创建数据库

    Android中存储数据用的最多的还是数据库,SQLite(Android自带的轻量级数据库)。 数据库保存在内部存储空间。所以,在备份数据的时候,不会写在数据库里。一旦应用删除,数据库就没了,备份的数据也就全没了。

    示例:创建数据库

    创建一个类,MyOpenHelper,继承SQLiteOpenHelper。

    src/cn.itcast.sqlite/MyOpenHelper.java

    package cn.itcast.sqlite; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; public class MyOpenHelper extends SQLiteOpenHelper { public MyOpenHelper(Context context, String name, CursorFactory factory, int version) { //第一个参数:用来打开和创建数据库的上下文 //第二个参数:数据库文件名字 //第三个参数:游标工厂,用来创建游标对象的工厂。如果传null,表示使用默认的游标工厂 //第四个参数:数据库的版本,最低为1,如果数据库做升级,版本值只能升,不能降 super(context, name, factory, version); } //数据库创建时,此方法调用 @Override public void onCreate(SQLiteDatabase db) { } //数据库升级时,此方法调用 @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }

    创建一个测试类,继承AndroidTestCase。 src/cn.itcast.sqlite/MyOpenHelper.java

    package cn.itcast.sqlite; import android.database.sqlite.SQLiteDatabase; import android.test.AndroidTestCase; public class Test extends AndroidTestCase { public void createDataBase(){ //创建数据库 //1. 创建打开帮助器 //测试框架提供了getContext()方法,获取虚拟上下文 MyOpenHelper oh = new MyOpenHelper(getContext(), "people.db", null, 1); //2. 创建并打开数据库 //如果数据库不存在,先创建,后打开。如果数据库存在,直接打开 //getWritableDatabase创建的数据库可读可写。 //getReadableDatabase返回的数据库也是可读可写。但是,在某些情况下,例如,数据库满了(但是现在,手机内部存储器都比较大,基本上不会出现这种情况),getReadableDatabase就会返回一个只读的数据库。 SQLiteDatabase db = oh.getWritableDatabase(); } }

    配置清单文件:

    测试运行结果:

    生成数据库成功。

    导出people.db,拖入SQLite Expert,可以看到自动生成了一张表,此表不能修改,不用管它。

    3.1 创建表

    创建数据库完毕后会调用onCreate方法,数据库升级后会调用onUpgrade方法。

    示例:src/cn.itcast.sqlite/MyOpenHelper.java

    package cn.itcast.sqlite; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; public class MyOpenHelper extends SQLiteOpenHelper { public MyOpenHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); } //数据库创建完之后,才会调用这个函数 @Override public void onCreate(SQLiteDatabase db) { System.out.println("数据库创建"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { System.out.println("数据库升级"); } }

    运行结果: 首先,删除创建好的数据库,重新创建数据库。

    可以看到onCreate方法被调用。

    然后,升级数据库版本。

    可以看到onUpgrade方法被调用。

    示例:创建表 src/cn.itcast.sqlite/MyOpenHelper.java

    package cn.itcast.sqlite; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; public class MyOpenHelper extends SQLiteOpenHelper { public MyOpenHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); } @Override public void onCreate(SQLiteDatabase db) { //执行sql语句 //Android所有自带的数据库主键都带下划线,入乡随俗 //所有字段类型对于SQLite都是varchar类型,sql语句写数据类型是写给程序员看的 db.execSQL("create table person(_id integer primary key autoincrement,name char(10),phone char(20),salary integer(10))"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { System.out.println("数据库升级"); } }

    src/cn.itcast.sqlite/Test.java

    package cn.itcast.sqlite; import android.database.sqlite.SQLiteDatabase; import android.test.AndroidTestCase; public class Test extends AndroidTestCase { public void createDataBase(){ MyOpenHelper oh = new MyOpenHelper(getContext(), "people.db", null, 1); SQLiteDatabase db = oh.getWritableDatabase(); } }

    测试运行结果: 首先,删除数据库,重新创建数据库,创建表。然后导出people.db,拖入SQLite Expert,刷新。可以看到,生成了新的person表。

    表格中的RecNo是不存在的字段,只是为了让开发人员能够方便地看到记录行数。

    3.2 使用SQL语句插入删除

    示例:src/cn.itcast.sqlite/Test.java

    package cn.itcast.sqlite; import android.database.sqlite.SQLiteDatabase; import android.test.AndroidTestCase; public class Test extends AndroidTestCase { private MyOpenHelper oh; private SQLiteDatabase db; //测试框架初始化完毕后,测试方法执行前,此方法调用 @Override protected void setUp() throws Exception { super.setUp(); oh = new MyOpenHelper(getContext(), "people.db", null, 1); db = oh.getWritableDatabase(); } public void createDataBase(){ MyOpenHelper oh = new MyOpenHelper(getContext(), "people.db", null, 1); SQLiteDatabase db = oh.getWritableDatabase(); } public void insert(){ db.execSQL("insert into person(name,phone,salary) values(?,?,?)",new Object[]{"云鹤",138438,"13000"}); db.execSQL("insert into person(name,phone,salary) values(?,?,?)",new Object[]{"云鹤儿子",138438,"13000"}); db.execSQL("insert into person(name,phone,salary) values(?,?,?)",new Object[]{"云鹤孙子",138438,"13000"}); db.execSQL("insert into person(name,phone,salary) values(?,?,?)",new Object[]{"云鹤曾孙",138438,"13000"}); db.execSQL("insert into person(name,phone,salary) values(?,?,?)",new Object[]{"王亚聪",138438,"13000"}); } public void delete(){ db.execSQL("delete from person where name = ?",new Object[]{"王亚聪"}); } //测试方法执行完毕后,执行。 @Override protected void tearDown() throws Exception { super.tearDown(); db.close(); } }

    测试运行结果: 首先,测试insert方法,插入数据,然后导出people.db,拖入SQLite Expert,刷新。可以看到,插入数据成功。

    然后,测试delete方法,删除一条数据,导出people.db,拖入SQLite Expert,刷新。可以看到,删除数据成功。

    如果将代码调整,如下图,测试insert方法或delete方法,就会报空指针异常。

    原因分析:测试框架需要先被系统创建出来,然后再对它做各个参数的初始化,所有初始化做完之后,虚拟上下文才会存在。但是,private MyOpenHelper oh = new MyOpenHelper(getContext(), “people.db”, null, 1);这条语句是在测试框架构造方法被调用之前调用的,因此,虚拟上下文(getContext方法返回值)根本不存在,获取不到。所以,MyOpenHelper根本new不出来。如此,才会报空指针异常。但是,setUp方法是在测试框架初始化完毕后,测试方法执行前,此方法才被调用。因此,虚拟上下文已经存在,没有任何问题。

    3.3 使用SQL语句修改查询

    示例:src/cn.itcast.sqlite/Test.java

    package cn.itcast.sqlite; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.test.AndroidTestCase; public class Test extends AndroidTestCase { private MyOpenHelper oh; private SQLiteDatabase db; @Override protected void setUp() throws Exception { super.setUp(); oh = new MyOpenHelper(getContext(), "people.db", null, 1); db = oh.getWritableDatabase(); } public void update(){ db.execSQL("update person set salary = ? where name = ?",new Object[]{10000,"云鹤"}); } public void select(){ Cursor cursor = db.rawQuery("select name,salary from person where name = ?", new String[]{"云鹤"}); //从游标中取出数据,方法与结果集类似 while(cursor.moveToNext()){ //只能传入索引值,索引值根据sql语句中查询内容所在的顺序而定,例如,上面的sql语句,name在第一位,索引即为0 String name = cursor.getString(0); //可以通过字段名称获取列索引,再通过列索引获取内容 String salary = cursor.getString(cursor.getColumnIndex("salary")); System.out.println(name + ":" + salary); } } @Override protected void tearDown() throws Exception { super.tearDown(); db.close(); } }

    测试运行结果: 测试update方法,更新一条数据,导出people.db,拖入SQLite Expert,刷新。可以看到,更新数据成功。

    测试select方法,查询数据,打印出来。可以看到,查询成功。

    3.4 使用API完成插入

    使用SQL语句不太方便,可以使用API完成增删改查操作。

    示例:src/cn.itcast.sqlite/Test.java

    package cn.itcast.sqlite; import android.content.ContentValues; import android.database.sqlite.SQLiteDatabase; import android.test.AndroidTestCase; public class Test extends AndroidTestCase { private MyOpenHelper oh; private SQLiteDatabase db; @Override protected void setUp() throws Exception { super.setUp(); oh = new MyOpenHelper(getContext(), "people.db", null, 1); db = oh.getWritableDatabase(); } public void insertApi(){ ContentValues values = new ContentValues(); values.put("name","亚聪和云鹤"); values.put("phone","138888"); values.put("salary","99999999"); //第一个参数:表名 //第二个参数:nullColumnHack,当第三个参数ContentValues对象为null,或者里面没有任何数据时,第二个参数才会有效。开发中,ContentValues都是有值的,所以直接传入null即可 //第三个参数:通过键值对把要插入的数据封装至ContentValues对象中,键名必须是字段名 //db.insert返回值为行id,如果为-1,表示插入失败 long l1 = db.insert("person", null, values); //ContentValues下次使用的时候记得清空 values.clear(); values.put("name","亚聪和云鹤的儿子"); values.put("phone","138888"); values.put("salary","99999999"); long l2 = db.insert("person", null, values); System.out.println(l1 + ":" + l2); } @Override protected void tearDown() throws Exception { super.tearDown(); db.close(); } }

    测试运行结果: 测试insertApi方法,插入一条数据,导出people.db,拖入SQLite Expert,刷新。可以看到,插入数据成功。

    3.5 使用API完成删改查

    示例:src/cn.itcast.sqlite/Test.java

    package cn.itcast.sqlite; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.test.AndroidTestCase; public class Test extends AndroidTestCase { private MyOpenHelper oh; private SQLiteDatabase db; @Override protected void setUp() throws Exception { super.setUp(); oh = new MyOpenHelper(getContext(), "people.db", null, 1); db = oh.getWritableDatabase(); } public void deleteApi(){ //返回被删除的行的数量 int i = db.delete("person", "name = ?", new String[]{"云鹤儿子"}); System.out.println(i); } public void updateApi(){ ContentValues values = new ContentValues(); values.put("salary",10500); int i = db.update("person", values, "name = ?", new String[]{"云鹤"}); System.out.println(i); } public void selectApi(){ //第二个参数如果传入null,表示查询所有字段 Cursor cursor = db.query("person", null, null, null, null, null, null, null); while(cursor.moveToNext()){ String name = cursor.getString(1); String phone = cursor.getString(2); String salary = cursor.getString(3); System.out.println(name + ":" + phone + ":" + salary); } } @Override protected void tearDown() throws Exception { super.tearDown(); db.close(); } }

    测试运行结果:

    测试deleteApi方法,删除一条数据,导出people.db,拖入SQLite Expert,刷新。可以看到,删除数据成功。

    测试updateApi方法,更新一条数据,导出people.db,拖入SQLite Expert,刷新。可以看到,更新数据成功。

    测试selectApi方法,查询数据,打印出来。可以看到,查询成功。

    3.6 SQLite的事务

    示例:src/cn.itcast.sqlite/Test.java

    package cn.itcast.sqlite; import android.content.ContentValues; import android.database.sqlite.SQLiteDatabase; import android.test.AndroidTestCase; public class Test extends AndroidTestCase { private MyOpenHelper oh; private SQLiteDatabase db; @Override protected void setUp() throws Exception { super.setUp(); oh = new MyOpenHelper(getContext(), "people.db", null, 1); db = oh.getWritableDatabase(); } public void transaction(){ try{ //开启事务 db.beginTransaction(); ContentValues values = new ContentValues(); values.put("salary", 11000); db.update("person", values, "name = ?", new String[]{"云鹤"}); values.put("salary", 12500); db.update("person", values, "name = ?", new String[]{"云鹤孙子"}); //如果下面这行代码执行,说明事务执行成功 db.setTransactionSuccessful(); }catch(Exception e){ e.printStackTrace(); }finally{ //关闭事务,这时候就提交了,不用再次提交 //关闭时候的时候,如果发现db.setTransactionSuccessful();语句未执行,那么就会回滚事务 db.endTransaction(); } } @Override protected void tearDown() throws Exception { super.tearDown(); db.close(); } }

    测试运行结果; 测试transaction方法导出people.db,拖入SQLite Expert,刷新。可以看到,事务执行成功。

    如果对代码作如下修改,事务执行中出现异常。

    可以看到,报异常,并且数据未发生任何变化。

    3.7 把数据库中的数据显示至屏幕

    src/cn.itcast.showdata/MyOpenHelper.java

    package cn.itcast.showdata; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class MyOpenHelper extends SQLiteOpenHelper { public MyOpenHelper(Context context) { super(context, "people.db", null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("create table person(_id integer primary key autoincrement,name char(10),phone char(20),salary integer(10))"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { System.out.println("数据库升级"); } }

    AndroidManifest.xml

    <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cn.itcast.showdata" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17" /> <instrumentation android:name="android.test.InstrumentationTestRunner" android:targetPackage="cn.itcast.showdata" ></instrumentation> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <uses-library android:name="android.test.runner"/> <activity android:name="cn.itcast.showdata.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

    src/cn.itcast.showdata/Test.java

    package cn.itcast.showdata; import android.content.ContentValues; import android.database.sqlite.SQLiteDatabase; import android.test.AndroidTestCase; public class Test extends AndroidTestCase { private MyOpenHelper oh; private SQLiteDatabase db; @Override protected void setUp() throws Exception { super.setUp(); oh = new MyOpenHelper(getContext()); db = oh.getWritableDatabase(); } public void insertApi(){ for(int i = 0; i < 50; i++){ ContentValues values = new ContentValues(); values.put("name","张" + i); values.put("phone","138" + i + i); values.put("salary","200" + i); db.insert("person", null, values); } } @Override protected void tearDown() throws Exception { super.tearDown(); db.close(); } }

    src/cn.itcast.showdata.domain/Person.java

    package cn.itcast.showdata.domain; public class Person { private int _id; private String name; private String phone; private int salary; public Person(int _id, String name, String phone, int salary) { super(); this._id = _id; this.name = name; this.phone = phone; this.salary = salary; } public int get_id() { return _id; } public void set_id(int _id) { this._id = _id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public int getSalary() { return salary; } public void setSalary(int salary) { this.salary = salary; } @Override public String toString() { return "name=" + name + ", phone=" + phone+ ", salary=" + salary; } }

    res\layout\activity_main.xml

    <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" > <LinearLayout android:id="@+id/ll" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:orientation="vertical"> </LinearLayout> </ScrollView>

    src/cn.itcast.showdata/MainActivity.java

    package cn.itcast.showdata; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.widget.LinearLayout; import android.widget.TextView; import cn.itcast.sqlite.domain.Person; public class MainActivity extends Activity { List<Person> personList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); personList = new ArrayList<Person>(); //读取数据库 MyOpenHelper oh = new MyOpenHelper(this); SQLiteDatabase db = oh.getWritableDatabase(); Cursor cursor = db.query("person", null, null, null, null, null, null, null); while(cursor.moveToNext()){ int _id = cursor.getInt(0); String name = cursor.getString(1); String phone = cursor.getString(2); int salary = cursor.getInt(3); Person p = new Person(_id, name, phone, salary); personList.add(p); } //获取线性布局 LinearLayout ll = (LinearLayout) findViewById(R.id.ll); for(Person p : personList){ TextView tv = new TextView(this); tv.setText(p.toString()); tv.setTextSize(16); //把tv设置为线性布局的子节点 ll.addView(tv); } } }

    运行结果:

    首先,在模拟器上运行应用程序,在data/data目录下创建应用程序文件目录。运行Test类中的insertApi方法,导出people.db文件,拖入SQLite Expert,可以看到插入数据成功。

    然后,再次在模拟器上运行应用程序,将数据库中的数据通过TextView的形式展示在屏幕上。

    使用ScrollView包裹LinearLayout,ScrollView表示可以上下滑动的View。如果线性布局过大,超出屏幕显示范围,那么就可以上下滑动了。

    4. 使用ListView显示数据

    开发中不会按照如上方法显示数据到屏幕上的。因为,如果待显示数据太多,程序可能会崩溃。原因在于循环提取数据,如果有10000条数据,就需要创建10000个JavaBean对象和10000个TextView对象到内存中,太耗内存。解决方案之一是采用分页查询,只查询一部分数据,通过SQL语句的limit控制读取的条数。另一个Android方式的解决方案是动态创建TextView,也就是说屏幕只能显示10条数据,那么就创建10个JavaBean对象和10个TextView对象。如果用户下滑屏幕显示数据,一方面缓存(内存不足的时候会被摧毁,内存充足的时候缓存,避免下次再显示的时候重新创建,提升性能)屏幕上方已经消失的TextView,一方面创建屏幕下方要显示的TextView。这样,内容中只有10个JavaBean对象及10个TextView,避免内存溢出。Android中用ListView实现此功能。

    ListView专门用于显示列表,每一行称为一个条目(Item)

    MVC

    M:模型层 Javabean personList V:视图层 jsp ListView C:控制层 servlet Adapter(Adapter是适配器,用适配器控制要显示哪些内容)

    ListAdapter是一个接口,需要实现的方法太多。因此,一般通过继承BaseAdapter(抽象类)的方式创建ListAdapter对象。

    所有在布局文件中定义的组件(包括LinearLayout),都可以在ListView中显示。

    示例:修改上个示例中的代码如下:res\layout\activity_main.xml

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:orientation="vertical"> <ListView android:id="@+id/lv" android:layout_width="match_parent" android:layout_height="match_parent"> </ListView> </LinearLayout>

    src/cn.itcast.listView/MainActivity.java

    package cn.itcast.listView; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.TextView; import cn.itcast.listView.domain.Person; public class MainActivity extends Activity { List<Person> personList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); personList = new ArrayList<Person>(); //读取数据库 MyOpenHelper oh = new MyOpenHelper(this); SQLiteDatabase db = oh.getWritableDatabase(); Cursor cursor = db.query("person", null, null, null, null, null, null, null); while(cursor.moveToNext()){ int _id = cursor.getInt(0); String name = cursor.getString(1); String phone = cursor.getString(2); int salary = cursor.getInt(3); Person p = new Person(_id, name, phone, salary); personList.add(p); } ListView lv = (ListView)findViewById(R.id.lv); lv.setAdapter(new MyAdapter()); } class MyAdapter extends BaseAdapter{ //获取集合的元素数量 @Override public int getCount() { return personList.size(); } //系统调用此方法,获取一个View对象,该View对象会作为一个条目显示至ListView中 //position:getView返回的View对象会作为ListView的第几个条目显示,那么position的值就是多少 @Override public View getView(int position, View convertView, ViewGroup parent) { System.out.println("getView调用" + position); Person p = personList.get(position); TextView tv = new TextView(MainActivity.this); tv.setText(p.toString()); tv.setTextSize(16); return tv; } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } } }

    运行结果:

    如果将屏幕向下滑动,结果如下:

    如果将屏幕向上滑动,结果如下:

    4.1 把布局文件填充成View对象

    如果想要修改ListView的展示样式,那么就需要将指定的布局文件填充为View。

    示例:修改上个示例中的代码,如下:res\layout\item_listview.xml

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="name" android:textSize="22sp"/> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:layout_alignParentRight="true" android:layout_centerVertical="true" > <TextView android:id="@+id/tv_phone" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="phone" /> <TextView android:id="@+id/tv_salary" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="salary" /> </LinearLayout> </RelativeLayout>

    src/cn.itcast.listView/MainActivity.java

    package cn.itcast.listView; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.TextView; import cn.itcast.listView.domain.Person; public class MainActivity extends Activity { List<Person> personList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); personList = new ArrayList<Person>(); MyOpenHelper oh = new MyOpenHelper(this); SQLiteDatabase db = oh.getWritableDatabase(); Cursor cursor = db.query("person", null, null, null, null, null, null, null); while(cursor.moveToNext()){ int _id = cursor.getInt(0); String name = cursor.getString(1); String phone = cursor.getString(2); int salary = cursor.getInt(3); Person p = new Person(_id, name, phone, salary); personList.add(p); } ListView lv = (ListView)findViewById(R.id.lv); lv.setAdapter(new MyAdapter()); } class MyAdapter extends BaseAdapter{ @Override public int getCount() { return personList.size(); } @Override public View getView(int position, View convertView, ViewGroup parent) { System.out.println("getView调用" + position); Person p = personList.get(position); //把指定布局文件填充成View对象 //第二个参数指定把哪个布局文件转换成View对象,变成ListView条目显示在屏幕上 //第三个参数给布局文件指定父节点,不需要,设置null View v = View.inflate(MainActivity.this, R.layout.item_listview, null); //第二种方式,不常用 //LayoutInflater inflater = LayoutInflater.from(MainActivity.this); //View v = inflater.inflate(R.layout.item_listview, null); //第三种方式,不常用 //LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); //View v = inflater.inflate(R.layout.item_listview, null); //修改View对象中各个组件的内容 //findViewById(R.id.tv_name);通过id寻找tv_name默认是在activity_mian.xml布局文件中查找(由于本类中onCreate方法有一条语句setContentView(R.layout.activity_main);)。 //v.findViewById(R.id.tv_name);就会在被填充的item_listview布局文件中查找tv_name。 TextView tv_name = (TextView)v.findViewById(R.id.tv_name); tv_name.setText(p.getName()); TextView tv_phone = (TextView)v.findViewById(R.id.tv_phone); tv_phone.setText(p.getPhone()); TextView tv_salary = (TextView)v.findViewById(R.id.tv_salary); //参数一定要为字符串,如果是整数,那么就会把它作为一个id,去寻找相应的资源 tv_salary.setText(p.getSalary() + ""); return v; } //下面两个方法,系统不会调用,程序员可以调用 //返回条目,一般返回某个元素,例如:personList.get(position); @Override public Object getItem(int position) { return null; } //返回条目的id,一般返回元素的主键 @Override public long getItemId(int position) { return 0; } } }

    运行结果:

    4.2 ListView的优化

    上面的代码,只要用户一直上下滑屏,就会不断填充新的View对象(View v = View.inflate(MainActivity.this, R.layout.item_listview, null);),一方面耗费内存(内存可能会溢出)、CPU,另一方面导致加载时间变长。

    可以使用listView的缓存机制解决这个问题。任何一个条目如果被滑出屏幕,那么就缓存起来,下次再显示,不用重新创建,直接从缓存中提取就好。ListView对条目的缓存。在显示新的条目时,只要内存中有缓存,就会拿来用,不管是哪个条目的缓存。

    示例:修改上面的示例代码,如下:src/cn.itcast.listView/MainActivity.java

    package cn.itcast.listView; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.TextView; import cn.itcast.listView.domain.Person; public class MainActivity extends Activity { List<Person> personList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); personList = new ArrayList<Person>(); MyOpenHelper oh = new MyOpenHelper(this); SQLiteDatabase db = oh.getWritableDatabase(); Cursor cursor = db.query("person", null, null, null, null, null, null, null); while(cursor.moveToNext()){ int _id = cursor.getInt(0); String name = cursor.getString(1); String phone = cursor.getString(2); int salary = cursor.getInt(3); Person p = new Person(_id, name, phone, salary); personList.add(p); } ListView lv = (ListView)findViewById(R.id.lv); lv.setAdapter(new MyAdapter()); } class MyAdapter extends BaseAdapter{ @Override public int getCount() { return personList.size(); } //convertView就是被缓存起来的View对象,系统每次调用getView方法,都会把convertView传递进来 @Override public View getView(int position, View convertView, ViewGroup parent) { System.out.println("getView调用" + position); Person p = personList.get(position); View v = null; System.out.println(convertView); if(convertView == null){ //如果没有缓存,填充新的View对象 v = View.inflate(MainActivity.this, R.layout.item_listview, null); }else{ //如果有缓存,复用缓存 v = convertView; } //缓存并不是指给每个单独的条目缓存,系统是只要系统中有对象被缓存就会拿来使用。 //所以缓存的是View对象,并不是内容。也就是说系统中缓存的对象个数等于同屏能够显示的条目+1个。 TextView tv_name = (TextView)v.findViewById(R.id.tv_name); tv_name.setText(p.getName()); TextView tv_phone = (TextView)v.findViewById(R.id.tv_phone); tv_phone.setText(p.getPhone()); TextView tv_salary = (TextView)v.findViewById(R.id.tv_salary); tv_salary.setText(p.getSalary() + ""); return v; } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } } }

    运行结果:

    可以看到,缓存了11个View对象之后,后来不会再创建新的的View对象了,而都是通过缓存获取的。

    4.3 ArrayAdapter和SimpleAdapter

    ListAdapter,我们用的最多的是BaseAdapter,因为它的四个方法是我们自己实现的,自由度比较大,做复杂列表的时候比较容易。但列表不是那么复杂的时候,ListAdapter有一些封装度更高的Adapter提供给我们使用,用起来会更方便,其中的getCount、getView都不需要我们自己写代码。

    示例1:res\layout\activity_main.xml

    <?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" > <ImageView android:id="@+id/iv" android:layout_width="40dp" android:layout_height="40dp" android:src="@drawable/ic_launcher" /> <TextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="name" android:textSize="25sp" android:layout_gravity="center_vertical" /> </LinearLayout>

    src/cn.itcast.simplearray/MainActivity.java

    package cn.itcast.simplearray; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.app.Activity; import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.SimpleAdapter; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String[] objects = new String[]{"白吃","沙比","亚聪"}; ListView lv = (ListView)findViewById(R.id.lv); //使用ArrayAdapter只能处理文本数据,图片数据无法个性化单独设置,所以如果只是简单的文本显示,就是用ArrayAdapter。 //第二个参数为资源文件,填充ListView条目的布局文件 //第三个参数指定文本显示至哪一个textView中 lv.setAdapter(new ArrayAdapter<String>(this, R.layout.item_listview, R.id.tv, objects)); } }

    运行结果:

    示例2:src/cn.itcast.simplearray/MainActivity.java

    package cn.itcast.simplearray; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.app.Activity; import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.SimpleAdapter; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ListView lv = (ListView)findViewById(R.id.lv); //集合中的每一个元素都是一个条目要显示的数据,因为有多种数据类型, // 所以List不能使用单一泛型,先把所有类型的数据都封装至map,然后把map存入List。 List<Map<String,Object>> data = new ArrayList<Map<String,Object>>(); Map<String,Object> map1= new HashMap<String,Object>(); map1.put("image", R.drawable.ic_launcher); map1.put("name", "白吃"); data.add(map1); Map<String,Object> map2= new HashMap<String,Object>(); map2.put("image", R.drawable.photo1); map2.put("name", "沙比"); data.add(map2); Map<String,Object> map3= new HashMap<String,Object>(); map3.put("image", R.drawable.photo2); map3.put("name", "亚聪"); data.add(map3); lv.setAdapter(new SimpleAdapter(this, data, R.layout.item_listview, new String[]{"image","name"}, new int[]{R.id.iv,R.id.tv})); } }

    运行结果:

    案例1:SQLite数据库

    public class MyOpenHelper extends SQLiteOpenHelper { public MyOpenHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); // TODO Auto-generated constructor stub } //数据库创建时,此方法会调用 @Override public void onCreate(SQLiteDatabase db) { db.execSQL("create table person(_id integer primary key autoincrement, name char(10), salary char(20), phone integer(20))"); } //数据库升级时,此方法会调用 @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { System.out.println("数据库升级了"); } } public class TestCase extends AndroidTestCase { //此时测试框架还没有初始化完毕,没有虚拟上下文对象 // private MyOpenHelper oh = new MyOpenHelper(getContext(), "people.db", null, 1); private MyOpenHelper oh; private SQLiteDatabase db; public void test(){ //getContext():获取一个虚拟的上下文 MyOpenHelper oh = new MyOpenHelper(getContext(), "people.db", null, 1); //如果数据库不存在,先创建数据库,再获取可读可写的数据库对象,如果数据库存在,就直接打开 SQLiteDatabase db = oh.getWritableDatabase(); //如果存储空间满了,那么返回只读数据库对象 // SQLiteDatabase db = oh.getReadableDatabase(); } //测试框架初始化完毕之后,在测试方法执行之前,此方法调用 @Override protected void setUp() throws Exception { super.setUp(); oh = new MyOpenHelper(getContext(), "people.db", null, 1); db = oh.getWritableDatabase(); } //测试方法执行完毕之后,此方法调用 @Override protected void tearDown() throws Exception { // TODO Auto-generated method stub super.tearDown(); db.close(); } public void insert(){ // db.execSQL("insert into person (name, salary, phone)values(?, ?, ?)", new Object[]{"小志的老婆[1]", "13000", 138438}); // db.execSQL("insert into person (name, salary, phone)values(?, ?, ?)", new Object[]{"小志的儿子", 14000, "13888"}); db.execSQL("insert into person (name, salary, phone)values(?, ?, ?)", new Object[]{"小志", 14000, "13888"}); } public void delete(){ db.execSQL("delete from person where name = ?", new Object[]{"小志"}); } public void update(){ db.execSQL("update person set phone = ? where name = ?", new Object[]{186666, "小志的儿子"}); } public void select(){ Cursor cursor = db.rawQuery("select name, salary from person", null); while(cursor.moveToNext()){ //通过列索引获取列的值 String name = cursor.getString(cursor.getColumnIndex("name")); String salary = cursor.getString(1); System.out.println(name + ";" + salary); } } public void insertApi(){ //把要插入的数据全部封装至ContentValues对象 ContentValues values = new ContentValues(); values.put("name", "游天龙"); values.put("phone", "15999"); values.put("salary", 16000); db.insert("person", null, values); } public void deleteApi(){ int i = db.delete("person", "name = ? and _id = ?", new String[]{"小志的儿子", "3"}); System.out.println(i); } public void updateApi(){ ContentValues values = new ContentValues(); values.put("salary", 26000); int i = db.update("person", values, "name = ?", new String[]{"游天龙"}); System.out.println(i); } public void selectApi(){ Cursor cursor = db.query("person", null, null, null, null, null, null, null); while(cursor.moveToNext()){ String name = cursor.getString(cursor.getColumnIndex("name")); String phone = cursor.getString(cursor.getColumnIndex("phone")); String salary = cursor.getString(cursor.getColumnIndex("salary")); System.out.println(name + ";" + phone + ";" + salary); } } public void transaction(){ try{ //开启事务 db.beginTransaction(); ContentValues values = new ContentValues(); values.put("salary", 12000); db.update("person", values, "name = ?", new String[]{"小志"}); values.clear(); values.put("salary", 16000); db.update("person", values, "name = ?", new String[]{"小志的儿子"}); int i = 3/0; //设置 事务执行成功 db.setTransactionSuccessful(); } finally{ //关闭事务,同时提交,如果已经设置事务执行成功,那么sql语句就生效了,反之,sql语句回滚 db.endTransaction(); } } }

    案例2:把数据显示至屏幕

    public class MainActivity extends Activity { List<Person> personList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); personList = new ArrayList<Person>(); //把数据库的数据查询出来 MyOpenHelper oh = new MyOpenHelper(this); SQLiteDatabase db = oh.getWritableDatabase(); Cursor cursor = db.query("person", null, null, null, null, null, null, null); while(cursor.moveToNext()){ String _id = cursor.getString(0); String name = cursor.getString(1); String salary = cursor.getString(2); String phone = cursor.getString(3); Person p = new Person(_id, name, phone, salary); personList.add(p); } LinearLayout ll = (LinearLayout) findViewById(R.id.ll); //把数据显示至屏幕 for (Person p : personList) { //1.集合中每有一条元素,就new一个textView TextView tv = new TextView(this); //2.把人物的信息设置为文本框的内容 tv.setText(p.toString()); tv.setTextSize(18); //3.把textView设置为线性布局的子节点 ll.addView(tv); } } }

    案例3:使用ListView把数据显示至屏幕

    public class MainActivity extends Activity { List<Person> personList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); personList = new ArrayList<Person>(); //把数据库的数据查询出来 MyOpenHelper oh = new MyOpenHelper(this); SQLiteDatabase db = oh.getWritableDatabase(); Cursor cursor = db.query("person", null, null, null, null, null, null, null); while(cursor.moveToNext()){ String _id = cursor.getString(0); String name = cursor.getString(1); String salary = cursor.getString(2); String phone = cursor.getString(3); Person p = new Person(_id, name, phone, salary); personList.add(p); } ListView lv = (ListView) findViewById(R.id.lv); lv.setAdapter(new MyAdapter()); } class MyAdapter extends BaseAdapter{ //系统调用,用来获知集合中有多少条元素 @Override public int getCount() { return personList.size(); } //由系统调用,获取一个View对象,作为ListView的条目 //position:本次getView方法调用所返回的View对象,在listView中是处于第几个条目,那么position的值就是多少 @Override public View getView(int position, View convertView, ViewGroup parent) { Person p = personList.get(position); // TextView tv = new TextView(MainActivity.this); System.out.println("getView调用:" + position + ";" + convertView); // tv.setText(p.toString()); // tv.setTextSize(18); View v = null; //判断条目是否有缓存 if(convertView == null){ //把布局文件填充成一个View对象 v = View.inflate(MainActivity.this, R.layout.item_listview, null); } else{ v = convertView; } //获取布局填充器对象 // LayoutInflater inflater = LayoutInflater.from(MainActivity.this); // 使用布局填充器填充布局文件 // View v2 = inflater.inflate(R.layout.item_listview, null); // LayoutInflater inflater2 = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); // View v3 = inflater2.inflate(R.layout.item_listview, null); //通过资源id查找组件,注意调用的是View对象的findViewById TextView tv_name = (TextView) v.findViewById(R.id.tv_name); tv_name.setText(p.getName()); TextView tv_phone = (TextView) v.findViewById(R.id.tv_phone); tv_phone.setText(p.getPhone()); TextView tv_salary = (TextView) v.findViewById(R.id.tv_salary); tv_salary.setText(p.getSalary()); return v; } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } } }

    案例4:ArrayAdapter&SimpleAdapter

    public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String[] objects = new String[]{ "小志", "小志的儿子", "萌萌" }; ListView lv = (ListView) findViewById(R.id.lv); // lv.setAdapter(new ArrayAdapter<String>(this, R.layout.item_listview, R.id.tv_name, objects)); //集合中每个元素都包含ListView条目需要的所有数据,该案例中每个条目需要一个字符串和一个整型,所以使用一个map来封装这两种数据 List<Map<String, Object>> data = new ArrayList<Map<String,Object>>(); Map<String, Object> map1 = new HashMap<String, Object>(); map1.put("photo", R.drawable.photo1); map1.put("name", "小志的儿子"); data.add(map1); Map<String, Object> map2 = new HashMap<String, Object>(); map2.put("photo", R.drawable.photo2); map2.put("name", "小志"); data.add(map2); Map<String, Object> map3 = new HashMap<String, Object>(); map3.put("photo", R.drawable.photo3); map3.put("name", "赵帅哥"); data.add(map3); lv.setAdapter(new SimpleAdapter(this, data, R.layout.item_listview, new String[]{"photo", "name"}, new int[]{R.id.iv_photo, R.id.tv_name})); } }

    案例5:对话框

    public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void click1(View v){ AlertDialog.Builder builder = new Builder(this); //设置图标 builder.setIcon(android.R.drawable.alert_dark_frame); //设置标题 builder.setTitle("欲练此功必先自宫"); //设置文本 builder.setMessage("李志平,想清楚哦"); //设置确定按钮 builder.setPositiveButton("确定", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "感谢使用本软件,再见", 0).show(); } }); //设置取消按钮 builder.setNegativeButton("取消", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "若不自宫,一定不成功", 0).show(); } }); //使用创建器,生成一个对话框对象 AlertDialog ad = builder.create(); ad.show(); } public void click2(View v){ AlertDialog.Builder builder = new Builder(this); builder.setTitle("请选择性别"); final String[] items = new String[]{ "男", "女" }; builder.setSingleChoiceItems(items, -1, new OnClickListener() { //which:用户所选的条目的下标 //dialog:触发这个方法的对话框 @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "您选择的是:" + items[which], 0).show(); //关闭对话框 dialog.dismiss(); } }); builder.show(); } public void click3(View v){ AlertDialog.Builder builder = new Builder(this); builder.setTitle("请选择您觉得帅的人"); final String[] items = new String[]{ "侃哥", "赵帅哥", "赵老师", "赵师兄" }; final boolean[] checkedItems = new boolean[]{ true, true, false, false }; builder.setMultiChoiceItems(items, checkedItems, new OnMultiChoiceClickListener() { //which:用户点击的条目的下标 //isChecked:用户是选中该条目还是取消该条目 @Override public void onClick(DialogInterface dialog, int which, boolean isChecked) { checkedItems[which] = isChecked; } }); //设置一个确定按钮 builder.setPositiveButton("确定", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String text = ""; for(int i = 0; i < 4; i++){ text += checkedItems[i]? items[i] + "," : ""; } Toast.makeText(MainActivity.this, text, 0).show(); dialog.dismiss(); } }); builder.show(); } }
    转载请注明原文地址: https://ju.6miu.com/read-3635.html

    最新回复(0)