Android项目实战--手机卫士33--ExpandableListView的使用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011485531/article/details/13279251



最新实战教程,让你了解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


今天源码下载



猜你喜欢

转载自blog.csdn.net/u011485531/article/details/13279251