編輯:關於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)是用來模擬請求網絡時的延遲。
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的布局文件。這樣就能展示數據了,但是存在很多的問題,下面我們就來簡單的優化。
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;
}
因為我們我們在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、效果如下:實例如下: 上圖的添加數據按鈕可以換成一個進度條 因為沒有數據所以我加了一個按鈕添加到數據庫用於測試;一般在服務器拉去數
當我們在手機上安裝360安全衛士時,手機屏幕上時刻都會出現一個小浮動窗口,點擊該浮動窗口可跳轉到安全衛士的操作界面,而且該浮動窗口不受其他activity的覆蓋影響仍然可
本篇文章包含以下內容: Crash異常捕獲的簡單使用 Crash異常捕獲並發送到服務器在項目中,我們常常會遇到Crash的現象,也就是程序崩潰的時候,這個時候最常看到的
本篇隨筆將講解一下Android當中比較常用的兩個布局容器--ScrollView和HorizontalScrollView,從字面意義上來看也是非常的簡單的,Scrol