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;}@Overridepublic 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;@Overrideprotected 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);}//数据库创建时,此方法调用@Overridepublic void onCreate(SQLiteDatabase db) {}//数据库升级时,此方法调用@Overridepublic 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);}//数据库创建完之后,才会调用这个函数@Overridepublic void onCreate(SQLiteDatabase db) {System.out.println("数据库创建");}@Overridepublic 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);}@Overridepublic 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))");}@Overridepublic 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;//测试框架初始化完毕后,测试方法执行前,此方法调用@Overrideprotected 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[]{"王亚聪"});}//测试方法执行完毕后,执行。@Overrideprotected 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;@Overrideprotected 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在第一位,索引即为0String name = cursor.getString(0);//可以通过字段名称获取列索引,再通过列索引获取内容String salary = cursor.getString(cursor.getColumnIndex("salary"));System.out.println(name + ":" + salary);}}@Overrideprotected 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;@Overrideprotected 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);}@Overrideprotected 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;@Overrideprotected 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);}}@Overrideprotected 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;@Overrideprotected 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();}}@Overrideprotected 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);}@Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL("create table person(_id integer primary key autoincrement,name char(10),phone char(20),salary integer(10))");}@Overridepublic 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;@Overrideprotected 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);}}@Overrideprotected 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;}@Overridepublic 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;@Overrideprotected 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;@Overrideprotected 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{//获取集合的元素数量@Overridepublic int getCount() {return personList.size();}//系统调用此方法,获取一个View对象,该View对象会作为一个条目显示至ListView中//position:getView返回的View对象会作为ListView的第几个条目显示,那么position的值就是多少@Overridepublic 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;}@Overridepublic Object getItem(int position) {return null;}@Overridepublic 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;@Overrideprotected 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{@Overridepublic int getCount() {return personList.size();}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {System.out.println("getView调用" + position);Person p = personList.get(position);//把指定布局文件填充成View对象//第二个参数指定把哪个布局文件转换成View对象,变成ListView条目显示在屏幕上//第三个参数给布局文件指定父节点,不需要,设置nullView 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);@Overridepublic Object getItem(int position) {return null;}//返回条目的id,一般返回元素的主键@Overridepublic 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;@Overrideprotected 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{@Overridepublic int getCount() {return personList.size();}//convertView就是被缓存起来的View对象,系统每次调用getView方法,都会把convertView传递进来@Overridepublic 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;}@Overridepublic Object getItem(int position) {return null;}@Overridepublic 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 {@Overrideprotected 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 {@Overrideprotected 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}//数据库创建时,此方法会调用@Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL("create table person(_id integer primary key autoincrement, name char(10), salary char(20), phone integer(20))");}//数据库升级时,此方法会调用@Overridepublic 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();}//测试框架初始化完毕之后,在测试方法执行之前,此方法调用@Overrideprotected void setUp() throws Exception {super.setUp();oh = new MyOpenHelper(getContext(), "people.db", null, 1);db = oh.getWritableDatabase();}//测试方法执行完毕之后,此方法调用@Overrideprotected void tearDown() throws Exception {// TODO Auto-generated method stubsuper.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;@Overrideprotected 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一个textViewTextView 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;@Overrideprotected 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{//系统调用,用来获知集合中有多少条元素@Overridepublic int getCount() {return personList.size();}//由系统调用,获取一个View对象,作为ListView的条目//position:本次getView方法调用所返回的View对象,在listView中是处于第几个条目,那么position的值就是多少@Overridepublic 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对象的findViewByIdTextView 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;}@Overridepublic Object getItem(int position) {return null;}@Overridepublic long getItemId(int position) {return 0;}}
}

案例4:ArrayAdapter&SimpleAdapter

public class MainActivity extends Activity {@Overrideprotected 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 {@Overrideprotected 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() {@Overridepublic void onClick(DialogInterface dialog, int which) {Toast.makeText(MainActivity.this, "感谢使用本软件,再见", 0).show();}});//设置取消按钮builder.setNegativeButton("取消", new OnClickListener() {@Overridepublic 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:触发这个方法的对话框@Overridepublic 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:用户是选中该条目还是取消该条目@Overridepublic void onClick(DialogInterface dialog, int which, boolean isChecked) {checkedItems[which] = isChecked;}});//设置一个确定按钮builder.setPositiveButton("确定", new OnClickListener() {@Overridepublic 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();}
}

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

  1. Android应用开发-数据存储和界面展现

    测试 黑盒测试 测试逻辑业务 白盒测试 测试逻辑方法 根据测试粒度 方法测试:function test 单元测试:unit test 集成测试:integration test 系统测试:syste ...

  2. Android-应用开发-数据存储和界面展现(三)

    测试 按岗位划分 黑盒测试:测试逻辑业务 白盒测试:测试逻辑方法 按测试粒度分 方法测试:function test 单元测试:unit test 集成测试:integration test 系统测试 ...

  3. android界面数据存储,Android应用开发基础之数据存储和界面展现(二)

    Android应用开发基础之数据存储和界面展现(二) 常见布局 相对布局 RelativeLayout 组件默认左对齐.顶部对齐 设置组件在指定组件的右边 android:layout_toRight ...

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

    1. 相对布局RelativeLayout 特点:相对布局所有组件可以叠加在一起:各个组件的布局是独立的,互不影响:所有组件的默认位置都是在左上角(顶部.左部对齐) 属性 功能描述 android:l ...

  5. Android(三)----数据存储和界面展现02

    Pull解析xml文件(掌握) 先自己写一个xml文件,存一些天气信息 拿到xml文件 InputStream is = getClassLoader().getResourceAsStream(&q ...

  6. 黑马--数据存储与界面展现2

    测试 黑盒测试 测试逻辑业务 白盒测试 测试逻辑方法 根据测试粒度 方法测试:function test 单元测试:unit test 集成测试:integration test 系统测试:syste ...

  7. 数据存储和界面展现之二

    测试 黑盒测试 测试逻辑业务 白盒测试 测试逻辑方法 根据测试粒度 方法测试:function test 单元测试:unit test 集成测试:integration test 系统测试:syste ...

  8. 数据存储和界面展现总结

    测试 黑盒测试 测试逻辑业务 白盒测试 测试逻辑方法 根据测试粒度 方法测试:function test 单元测试:unit test 集成测试:integration test 系统测试:syste ...

  9. [免费专栏] Android安全之数据存储与数据安全「详解」

    也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 Android安全付费专栏长期更新,本篇最新内容请前往: [ ...

最新文章

  1. Axure 全局辅助线(转)
  2. Android SystemProperties系统属性分析
  3. “躲避球”体育游戏《Sparc》登陆Rift和Vive,可以和PS用户一起玩了【附游戏视频】
  4. 算法入门经典第六章 例题6-8 树
  5. 解决由于操作顺序问题导致 hbase 一直处于启动无法关闭的问题
  6. 四因素三水平正交试验表_测试用例设计方法(二)——正交实验法
  7. unityui等比例缩放_Unity 4.6-如何针对每种分辨率将GUI元素缩放到合适的大小
  8. QPS、TPS、并发用户数、吞吐量的关系
  9. C/S通信模型与B/S通信模型介绍
  10. java rmi 安全管理器_Java:没有安全管理器:RMI类加载器被禁用
  11. 主动申请linux内存 脚本,Shell 脚本来自动监控 Linux 系统的内存
  12. HDU 5533 Dancing Stars on Me( 有趣的计算几何 )
  13. java8新日期时间类使用
  14. c语言创建空顺序表的程序,用C语言编写一个完整的程序,实现顺序表的建立、插入、删除、输出等基本运算。...
  15. python写个验证码
  16. python中define的用法_C语言中#define用法总结
  17. 25行代码实现定时自动发送微信消息!
  18. Proteus十字路口交通灯
  19. 网站seo优化一定要注意这几件事
  20. 【mosek.fusion】Primal SVM

热门文章

  1. IE/Firefox中css兼容常见问题
  2. 企业组网安全如何保障?SDWAN提供怎样的安全服务?
  3. 大数据与智能算法(三-集疏运应用)-SMU在线学习笔记
  4. 【指标导出】指标原始数据导出流程
  5. 有待试用,查看是谁锁住了我的数据库
  6. HDU 4556 Stern-Brocot Tree
  7. Ubuntu 14.04 python3.6 安装
  8. fps 每秒刷新的频率
  9. 在 Eclipse 中使用 Maven 构建 Web 项目
  10. 浙大团队研发铜基沸石纱布口罩,1分钟杀灭新冠病毒逾99%