学生管理系统案列【ListView和适配器】

案例:学生信息管理系统

需求:在输入框中输入姓名,选择性别,点击保存按钮将数据存入数据库,并展示在下方的空白处。

操作步骤

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个学生就得new100textview如果成千上万那内存开销会巨大导致内存溢出所以android给我们引入了一个对数据进行列表展示的控件ListView.

ListView的特点

1、屏幕上可以展示几个条目,ListView 就初始化几个,节省内存。

2、通过使用convertView 对创建的视图对象进行复用,ListView 始终保持创建的对象个数为: 屏幕显示的条目的个数+ 1

3ListView 自带ScrollView 的功能,可以实现界面滚动。

4ListView 控件的设计遵循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;

}

}

最后显示的效果即是展示30textview文本,并且自带滑动的功能:

 ListView的优化

上诉的步骤只是最简单的显示出来了一个listview以及里面展示的数据但是如果我们要显示的数据有一万条那么在快速往上滚动的过程中就会不断的调用adapter 中的getView方法不断的生成新的Textview控件,消耗内存资源,最后导致内存不足,造成oom内存溢出。

ListView复用的原理

当我们滑动listview的时候,每当一个item看不见的时候,那么那个item就可以被复用起来了,也就是成为了convertView,那个时候convertView就不为null

所谓的复用,其实本质是就是itemview对象没有真正的被垃圾回收器回收掉,而是重新将身上的数据给换掉了,看起来好像是出现了一个新的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构成的ListList 的每一个对象对应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}));




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值