最新實戰教程,讓你瞭解Android自動化刷量、作弊與防作弊的那些事,案例:刷友盟統計、批量註冊蘋果帳號
好啦,本來我上次說,這一次是講流量管理這個功能的,但是由於一些特殊情況,我們就下次再說那個流量管理的功能,那麼我們今天就來講一個控件,這個控件我覺得應該會挺常用的,但之前我從來沒有接觸過,所有感覺好像很少人用一樣,今天我們就講一下,畢竟,感覺應用還是挺多的,這個控件就是ExpandableListView,我們是把它整合到我們手機衛士這個項目裏面去的,所以我們就來看一下我們今天要做的效果
我們的這個功能是常用電話查詢,但是功能是其次的,主要是這個控件,右邊的圖就是它的顯示樣子啦,是不是感覺和QQ的好友那個界面有點熟悉呢,如果把全部收起來,它看上去就和一個listview沒什麼差別,但如果條開某一下條目,那它裏面的也是和一個listview差不多,這樣,就和我們的手機QQ裏面,QQ好友這個列表是非常的相似的了,但是具體QQ使用的是不是這個控件,我就不知道啦,但是完全可以用這個控件來實現的
好啦,下面我們來說一下這個功能,這個功能就是常用的電話查詢,就是我們在高級工具裏面新建一個條目,就是常用查詢,然後裏面呢就是分類來存放一些常用的電話號碼啦,當我們點擊其中一個條目的號碼的時候,我們就會撥打這個條目對應的號碼的
功能是非常的簡單的,主要就是ExpandableListView這個控件的使用啦
其實這個ExpandableListView就和listview差不多的,只不過它是按級分類的而已
那麼,我們現在就開始寫代碼啦,首先我們是要在高級工具裏面添加一個條目的,這裏我就不粘代碼出來啦,因爲我們已經在高級工具這個類裏面添加了很多的條目啦,大家可以下載源碼來看一下,或看看之前的文章,(因爲我們這個佈局原因,我對那個高級工具的佈局文件進行了一些修改的,修改也不大,大家有疑問的可以看看)
寫完這個條目的代碼之後,我們就要寫一下ExpandableListView所在的activity啦
但是在這之前,我們要先講一下我們上面那些電話的數據來源,我們上面的那些電話都是來源於一個數據庫的,
classlist就是存放那些分組的,每一個分組對應一個table分別就是上面的table1這些啦
這個數據庫是很小的,所以我們這一次就給大家講一下,怎樣把數據庫文件打包到apk裏面,
首先我們要先講一下,我們一個Android工程裏面的的目錄,有兩個目錄是不會被打包成二進制的,一個就是assets,一個就是res/raw,它們有什麼區別呢
我在這裏說一下,assets是不會生成id的,而且它支持任意深度的子目錄,res/raw就會生成id的啦,
因爲我們的數據庫是要用來讀取數據的,肯定是不能被打包成二進制的,所以我們就要放在這兩個目錄的其中一個裏面啦,
因爲不用生成id,那麼我們就把我們的數據庫放到assets這個目錄下面啦
好啦,我們把數據庫文件放到assets這個目錄下面,那用戶安裝了這個apk之後,我們肯定是要把這個數據庫拷貝到sd卡上的,不然我們無法訪問這個數據庫嘛,所有我們就要寫一個拷貝文件的方法啦,因爲文件可能會比較大,所有就要開啓一個線程來進行啦
// 把數據庫從assets裏面讀取到sd卡里面,開啓了一條線程進程操作,等讀取完成之後,發送一個消息給handler處理
private void moveDatabase(File file)
{
File dir = new File(Environment.getExternalStorageDirectory()
+ "/security/db");
if (!dir.exists())
{
dir.mkdirs();
}
else
{
try
{
final InputStream is = getAssets().open("commonnum.db");
final FileOutputStream fos = new FileOutputStream(file);
new Thread(new Runnable()
{
@Override
public void run()
{
try
{
int len = 0;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer)) != -1)
{
fos.write(buffer, 0, len);
}
fos.flush();
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
if (is != null)
{
try
{
is.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
if (fos != null)
{
try
{
fos.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
handler.sendEmptyMessage(1);
}
}).start();
}
catch (Exception e)
{
e.printStackTrace();
}
}
好啦,拷貝完我們的數據庫文件之後,我們肯定就要讀取裏面的數據啦,我們要讀取的就是一個分組的信息,一個就是對應分組的信息,所有我們的dao裏面只有兩個方法
com.xiaobin.security.dao.CommonNumberDao
package com.xiaobin.security.dao;
import java.util.ArrayList;
import java.util.List;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
public class CommonNumberDao
{
//拿到所有的分組信息
public static List<String> getAllGroup(String path)
{
List<String> group = new ArrayList<String>();
SQLiteDatabase db = SQLiteDatabase.openDatabase(path, null,
SQLiteDatabase.OPEN_READONLY);
if (db.isOpen())
{
Cursor cursor = db.rawQuery("select name from classlist", null);
while (cursor.moveToNext())
{
group.add(cursor.getString(0));
}
cursor.close();
}
db.close();
return group;
}
//拿到所有的電話信息
public static List<List<String>> getAllChildren(String path,
int groupCount)
{
StringBuffer sb = new StringBuffer();
List<List<String>> allChild = new ArrayList<List<String>>();
SQLiteDatabase db = SQLiteDatabase.openDatabase(path, null,
SQLiteDatabase.OPEN_READONLY);
if (db.isOpen())
{
for (int i = 1; i <= groupCount; i++)
{
List<String> child = new ArrayList<String>();
// 應爲我們的數據庫是每一個group裏面的條目,都是對應一張表的,
// 所以我們就可以這樣來裝拼sql語句啦
String sql = "select name, number from table" + i;
Cursor cursor = db.rawQuery(sql, null);
while (cursor.moveToNext())
{
// 把信息拼成name-number的形式,到時再拿出來
sb.append(cursor.getString(0));
sb.append("-");
sb.append(cursor.getString(1));
child.add(sb.toString());
// 清空stringbuffer裏面的內容
sb.setLength(0);
}
cursor.close();
allChild.add(child);
}
}
db.close();
return allChild;
}
}
好啦,上面兩個方法都是數據庫的簡單操作,我們就多說啦,不明白的可以問一下
數據準備好啦,那麼我們就來寫一下我們的acitivty的佈局文件啦
common_number.xml
<?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" >
<ExpandableListView
android:id="@+id/elv_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:cacheColorHint="@android:color/white"
android:groupIndicator="@drawable/expendable_group_selector" />
</LinearLayout>
這個佈局很簡單啦,就是一個ExpandableListView,最值得關注的就是最後一行啦,最後一行就是指定那個分組的前面的那個圖標的,我就對它進行了更改,如果更喜歡系統的,那也可以不改的
expendable_group_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/group_list" android:state_expanded="true"></item>
<item android:drawable="@drawable/group_list_pressed"></item>
</selector>
佈局文件寫好了之後呢,我們就要把數據放置到這個view裏面了,其實呢,ExpandableListView和Listview是差不多的,其實感覺它就是listview裏面嵌套一個ListView
而我們的listview是通過adapter來設置數據的,那麼,ExpandableListView可不可以這樣呢
答案是可以的,Android裏面它就有一個專門用來爲它放置數據的adapter的,它就是BaseExpandableListAdapter,我要只要繼續它,然後重寫裏面的方法就可以的啦
private class MyBaseExpandableListAdapter extends BaseExpandableListAdapter
{
// 有多少個分組
@Override
public int getGroupCount()
{
return group.size();
}
// 對應分組的條目個數
@Override
public int getChildrenCount(int groupPosition)
{
return childs.get(groupPosition).size();
}
// 拿到第幾個分組對應的對象
@Override
public Object getGroup(int groupPosition)
{
return group.get(groupPosition);
}
// 拿到第幾個分組對應的第幾個條目
@Override
public Object getChild(int groupPosition, int childPosition)
{
return childs.get(groupPosition).get(childPosition);
}
// 拿到第幾個分組對應的位置
@Override
public long getGroupId(int groupPosition)
{
return groupPosition;
}
// //拿到第幾個分組對應的第幾個條目位置
@Override
public long getChildId(int groupPosition, int childPosition)
{
return childPosition;
}
// 這個具體的作用,我也還沒弄清楚,各位有了解的,不訪告訴我們一聲
// 官方文檔是這樣說的:組和子元素是否持有穩定的ID,也就是底層數據的改變不會影響到它們。
// 返回一個Boolean類型的值,如果爲TRUE,意味着相同的ID永遠引用相同的對象。
// 具體有什麼用,我就弄不懂了
@Override
public boolean hasStableIds()
{
return false;
}
// 返回對應的分組的view
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent)
{
View view;
ViewHolder holder;
if (convertView == null)
{
view = View.inflate(CommonNumberActivity.this,
R.layout.expandable_group, null);
TextView textView = (TextView) view
.findViewById(R.id.expandable_group);
holder = new ViewHolder();
holder.tv_group = textView;
view.setTag(holder);
}
else
{
view = convertView;
holder = (ViewHolder) view.getTag();
}
holder.tv_group.setText(group.get(groupPosition));
return view;
}
// 返回對應分組的對應條目的view
@Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent)
{
View view;
ViewHolder holder;
if (convertView == null)
{
view = View.inflate(CommonNumberActivity.this,
R.layout.expandable_children, null);
TextView tv_name = (TextView) view
.findViewById(R.id.expandable_child_name);
TextView tv_number = (TextView) view
.findViewById(R.id.expandable_child_num);
holder = new ViewHolder();
holder.tv_name = tv_name;
holder.tv_number = tv_number;
view.setTag(holder);
}
else
{
view = convertView;
holder = (ViewHolder) view.getTag();
}
String str = childs.get(groupPosition).get(childPosition);
// 分割出來
String[] msg = str.split("-");
holder.tv_name.setText(msg[0]);
holder.tv_number.setText(msg[1]);
return view;
}
// 是不是允許分組裏面的條目接收點擊事件,false爲不允許,true爲允許
@Override
public boolean isChildSelectable(int groupPosition, int childPosition)
{
return true;
}
}
private class ViewHolder
{
TextView tv_group;
TextView tv_name;
TextView tv_number;
}
其中,group和childs就是我們的dao裏面讀取到的東西啦,都是一個list列表,上面的這個adapter雖然方法比較多,但是如果瞭解了,還是比較好理解的
如果有什麼不明白的,可以提出來,那麼數據設置進行,我們就要完成我們的點擊,就撥打對應的電話的功能啦
所有我們就給它的子列表添加一個點擊事件啦,Android也幫我們定義好這樣一個事件的啦
// 給子列表添加點擊事件
elv_list.setOnChildClickListener(new OnChildClickListener()
{
@Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id)
{
String str = childs.get(groupPosition).get(childPosition);
// 拿到電話號碼
String number = str.split("-")[1];
// 設置打電話的intent
Intent intent = new Intent("android.intent.action.CALL",
Uri.parse("tel:" + number));
startActivity(intent);
return false;
}
});
就這樣子,我們的這個功能就完成的啦,而且我們主要學習的就是ExpandableListView是怎麼用的啦,上面還有兩個ExpandableListView的佈局文件我沒有粘出來,都是一些簡單的textview,大家看也可以看到的啦,所有就不粘了
下面把完整的activity類粘出來
com.xiaobin.security.ui.CommonNumberActivity
package com.xiaobin.security.ui;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.TextView;
import android.widget.Toast;
import com.xiaobin.security.R;
import com.xiaobin.security.dao.CommonNumberDao;
public class CommonNumberActivity extends Activity
{
private static final String DBPATH = Environment
.getExternalStorageDirectory() + "/security/db/commonnum.db";
private ExpandableListView elv_list;
private MyBaseExpandableListAdapter adapter;
private List<String> group;
private List<List<String>> childs;
@SuppressLint("HandlerLeak")
private Handler handler = new Handler()
{
public void handleMessage(Message msg)
{
// 拿到數據庫裏面的內容
group = CommonNumberDao.getAllGroup(DBPATH);
childs = CommonNumberDao.getAllChildren(DBPATH, group.size());
// 當移動成功數據庫之後,就把數據顯示出來
adapter = new MyBaseExpandableListAdapter();
elv_list.setAdapter(adapter);
// 給子列表添加點擊事件
elv_list.setOnChildClickListener(new OnChildClickListener()
{
@Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id)
{
String str = childs.get(groupPosition).get(childPosition);
// 拿到電話號碼
String number = str.split("-")[1];
// 設置打電話的intent
Intent intent = new Intent("android.intent.action.CALL",
Uri.parse("tel:" + number));
startActivity(intent);
return false;
}
});
}
};
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.common_number);
elv_list = (ExpandableListView) findViewById(R.id.elv_list);
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED))
{
File file = new File(DBPATH);
// 如果數據庫文件已經存在,就不用移動啦
if (file.exists())
{
handler.sendEmptyMessage(1);
}
else
{
// 把數據庫從assets裏面讀取出來
moveDatabase(file);
}
}
else
{
Toast.makeText(this, "SD卡不可用,請插入SD卡", Toast.LENGTH_SHORT).show();
}
}
// 把數據庫從assets裏面讀取到sd卡里面,開啓了一條線程進程操作,等讀取完成之後,發送一個消息給handler處理
private void moveDatabase(File file)
{
File dir = new File(Environment.getExternalStorageDirectory()
+ "/security/db");
if (!dir.exists())
{
dir.mkdirs();
}
else
{
try
{
final InputStream is = getAssets().open("commonnum.db");
final FileOutputStream fos = new FileOutputStream(file);
new Thread(new Runnable()
{
@Override
public void run()
{
try
{
int len = 0;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer)) != -1)
{
fos.write(buffer, 0, len);
}
fos.flush();
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
if (is != null)
{
try
{
is.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
if (fos != null)
{
try
{
fos.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
handler.sendEmptyMessage(1);
}
}).start();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
// ========================================================================
private class MyBaseExpandableListAdapter extends BaseExpandableListAdapter
{
// 有多少個分組
@Override
public int getGroupCount()
{
return group.size();
}
// 對應分組的條目個數
@Override
public int getChildrenCount(int groupPosition)
{
return childs.get(groupPosition).size();
}
// 拿到第幾個分組對應的對象
@Override
public Object getGroup(int groupPosition)
{
return group.get(groupPosition);
}
// 拿到第幾個分組對應的第幾個條目
@Override
public Object getChild(int groupPosition, int childPosition)
{
return childs.get(groupPosition).get(childPosition);
}
// 拿到第幾個分組對應的位置
@Override
public long getGroupId(int groupPosition)
{
return groupPosition;
}
// //拿到第幾個分組對應的第幾個條目位置
@Override
public long getChildId(int groupPosition, int childPosition)
{
return childPosition;
}
// 這個具體的作用,我也還沒弄清楚,各位有了解的,不訪告訴我們一聲
// 官方文檔是這樣說的:組和子元素是否持有穩定的ID,也就是底層數據的改變不會影響到它們。
// 返回一個Boolean類型的值,如果爲TRUE,意味着相同的ID永遠引用相同的對象。
// 具體有什麼用,我就弄不懂了
@Override
public boolean hasStableIds()
{
return false;
}
// 返回對應的分組的view
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent)
{
View view;
ViewHolder holder;
if (convertView == null)
{
view = View.inflate(CommonNumberActivity.this,
R.layout.expandable_group, null);
TextView textView = (TextView) view
.findViewById(R.id.expandable_group);
holder = new ViewHolder();
holder.tv_group = textView;
view.setTag(holder);
}
else
{
view = convertView;
holder = (ViewHolder) view.getTag();
}
holder.tv_group.setText(group.get(groupPosition));
return view;
}
// 返回對應分組的對應條目的view
@Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent)
{
View view;
ViewHolder holder;
if (convertView == null)
{
view = View.inflate(CommonNumberActivity.this,
R.layout.expandable_children, null);
TextView tv_name = (TextView) view
.findViewById(R.id.expandable_child_name);
TextView tv_number = (TextView) view
.findViewById(R.id.expandable_child_num);
holder = new ViewHolder();
holder.tv_name = tv_name;
holder.tv_number = tv_number;
view.setTag(holder);
}
else
{
view = convertView;
holder = (ViewHolder) view.getTag();
}
String str = childs.get(groupPosition).get(childPosition);
// 分割出來
String[] msg = str.split("-");
holder.tv_name.setText(msg[0]);
holder.tv_number.setText(msg[1]);
return view;
}
// 是不是允許分組裏面的條目接收點擊事件,false爲不允許,true爲允許
@Override
public boolean isChildSelectable(int groupPosition, int childPosition)
{
return true;
}
}
private class ViewHolder
{
TextView tv_group;
TextView tv_name;
TextView tv_number;
}
}
好啦,今天就講到這裏啦,有什麼不明白的可以提問,下一次我們就真的講流量管理的功能啦
最後,和大家說一下
爲了方便大家的交流,我創建了一個羣,這樣子大家有什麼疑問也可以在羣上交流
羣號是298440981