【Android UI】ListView的使用和简单优化

ListView是每个app中都要使用的,所以今天我来总结下ListView的使用和一些简单的优化。

先看下运行效果:

一、创建数据库

为了模拟数据,这里将数据保存数据库中,顺便复习一下SQLite的知识,将数据保存到数据库的好处就是很容易模拟网络请求的延迟。

1.创建数据库打开帮助类BlackNumberDBOpenHelper,它继承自SQLiteOpenHelper

package com.yzx.listviewdemo.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class BlackNumberDBOpenHelper extends SQLiteOpenHelper {

    public BlackNumberDBOpenHelper(Context context) {
        super(context, "blacknumber.db", null, 1);
    }

    //数据库第一次创建的时候调用的方法。 适合做数据库表结构的初始化
    @Override
    public void onCreate(SQLiteDatabase db) {
        //创建数据库的表结构  主键_id 自增长  number黑名单号码  mode拦截模式  1电话拦截 2短信拦截 3全部拦截。
        db.execSQL("create table blacknumber (_id integer primary key autoincrement , number varchar(20), mode varchar(2))");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

2.创建类BlackNumberDao 在里面实现增删改查

package com.yzx.listviewdemo.db.dao;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import java.util.ArrayList;
import java.util.List;

import com.yzx.listviewdemo.db.BlackNumberDBOpenHelper;
import com.yzx.listviewdemo.domain.BlackNumberInfo;

/**
 * 数据库增删改查的工具类
 *
 */
public class BlackNumberDao {
    private BlackNumberDBOpenHelper helper;

    /**
     * 构造方法中完成数据库打开帮助类的初始化
     * @param context
     */
    public BlackNumberDao(Context context) {
        helper = new BlackNumberDBOpenHelper(context);
    }
    /**
     * 添加一条黑名单号码
     * @param number 黑名单号码
     * @param mode 拦截模式  1电话拦截 2短信拦截 3全部拦截。
     */
    public void add(String number,String mode){
        SQLiteDatabase db = helper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("number", number);
        values.put("mode", mode);
        db.insert("blacknumber", null, values);
        db.close();
    }
    /**
     * 删除一条黑名单号码
     * @param number 黑名单号码
     */
    public void delete(String number){
        SQLiteDatabase db = helper.getWritableDatabase();
        db.delete("blacknumber", "number=?", new String[]{number});
        db.close();
    }
    /**
     * 更改一条黑名单号码的拦截模式
     * @param number 要修改的黑名单号码
     * @param newmode 新的拦截模式
     */
    public void update(String number, String newmode){
        SQLiteDatabase db = helper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("mode", newmode);
        db.update("blacknumber", values, "number=?", new String[]{number});
        db.close();
    }

    /**
     * 获取全部的黑名单号码信息。
     * @return
     */
    public List<BlackNumberInfo> findAll(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.query("blacknumber", new String[]{"number","mode"}, null, null, null, null, null);
        List<BlackNumberInfo>  infos = new ArrayList<BlackNumberInfo>();
        while(cursor.moveToNext()){
            BlackNumberInfo info = new BlackNumberInfo();
            String number = cursor.getString(0);
            String mode = cursor.getString(1);
            info.setMode(mode);
            info.setNumber(number);
            infos.add(info);
        }
        cursor.close();
        db.close();
        return infos;
    }
    /**
     * 分页获取部分的黑名单号码信息。
     * @param startIndex 查询的开始位置
     * @return
     */
    public List<BlackNumberInfo> findPart(int startIndex){
        try {
            Thread.sleep(600);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select number,mode  from blacknumber order by _id desc limit 20 offset ?", new String[]{String.valueOf(startIndex)});
        List<BlackNumberInfo>  infos = new ArrayList<BlackNumberInfo>();
        while(cursor.moveToNext()){
            BlackNumberInfo info = new BlackNumberInfo();
            String number = cursor.getString(0);
            String mode = cursor.getString(1);
            info.setMode(mode);
            info.setNumber(number);
            infos.add(info);
        }
        cursor.close();
        db.close();
        return infos;
    }
    /**
     * 获取数据库一共有多少条记录
     * @return  int 总条目个数
     */
    public int getTotalCount(){
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select count(*) from blacknumber ",null);
        cursor.moveToNext();
        int count = cursor.getInt(0);
        cursor.close();
        db.close();
        return count;
    }
    /**
     * 查询黑名单号码是否存在
     * @param number
     * @return
     */
    public boolean find(String number){
        boolean result = false;
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.query("blacknumber", null, "number=?", new String[]{number}, null, null, null);
        if(cursor.moveToNext()){
            result = true;
        }
        cursor.close();
        db.close();
        return result;
    }
    /**
     * 查询黑名单号码的拦截模式
     * @param number
     * @return  null代表不存在  1电话 2短信 3全部
     */
    public String findMode(String number){
        String mode = null;
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.query("blacknumber", new String[]{"mode"}, "number=?", new String[]{number}, null, null, null);
        if(cursor.moveToNext()){
            mode = cursor.getString(0);
        }
        cursor.close();
        db.close();
        return mode;
    }
}

3.创建BlackNumberInfo实体

package com.yzx.listviewdemo.domain;

/**
 * Created by yzx on 2016/7/5.
 */
public class BlackNumberInfo {

    private String number;
    private String mode;
    public String getNumber() {
        return number;
    }
    public void setNumber(String number) {
        this.number = number;
    }
    public String getMode() {
        return mode;
    }
    public void setMode(String mode) {
        this.mode = mode;
    }
    @Override
    public String toString() {
        return "BlackNumberInfo [number=" + number + ", mode=" + mode + "]";
    }

}

里面的Thread.sleep(600)和Thread.sleep(3000)是用来模拟请求网络时的延迟。

二、在使用ListView展示列表

1.便于展示先添加100条数据

Random random = new Random();
//13512340001
for(int i = 0 ;i< 100;i++ ){
 dao.add("1351234000"+i,String.valueOf(random.nextInt(3)+1));
}

2.列表的展示

在onCreate中初始化数据和布局文件,只有自定义继承自BaseAdapter的Adapter,在其getView加在每个item的布局文件。这样就能展示数据了,但是存在很多的问题,下面我们就来简单的优化。

三、ListView的简单优化

1.使用历史缓存的显示数据

View view;
if(convertView != null){
    view = convertView;
    Log.i(TAG, "使用历史缓存的显示数据"+position);
}else{
    Log.i(TAG, "创建新的View的显示数据"+position);
    view=View.inflate(CallSmsSafeActivity.this,
                 R.layout.list_callsmssafe_item, null);
}

2.使用ViewHolder优化

因为寻找孩子的过程是一个比较耗时,消耗资源的操作,进一步的优化。没有必要每一次都去查看每个孩子的特征,根据id得到孩子的引用。只需要在孩子出生的时候,找到特征,把特征给存起来。这里可以使用ViewHolder作为容器来保存孩子的引用。

private class CallSmsSafeAdapter extends BaseAdapter {
    private static final String TAG = "CallSmsSafeAdapter";

    @Override
    public int getCount() {
        return infos.size();
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        // 为了减少view对象创建的个数 使用历史缓存的view对象 convertview
        View view;
        ViewHolder holder;
        if (convertView != null) {
            view = convertView;
            Log.i(TAG, "使用历史缓存的view对象" + position);
            holder = (ViewHolder) view.getTag();
        } else {
            Log.i(TAG, "创建新的view对象" + position);
            view = View.inflate(getApplicationContext(),
                    R.layout.list_callsmssafe_item, null);
            // 寻找孩子的过程 是一个比较耗时 消耗资源的操作 进一步的优化。
            // 没有必要每一次都去查看每个孩子的特征 根据id得到孩子的引用。
            // 只需要在孩子出生的时候 , 找到特征 ,把特征给存起来
            holder = new ViewHolder();// 买了一个记事本 记录孩子的信息。
            holder.tv_number = (TextView) view.findViewById(R.id.tv_number);
            holder.tv_mode = (TextView) view.findViewById(R.id.tv_mode);
            holder.iv_delete = (ImageView) view.findViewById(R.id.iv_delete);
                view.setTag(holder); // 把孩子的引用 记事本 放在口袋里
        }

        BlackNumberInfo info = infos.get(position);
        String mode = info.getMode();
        if ("1".equals(mode)) {
            holder.tv_mode.setText("电话拦截");
        } else if ("2".equals(mode)) {
            holder.tv_mode.setText("短信拦截");
        } else if ("3".equals(mode)) {
            holder.tv_mode.setText("电话+短信拦截");
        }
        holder.tv_number.setText(info.getNumber());

        holder.iv_delete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                BlackNumberInfo info = infos.get(position);
                String number = info.getNumber();
                dao.delete(number);//删除数据库里面的记录。
                infos.remove(info);//删除当前界面对应的集合的数据。
                adapter.notifyDataSetChanged();//通知界面刷新。
                totalCount--;
            }
        });

        return view;
    }

    @Override
    public Object getItem(int position) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return 0;
    }
}

class ViewHolder {
    TextView tv_number;
    TextView tv_mode;
    ImageView iv_delete;
}

四、ListView的UI效果优化

因为我们我们在app中使用ListView展示数据一般都是请求网络获取数据的,所以曾在延迟,我们应该给用户显示一个ProgressBar来增加用户的等待欲。当数据很多时,我们不仅要使用ProgressBar,还要分批加载数据。

1.创建一个线程去读取数据

new Thread() {
  public void run() {
    list = dao.findAll();//查询数据库得到数据
    handler.sendEmptyMessage(0);//发消息给主线程更新数据
  };
}.start();

//更新数据
private Handler handler = new Handler() {
    public void handleMessage(android.os.Message msg) {
        lv_call_sms_safe.setAdapter(new CallSmsSafeAdapter());
    };
};

2.没数据的时候加一个提醒效果

在布局文件中添加相应控件:

<?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="match_parent"
    android:orientation="vertical" >

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <ListView
            android:id="@+id/lv_call_sms_safe"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
        </ListView>

        <LinearLayout
            android:id="@+id/ll_loading"
            android:visibility="invisible"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:orientation="vertical" >

            <ProgressBar
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="玩命加载中。。。" />
        </LinearLayout>
    </FrameLayout>

</LinearLayout>
//代码配合处理
ll_loading.setVisibility(View.VISIBLE);//没数据的时候显示
ll_loading.setVisibility(View.INVISIBLE);//数据加载好了隐藏

3.分批加载的好处:不用等待太久、节约流量、慢慢引导用户看感兴趣内容

/**
 * 分页获取部分的黑名单号码信息。
 * @param startIndex 查询的开始位置
 * @return
 */
public List<BlackNumberInfo> findPart(int startIndex){
    try {
        Thread.sleep(600);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    SQLiteDatabase db = helper.getReadableDatabase();
    Cursor cursor = db.rawQuery("select number,mode  from blacknumber order by _id desc limit 20 offset ?", new String[]{String.valueOf(startIndex)});
    List<BlackNumberInfo>  infos = new ArrayList<BlackNumberInfo>();
    while(cursor.moveToNext()){
        BlackNumberInfo info = new BlackNumberInfo();
        String number = cursor.getString(0);
        String mode = cursor.getString(1);
        info.setMode(mode);
        info.setNumber(number);
        infos.add(info);
    }
    cursor.close();
    db.close();
    return infos;
}

4.监听拖动到末尾

lv_call_sms_safe.setOnScrollListener(new AbsListView.OnScrollListener() {
    // 滚动状态变化了。
    // 静止-->滚动
    // 滚动-->静止
    // 手指触摸滚动-->惯性滑动
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        switch (scrollState) {
            case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:// 静止状态
                // 判断界面是否已经滑动到了最后面。
                // 获取最后一个可见条目的位置
                int position = lv_call_sms_safe.getLastVisiblePosition(); // 19
                int total = infos.size(); // 20
                if (isloading) {
                    Toast.makeText(getApplicationContext(),
                            "正在加载数据,不要重复刷新。", Toast.LENGTH_SHORT).show();
                    return;
                }
                if (position >= (total - 5)) {

                    // 指定新的获取数据的开始位置
                    startIndex += 20;
                    if (startIndex >= totalCount) {
                        Toast.makeText(getApplicationContext(),
                                "没有更多数据了,别再拖了", Toast.LENGTH_SHORT).show();
                        return;
                    }
                    Toast.makeText(getApplicationContext(),
                            "拖动到最下面了。加载更多数据", Toast.LENGTH_SHORT).show();
                    fillData();
                }
                break;
            case AbsListView.OnScrollListener.SCROLL_STATE_FLING:// 惯性滑动

                break;
            case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:// 触摸滚动

                break;
        }

    }

    // 滚动的时候执行的方法
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
                         int visibleItemCount, int totalItemCount) {

    }
});
private void fillData() {
    ll_loading.setVisibility(View.VISIBLE);
    isloading = true;
    new Thread() {
        public void run() {
            if (infos == null) {
                infos = dao.findPart(startIndex);
            } else {// 原来集合里面已经有数据了。
                infos.addAll(dao.findPart(startIndex));
            }
            // 发送一个消息 更新界面。
            handler.sendEmptyMessage(0);
        };
    }.start();
}

5.让数据继续停留在当前位置

两种实现方式

第一种:lv_call_sms_safe.setSelection(startIndex);//不推荐

第二种:重复利用适配器,数据变化通知一下就行了

private CallSmsSafeAdapter adapter;

Handler里面修改成:

if(adapter == null){
    adapter = new CallSmsSafeAdapter();
    lv_call_sms_safe.setAdapter(adapter);
}else{
    //通知数据适配器更新一下界面
    adapter.notifyDataSetChanged();
}

6.防止重复加载

定义成员变量

private boolean isLoading  = false;

在加载的过程中

if(isLoading){
    return;
} 

isLoading = true;

加载好后 handler里面处理

isLoading = false;  

7.处理拖动到所有数据的最后一条时的处理

得到数据库一共有多少条数据

 /**
 * 得到数据的总数
 * @return
 */
public int  getTotalCount(){
    int count = 0;
    SQLiteDatabase database = helper.getWritableDatabase();
    Cursor cursor = database.rawQuery("select count(*) from blacknumber", null);
    if(cursor.moveToNext()){
         count = cursor.getInt(0);
    }
    cursor.close();
    database.close();
    return count;
}

代码处理

if(startIndex >= total){
    Toast.makeText(getApplicationContext(), "已经最后一条了", 0).show();
    return;
    }

到此就已经实现了ListView的使用和简单优化。

时间: 2024-10-27 08:26:44

【Android UI】ListView的使用和简单优化的相关文章

Android之ListView异步加载网络图片(优化缓存机制)【转】

网上关于这个方面的文章也不少,基本的思路是线程+缓存来解决.下面提出一些优化: 1.采用线程池 2.内存缓存+文件缓存 3.内存缓存中网上很多是采用SoftReference来防止堆溢出,这儿严格限制只能使用最大JVM内存的1/4 4.对下载的图片进行按比例缩放,以减少内存的消耗 具体的代码里面说明.先放上内存缓存类的代码MemoryCache.java: public class MemoryCache { private static final String TAG = "MemoryCa

Android中ListView嵌套GridView的简单消息流UI(解决宽高问题)

最近搞一个项目,需要用到类似于新浪微博的消息流,即每一项有文字.有九宫格图片,因此这就涉及到ListView或者ScrollView嵌套GridView的问题.其中GridView的高度问题在网上都很容易找到答案,即覆写onMeasure方法,然后设置高度的MeasureSpec.但是宽度问题确实没有什么资料,这里所说的宽度问题是比如GridView的列数为3,那么即使只有一张图片,gridview的宽度也是match_parent的,导致用户点击在图片范围外但是在gridview范围内时Lis

Android之ListView异步加载网络图片(优化缓存机制)

网上关于这个方面的文章也不少,基本的思路是线程+缓存来解决.下面提出一些优化: 1.采用线程池 2.内存缓存+文件缓存 3.内存缓存中网上很多是采用SoftReference来防止堆溢出,这儿严格限制只能使用最大JVM内存的1/4 4.对下载的图片进行按比例缩放,以减少内存的消耗 具体的代码里面说明.先放上内存缓存类的代码MemoryCache.java: [java] view plain copy public class MemoryCache { private static final

Android UI:ListView -- SimpleAdapter

SimpleAdapter是扩展性最好的适配器,可以定义各种你想要的布局,而且使用很方便. layout : 1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent&quo

Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)

Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载) POSTED ON 2014年6月27日 BY 天边的星星 本文内容: 1.横向ListView的所有实现思路; 2.其中一个最通用的思路HorizontalListView,并基于横向ListView开发一个简单的相册: 3.实现的横向ListView在点击.浏览时item背景会变色,并解决了listview里setSelected造成item的选择状态混乱的问题.

Android UI性能优化实战 识别绘制中的性能问题

出自:[张鸿洋的博客]来源:http://blog.csdn.net/lmj623565791/article/details/45556391 1.概述 2015年初google发布了Android性能优化典范,发了16个小视频供大家欣赏,当时我也将其下载,通过微信公众号给大家推送了百度云的下载地址(地址在文末,ps:欢迎大家订阅公众号),那么近期google又在udacity上开了系列类的相关课程.有了上述的参考,那么本性能优化实战教程就有了坚实的基础,本系列将结合实例为大家展示如何去识别.

Android UI设计之&lt;十&gt;自定义ListView,实现QQ空间阻尼下拉刷新和渐变菜单栏效果

转载请注明出处:http://blog.csdn.net/llew2011/article/details/51559694 好久没有写有关UI的博客了,刚刚翻了一下之前的博客,最近一篇有关UI的博客是在2014年写的:Android UI设计之<七>自定义Dialog,实现各种风格效果的对话框,在那篇博客写完后由于公司封闭开发封网以及其它原因致使博客中断至今,中断这么久很是惭愧,后续我会尽量把该写的都补充出来.近来项目有个需求,要做个和QQ空间类似的菜单栏透明度渐变和下拉刷新带有阻尼回弹的效

android UI进阶之实现listview中checkbox的多选与记录

今天继续和大家分享涉及到listview的内容.在很多时候,我们会用到listview和checkbox配合来提供给用户一些选择操作.比如在一个清单页面,我们需要记录用户勾选了哪些条目.这个的实现并不太难,但是有很多朋友来问我如何实现,他们有遇到各种各样的问题,这里就一并写出来和大家一起分享. ListView的操作就一定会涉及到item和Adapter,我们还是先来实现这部分内容. 首先,写个item的xml布局,里面放置一个TextView和一个CheckBox.要注意的时候,这里我设置了C

android UI进阶之实现listview的分页加载

 分享了下拉刷新,这是一个用户体验非常好的操作方式.新浪微薄就是使用这种方式的典型. 还有个问题,当用户从网络上读取微薄的时候,如果一下子全部加载用户未读的微薄这将耗费比较长的时间,造成不好的用户体验,同时一屏的内容也不足以显示如此多的内容.这时候,我们就需要用到另一个功能,那就是listview的分页了.通过分页分次加载数据,用户看多少就去加载多少. 通常这也分为两种方式,一种是设置一个按钮,用户点击即加载.另一种是当用户滑动到底部时自动加载.今天我就和大家分享一下这个功能的实现. 首先,