案例:学生信息管理系统
需求:在输入框中输入姓名,选择性别,点击保存按钮将数据存入数据库,并展示在下方的空白处。
操作步骤:
1. 界面布局:
<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"
android:orientation="vertical"
tools:context=".MainActivity" >
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入学生的姓名" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="请选择学生的性别:" />
<RadioGroup
android:id="@+id/rg_sex"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<RadioButton
android:id="@+id/rb_male"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:checked="true"
android:text="男" />
<RadioButton
android:id="@+id/rb_female"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="女" />
</RadioGroup>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="save"
android:text="保存" />
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<LinearLayout
android:id="@+id/ll_result"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
</LinearLayout>
</ScrollView>
</LinearLayout>
注意:最下方的空白处用了一个linearlayout容器来显示添加的学生信息,但是有可能数据太多显示不全,所以包裹了一个ScrollView在外面,可以让里面的信息进行滚动。ScrollView只能有一个子孩子,不能包裹多个。
2. 创建数据库,创建表结构
表名:student; 字段:姓名name,性别sex
public class StudentDBOpenHelper extends SQLiteOpenHelper {
public StudentDBOpenHelper(Context context) {
super(context, "info.db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table student (_id integer primary key autoincrement,name varchar(20), sex varchar(6))");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
3. 为了实现业务逻辑和界面的解耦隔离,一般数据库的增删改查都单独创建一个dao的业务逻辑类来实现:
由于操作数据库需要得到SqliteDatabase对象,所以在创建该类的时候,在构造函数中去实例化StudentDBOpenHelper类
/**
* 学生信息数据库的dao( data access objcet)
* 增删改查
*/
public class StudentDao {
private StudentDBOpenHelper helper;
/**
* 只有一个有参的构造方法,要求必须传入上下文
* @param context
*/
public StudentDao(Context context) {
helper = new StudentDBOpenHelper(context);
}
/**
* 添加一个学生
* @param name 姓名
* @param sex 性别,male female
*/
public void add(String name,String sex){
SQLiteDatabase db = helper.getWritableDatabase();
db.execSQL("insert into student (name,sex) values (?,?)", new Object[]{name,sex});
db.close();//释放资源
}
/**
* 删除一个学生
* @param name 姓名
*/
public void delete(String name){
SQLiteDatabase db = helper.getWritableDatabase();
db.execSQL("delete from student where name=?",new Object[]{name});
db.close();//释放资源
}
/**
* 修改一个学生的性别
* @param name 姓名
* @param newsex 新的性别
*/
public void update(String name,String newsex){
SQLiteDatabase db = helper.getWritableDatabase();
db.execSQL("update student set sex =? where name=?",new Object[]{newsex,name});
db.close();//释放资源
}
/**
* 查询学生的性别
* @param name 学生的姓名
* @return 学生性别 null代表学生不存在
*/
public String find(String name){
String sex = null;
SQLiteDatabase db = helper.getReadableDatabase();
//结果集 游标
Cursor cursor = db.rawQuery("select sex from student where name=?", new String[]{name});
boolean result = cursor.moveToNext();
if(result){
sex = cursor.getString(0);
}
cursor.close();//释放资源
db.close();
return sex;
}
在MainActivity中初始化控件以及数据库dao业务逻辑类
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_name = (EditText) findViewById(R.id.et_name);
rg_sex = (RadioGroup) findViewById(R.id.rg_sex);
// 找到界面下方的空白的线性布局
ll_result = (LinearLayout) findViewById(R.id.ll_result);
dao = new StudentDao(this);
refreshData();
}
public void save(View view) {
String name = et_name.getText().toString().trim();
if (TextUtils.isEmpty(name)) {
Toast.makeText(this, "请输入学生的姓名", 0).show();
return;
}
int id = rg_sex.getCheckedRadioButtonId();
String sex = "male";
if (id == R.id.rb_male) {
// 男
sex = "male";
} else {
// 女
sex = "female";
}
dao.add(name, sex);
Toast.makeText(this, "数据添加成功", 0).show();
refreshData();
}
通过以上4步即可成功的将我们输入的姓名选择的性别信息插入到数据库中,接下来就需要当插入数据成功的时候还得显示在界面的下方,为了方便数据操作,我们最好通过面向对象的思想去实现将一个学生的信息做一个业务bean来操作,所有学生就是一个List集合对象:
/**
* 学生的业务bean
*/
public class Student {
private String name;//姓名
private String sex;//性别
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Student [name=" + name + ", sex=" + sex + "]";
}
}
4. 在StudentDao中增加一个查询所有学生信息的方法,返回一个List<Student>集合对象,方便在界面操作显示
/**
* 获取全部的学生信息
* @return
*/
public List<Student> findAll(){
List<Student> students =new ArrayList<Student>();
SQLiteDatabase db = helper.getReadableDatabase();
Cursor cursor = db.rawQuery("select name, sex from student", null);
while(cursor.moveToNext()){
String name = cursor.getString(0);
String sex = cursor.getString(1);
Student student = new Student();
student.setName(name);
student.setSex(sex);
students.add(student);
}
cursor.close();
db.close();
return students;
}
}
5. 在MainActivity中定义一个单独的方法查询所有的学生数据并展示到界面上
/**
* 获取数据库的全部记录,刷新显示数据
*/
private void refreshData() {
List<Student> students = dao.findAll();
ll_result.removeAllViews();// 把原来的数据给清除
for (Student student : students) {
TextView tv = new TextView(this);
tv.setText(student.toString());
ll_result.addView(tv);
}
}
注意:在将textview添加到linearlayout容器中之前,一定要先将linearlayout中原来存在的textview控件清除,否则会造成重复的数据不断显示。
最后在点击保存按钮的save()方法中,当添加成功一条数据时,调用refreshData()方法刷新界面,以及在oncreate方法中调用refreshData()将数据库存在的学生数据显示出来。
2.ListView
通过数据库中的学生信息管理系统中的案例让我们熟悉了数据库的创建和使用,以及动态添加一个控件到linearlayout容器中;不过这种方式有一个极大的问题就是:不断的在创建new一个Textview控件添加到linearlayout,会造成如果有100个学生就得new100个textview,如果成千上万那内存开销会巨大导致内存溢出,所以android给我们引入了一个对数据进行列表展示的控件:ListView.
ListView的特点:
1、屏幕上可以展示几个条目,ListView 就初始化几个,节省内存。
2、通过使用convertView 对创建的视图对象进行复用,ListView 始终保持创建的对象个数为: 屏幕显示的条目的个数+ 1。
3、ListView 自带ScrollView 的功能,可以实现界面滚动。
4、ListView 控件的设计遵循MVC 设计模式:mode 数据模型(数据) :要被显示到ListView 上的数据集合 view 视图(展示数据) : ListView controller 控制层(把数据 展示到控件上) : 适配器Adapter
ListView的使用 布局设计
1. 布局里引入控件:
<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" >
<ListView
android:id="@+id/lv"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</RelativeLayout>
2. 查找控件,设置控制器
public class MainActivity extends Activity {
// mvc中的view,视图
private ListView lv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 查找视图
lv = (ListView) findViewById(R.id.lv);
// 设置控制器 controller
lv.setAdapter(new MyAdapter());
}
// BaseXXX SimpleXXX DefalutXXX
/**
* 控制器 用来控制listview如何显示
*/
private class MyAdapter extends BaseAdapter {
// 控制listview里面有多少个item
@Override
public int getCount() {
return 30;
}
// 返回某个位置显示的view对象。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView tv = new TextView(MainActivity.this);
tv.setText("我是文本:" + position);
tv.setTextSize(24);
return tv;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
}
最后显示的效果即是展示30个textview文本,并且自带滑动的功能:
ListView的优化
上诉的步骤只是最简单的显示出来了一个listview以及里面展示的数据,但是如果我们要显示的数据有一万条,那么在快速往上滚动的过程中就会不断的调用adapter 中的getView方法,不断的生成新的Textview控件,消耗内存资源,最后导致内存不足,造成oom内存溢出。
ListView复用的原理:
当我们滑动listview的时候,每当一个item看不见的时候,那么那个item就可以被复用起来了,也就是成为了convertView,那个时候convertView就不为null了
所谓的复用,其实本质是就是item的view对象没有真正的被垃圾回收器回收掉,而是重新将身上的数据给换掉了,看起来好像是出现了一个新的item而已
对ListView 进行优化的最简单有效的措施就是复用getView 方法中的convertView 对象。convertView来自缓存池,缓存池由ListView 维护,缓存池中的数据来自getView 方法的返回值,也就是说getView 方法的返回值用完后并没有“浪费”,而是被系统放到ListView 的缓存池里了。缓存池的大小=屏幕显示的条目数+1,当滑动屏幕时,被隐藏的项会作为缓存对象,作为getView 的参数传递进来。只需修改此缓存对象的数据,就可以直接使用,而不需要再重新new 一个新的对象,节省了内存,防止内存溢出。
优化后的代码
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView tv = null;
if (null == convertView) {
//如果缓存中没有数据则需要创建一个新的TextView
tv = new TextView(MainActivity.this);
}else{
//如果缓存中有数据则直接强转即可
tv = (TextView) convertView;
}
tv.setText("我是文本:" + position);
tv.setTextSize(24);
return tv;
}
打气筒inflate
(以下代码展示了三种方式将一个布局文件转化为Veiw对象)
@Override
public View getView(final int position, View convertView,ViewGroup parent) {//6个item
View view = null;
if (convertView == null) {
// 把一个布局xml文件转化成view对象
/**
* 第一种方式:
* 系统控件View自带有inflate方法
*/
view = View.inflate(MainActivity.this, R.layout.item, null);
/**
* 第二种方式实现
* 参考底层View.inflate()源码的实现:先通过LayoutInfalter 的静态方法from
* 获取LayoutInflater 对象,然后调用inflate 方法
*/
LayoutInflater layoutInflater = LayoutInflater.from(MainActivity.this);
view = layoutInflater.inflate(R.layout.item, null);
/**
* 第三种方式实现:
* 先通过上下文提供的getSystemService 方法获取LayoutInfater 对象
* 然后调用inflate 方法
*/
LayoutInflater layoutInflater2 = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = layoutInflater2.inflate(R.layout.item, null);
} else {
view = convertView;
}
// 在view里面查找孩子控件
TextView tv_name = (TextView) view.findViewById(R.id.tv_name);
ImageView iv_sex = (ImageView) view.findViewById(R.id.iv_sex);
Student student = students.get(position);
String sex = student.getSex();
if ("male".equals(sex)) {
iv_sex.setImageResource(R.drawable.nan);
} else {
iv_sex.setImageResource(R.drawable.nv);
}
tv_name.setText(student.getName());
view.findViewById(R.id.iv_delete).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Student student = students.get(position);
String name = student.getName();
// 从数据库删除数据.
dao.delete(name);
Toast.makeText(MainActivity.this, "数据被删除了", 0).show();
// 更新ui界面.
refreshData();
}
});
return view;
}
Item条目上有一个删除按钮,在上诉代码中给这个按钮注册了点击事件,点击的时候从数据库删除数据,这个时候需要更新UI界面;以及在点击保存按钮的时候添加一条数据库的时候,添加成功后需要刷新界面,那么ListView界面如何刷新呢?
通知数据适配器刷新数据
BaseAdapter适配器中有一个方法adapter.notifyDataSetChanged()该方法是通知适配器进行数据刷新,会自己调用adapter中的getCount()和getView()方法进行数据的刷新。
/**
* 获取数据库的全部记录,刷新显示数据
*/
private void refreshData() {
students = dao.findAll();
if (adapter == null) {
//第一次显示数据
adapter = new MyAdapter();
lv.setAdapter(adapter);
}else{
//通知数据适配器更新数据,而不是new出来新的数据适配器
adapter.notifyDataSetChanged();
}
}
ListView 的常见适配器
除了经常使用到的BaseAdapter 外,系统还提供了几个已经实现好的适配器,都是继承至BaseAdapter
ArrayAdapter:
lv = (ListView) findViewById(R.id.lv);
String[] objects = new String[]{"Animation","App","content","Media","NFC","OS"};
/**
* 第一个参数是:上下文
* 第二个参数是:布局文件的id,这里使用Android 系统提供的简单布局
* 第三个参数是:要显示的数据,数组或者List 集合都行
*/
lv.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, objects));
SimpleAdapter
SimpleAdapter 可以实现比ArrayAdapter 复杂一点的布局。使用SimpleAdapter 的数据一般都是HashMap构成的List,List 的每一个对象对应ListView 的每一行。HashMap 的每个键值数据映射到布局文件中对应id 的组件上。因为系统没有对应的布局文件可用,我们可以自己定义一个布局文件。
代码实现如下:
lv = (ListView) findViewById(R.id.lv);
//准备数据
List<Map<String, Object>> data = new ArrayList<Map<String,Object>>();
Map<String, Object> map1 = new HashMap<String, Object>();
map1.put("icon", R.drawable.ic_menu_preferences);
map1.put("name", "功能设置");
data.add(map1);
Map<String, Object> map2 = new HashMap<String, Object>();
map2.put("icon", R.drawable.ic_menu_recent_history);
map2.put("name", "时钟设置");
data.add(map2);
Map<String, Object> map3 = new HashMap<String, Object>();
map3.put("icon", R.drawable.ic_menu_refresh);
map3.put("name", "同步设置");
data.add(map3);
Map<String, Object> map4 = new HashMap<String, Object>();
map4.put("icon", R.drawable.ic_menu_report_image);
map4.put("name", "图片设置");
data.add(map4);
/**
* 第一个参数:上下文
* 第二个参数:显示的数据
* 第三个参数:布局文件的资源id
* 第四个参数:Map 集合中key 的数组
* 第五个参数:item 布局中ImageView控件的id 和 TextView 的控件的id
*/
lv.setAdapter(new SimpleAdapter(this, data, R.layout.item, new String[]{"icon","name"}, new int[]{R.id.iv,R.id.tv}));