Android項目實戰--手機衛士33--ExpandableListView的使用



最新實戰教程,讓你瞭解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


今天源碼下載



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章