Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 【Android UI】ListView的使用和簡單優化

【Android UI】ListView的使用和簡單優化

編輯:關於Android編程

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 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  infos = new ArrayList();
        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 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  infos = new ArrayList();
        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.沒數據的時候加一個提醒效果
在布局文件中添加相應控件:




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

        
        

        

            

            
        
    </framelayout>

//代碼配合處理
ll_loading.setVisibility(View.VISIBLE);//沒數據的時候顯示
ll_loading.setVisibility(View.INVISIBLE);//數據加載好了隱藏

3.分批加載的好處:不用等待太久、節約流量、慢慢引導用戶看感興趣內容

/**
 * 分頁獲取部分的黑名單號碼信息。
 * @param startIndex 查詢的開始位置
 * @return
 */
public List 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  infos = new ArrayList();
    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的使用和簡單優化。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved