本帖最后由 小鲁哥哥 于 2017-4-12 14:27 编辑
【济南中心】Android课程同步笔记day07:Android应用之安全卫士
常用工具-常用号码常用号码界面展示的效果图:
整个界面的展示需要用到一个可展开的listview来实现--ExpandableListView里面group item的布局文件xml: [Java] 纯文本查看 复制代码 <?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="wrap_content"
android:background="#33000000"
android:orientation="vertical"
android:padding="8dp" >
<TextView
android:id="@+id/item_group_tv_title"
style="@style/textNormal"
android:text="group的title" />
</LinearLayout>
Child item的xml布局为:
[Java] 纯文本查看 复制代码 <?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="wrap_content"
android:orientation="vertical"
android:padding="8dp" >
<TextView
android:id="@+id/item_child_tv_name"
style="@style/textNormal"
android:text="child的name" />
<TextView
android:id="@+id/item_child_tv_number"
style="@style/textNormal"
android:text="child的number" />
</LinearLayout>
将我们准备的常用号码的数据库common.db跟之前的号码归属地一样需要拷贝到我们的应用程序sdcrad中,不过该数据库比较小就29KB不需要进行gzip的压缩处理了,直接进行拷贝即可。
在SpalshActivity中,拷贝常用号码数据库的逻辑: [Java] 纯文本查看 复制代码 private void copyCommonNumberDB() {
File file = new File(getFilesDir(), "commonnum.db");
if (file.exists()) {
return;
}
AssetManager assets = getAssets();
InputStream stream = null;
FileOutputStream fos = null;
try {
stream = assets.open("commonnum.db");
fos = new FileOutputStream(file);
int len = -1;
byte[] buffer = new byte[1024];
while ((len = stream.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
StreamUtils.closeIO(stream);
StreamUtils.closeIO(fos);
}
} 有了数据库,那么必然就得有一个数据库操作类dao,这里创建一个CommonNumberDao,用于获取所有的常用号码数据: [Java] 纯文本查看 复制代码 public class CommonNumberDao {
/**
* 获取所有的常用号码数据
* @param context
* @return
*/
public static List<GroupBean> getGroupDatas(Context context) {
List<GroupBean> list = new ArrayList<GroupBean>();
File file = new File(context.getFilesDir(), "commonnum.db");
String path = file.getAbsolutePath();
SQLiteDatabase db = SQLiteDatabase.openDatabase(path, null,
SQLiteDatabase.OPEN_READONLY);
// sql查询
String sql = "select name,idx from classlist";
Cursor groupCursor = db.rawQuery(sql, null);
if (groupCursor != null) {
while (groupCursor.moveToNext()) {
String title = groupCursor.getString(0);
int idx = groupCursor.getInt(1);
GroupBean group = new GroupBean();
group.title = title;
group.childrenDatas = new ArrayList<ChildBean>();
String childSql = "select number,name from table" + idx;
Cursor childCursor = db.rawQuery(childSql, null);
if (childCursor != null) {
while (childCursor.moveToNext()) {
String number = childCursor.getString(0);
String name = childCursor.getString(1);
ChildBean child = new ChildBean();
child.name = name;
child.number = number;
group.childrenDatas.add(child);
}
childCursor.close();
}
list.add(group);
}
groupCursor.close();
}
db.close();
return list;
}
} 、 有了数据后我们将数据填充到ExpandableListView里面进行展示就可以了,代码如下 [Java] 纯文本查看 复制代码 public class CommonNumberActivity extends Activity {
private ExpandableListView mListView;
private List<GroupBean> mGroupDatas;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_common_number);
initView();
initData();
}
private void initData() {
mGroupDatas=CommonNumberDao.getGroupDatas(this);
// adapter --> List<Group数据>--->Group数据(必备List<Child数据>)
mListView.setAdapter(new CommonNumberAdapter());
}
private void initView() {
mListView = (ExpandableListView) findViewById(R.id.cn_listview);
}
private class CommonNumberAdapter extends BaseExpandableListAdapter {
@Override
public int getGroupCount() {
if (mGroupDatas != null) {
return mGroupDatas.size();
}
return 0;
}
@Override
public int getChildrenCount(int groupPosition) {
if (mGroupDatas != null) {
GroupBean bean = mGroupDatas.get(groupPosition);
if (bean != null) {
List<ChildBean> childrenDatas = bean.childrenDatas;
if (childrenDatas != null) {
return childrenDatas.size();
}
}
}
return 0;
}
@Override
public Object getGroup(int groupPosition) {
if (mGroupDatas != null) {
return mGroupDatas.get(groupPosition);
}
return null;
}
@Override
public Object getChild(int groupPosition, int childPosition) {
if (mGroupDatas != null) {
GroupBean bean = mGroupDatas.get(groupPosition);
if (bean != null) {
List<ChildBean> childrenDatas = bean.childrenDatas;
if (childrenDatas != null) {
return childrenDatas.get(childPosition);
}
}
}
return null;
}
@Override
public long getGroupId(int groupPosition) {
// TODO Auto-generated method stub
return 0;
}
@Override
public long getChildId(int groupPosition, int childPosition) {
// TODO Auto-generated method stub
return 0;
}
@Override
public boolean hasStableIds() {
// TODO Auto-generated method stub
return false;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
GroupViewHolder holder = null;
if (convertView == null) {
// 没有复用
// 1. 加载布局
convertView = View.inflate(CommonNumberActivity.this,
R.layout.item_group, null);
// 2. 新建holder
holder = new GroupViewHolder();
// 3. 设置标记
convertView.setTag(holder);
// 4. findViewById
holder.tvTitle = (TextView) convertView
.findViewById(R.id.item_group_tv_title);
} else {
// 有复用
holder = (GroupViewHolder) convertView.getTag();
}
// 设置数据
GroupBean groupBean = mGroupDatas.get(groupPosition);
holder.tvTitle.setText(groupBean.title);
return convertView;
}
@Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
ChildViewHolder holder = null;
if (convertView == null) {
// 没有复用
// 1.加载布局
convertView = View.inflate(CommonNumberActivity.this,
R.layout.item_child, null);
// 2. 新建holder
holder = new ChildViewHolder();
// 3. 设置标记
convertView.setTag(holder);
// 4. findViewById
holder.tvName = (TextView) convertView
.findViewById(R.id.item_child_tv_name);
holder.tvNumber = (TextView) convertView
.findViewById(R.id.item_child_tv_number);
} else {
// 有复用
holder = (ChildViewHolder) convertView.getTag();
}
// 设置数据
ChildBean bean = (ChildBean) getChild(groupPosition, childPosition);
holder.tvName.setText(bean.name);
holder.tvNumber.setText(bean.number);
return convertView;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
// 用来控制child条目是否可以被点击
return true;
}
}
private class GroupViewHolder {
TextView tvTitle;
}
private class ChildViewHolder {
TextView tvName;
TextView tvNumber;
}
} ExpandableListView的点击事件
ExpandableListView点击子条目的时候需要获取当前的电话号码然后跳转到系统的拨打界面;而group条目点击的时候需要判断是否当前条目已经展开,如果展开即关闭,否则展开当前条目并将其他已经打开的条目关闭,将自己置顶。
[Java] 纯文本查看 复制代码 private void initEvent() {
//点击child条目的时候拨打电话
mListView.setOnChildClickListener(new OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
ChildBean bean = mGroupDatas.get(groupPosition).childrenDatas
.get(childPosition);
String number = bean.number;
// 拨号
Intent intent = new Intent();
intent.setAction(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:" + number));
startActivity(intent);
return false;
}
});
//点击group控制展开与关闭
mListView.setOnGroupClickListener(new OnGroupClickListener() {
@Override
public boolean onGroupClick(ExpandableListView parent, View v,
int groupPosition, long id) {
// 如果点击的当前条目是关闭的,就去展开,要求置顶显示
if (mCurrentOpenGroup != groupPosition) {
// 展开
mListView.expandGroup(groupPosition);
// 关闭已经打开的
mListView.collapseGroup(mCurrentOpenGroup);
// 置顶
mListView.setSelectedGroup(groupPosition);
// 记录当前打开
mCurrentOpenGroup = groupPosition;
} else {
// 点击的是打开的,关闭当前
mListView.collapseGroup(groupPosition);
// 记录没有一个是打开的
mCurrentOpenGroup = -1;
}
// 是否需要系统实现点击行为
return true;
}
});
}
归属地显示界面
服务的代码主要是在外拨电话和接收到电话的时候显示号码的归属地
[Java] 纯文本查看 复制代码 public class NumberAddressService extends Service {
private static final String TAG = "NumberAddressService";
private TelephonyManager mTm;
private CallInListener mCallInListener;
private CallOutReceiver mCallOutReceiver;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
Log.d(TAG, "号码归属地服务的开启");
mTm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
// 1.如果电话拨入时,显示拨入号码的归属地
mCallInListener = new CallInListener();
mTm.listen(mCallInListener, PhoneStateListener.LISTEN_CALL_STATE);
// 2.如果电话拨出时,显示拨出号码的归属地
// 广播接受者获取对应拨出状态,动态注册
mCallOutReceiver = new CallOutReceiver();
IntentFilter filter = new IntentFilter();
// 接收的行为
filter.addAction(Intent.ACTION_NEW_OUTGOING_CALL);
registerReceiver(mCallOutReceiver, filter);
}
private class CallInListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
// * @see TelephonyManager#CALL_STATE_IDLE
// * @see TelephonyManager#CALL_STATE_RINGING
// * @see TelephonyManager#CALL_STATE_OFFHOOK
if (state == TelephonyManager.CALL_STATE_RINGING) {
// 响铃时显示归属地
String address = NumberAddressDao.findAddress(
NumberAddressService.this, incomingNumber);
// 显示 Toast
Toast.makeText(NumberAddressService.this, address,
Toast.LENGTH_LONG).show();
}
}
}
private class CallOutReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String number = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
Log.d(TAG, "outgoing: " + number);
// 响铃时显示归属地
String address = NumberAddressDao.findAddress(
NumberAddressService.this, number);
// 显示 Toast
Toast.makeText(NumberAddressService.this, address,
Toast.LENGTH_LONG).show();
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "号码归属地服务的关闭");
// 注销拨入的监听
mTm.listen(mCallInListener, PhoneStateListener.LISTEN_NONE);
// 注销拨出的广播接受者
unregisterReceiver(mCallOutReceiver);
}
}
注意添加权限: 外拨电话 <uses-permissionandroid:name="android.permission.PROCESS_OUTGOING_CALLS" /> 外来电话读取号码: <uses-permission android:name="android.permission.READ_PHONE_STATE"/> 来电去电的页面能够显示出号码的正确归属地。但是提示框会在显示一段时间之后会消失,但是我们希望这个吐司能够一直不消失。因此,我们需要自定义一个吐司。
自定义吐司
我们需要自定义一个吐司让归属地显示能够一直不消失,并在此基础上进行一定样式的修改,下面我们先开始从系统的吐司源码中抽取出我们所需要的代码,进行改写后的代码如下所示:
[Java] 纯文本查看 复制代码 /**
* 自定义的号码归属地显示吐司
*/
public class NumberAddressToast {
private WindowManager mWM;
private View mView;
private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
public NumberAddressToast(Context context) {
// 初始化WindowManager
mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
// 加载自定义Toast的样式
mView = View.inflate(context, R.layout.toast_number_address, null);
// param初始化
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
params.format = PixelFormat.TRANSLUCENT;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
}
public void show(String address) {
if (mView.getParent() != null) {
mWM.removeView(mView);
}
TextView tv = (TextView) mView.findViewById(R.id.toast_tv_location);
tv.setText(address);
mWM.addView(mView, mParams);
}
public void hide() {
if (mView.getParent() != null) {
mWM.removeView(mView);
}
}
}
然后在号码归属地服务中将之前的吐司显示换成我们自定义的吐司即可,不过要注意的是由于吐司是由系统的windowmanager服务来进行显示的,如果不显示吐司了一定要记得去隐藏吐司,所以这里在监听到电话状态为idle空闲的时候一定要隐藏吐司:
[Java] 纯文本查看 复制代码 private class CallListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, final String incomingNumber) {
// * @see TelephonyManager#CALL_STATE_IDLE:闲置状态
// * @see TelephonyManager#CALL_STATE_RINGING:响铃状态
// * @see TelephonyManager#CALL_STATE_OFFHOOK:摘机状态,接听电话状态
// state
// incomingNumber:拨入的号码
if (state == TelephonyManager.CALL_STATE_RINGING) {
// 响铃时,显示归属地
String address = NumberAddressDao.findAdress(NumberAddressService.this,incomingNumber);
mAddressToast.show(address);
}else if(state == TelephonyManager.CALL_STATE_IDLE){
mAddressToast.hide()
}
}
}
移动吐司
我们将实现吐司的移动,为实现这样的效果就需要实现吐司的onTouch( )方法,即在吐司的触摸事件中实现这一效果,实现onTouch( )之前,前面的自定义吐司的代码我们需要进行一定的更改,如下所示:[Java] 纯文本查看 复制代码 public NumberAddressToast(Context context) {
this.mContext = context;
// 初始化WindowManager
mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
// 加载自定义Toast的样式
mView = View.inflate(context, R.layout.toast_number_address, null);
// param初始化
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
// | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
params.format = PixelFormat.TRANSLUCENT;
// params.windowAnimations =
// com.android.internal.R.style.Animation_Toast;
// params.type = WindowManager.LayoutParams.TYPE_TOAST;// 不可以被触摸的
params.type = WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;// 可以被触摸的,可以显示在打电话界面上方的
params.setTitle("Toast");
}
不过要记得添加一个权限:
<uses-permissionandroid:name="android.permission.SYSTEM_ALERT_WINDOW"/>
接下来给吐司设置onTouch事件,在onTouch方法中实现: [Java] 纯文本查看 复制代码 @Override
public boolean onTouch(View v, MotionEvent event) {
Log.d(TAG, "触摸了自定义的toast");
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mDownX = event.getRawX();
mDownY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float moveX = event.getRawX();
float moveY = event.getRawY();
float diffX = moveX - mDownX;
float diffY = moveY - mDownY;
mParams.x += diffX;
mParams.y += diffY;
mWM.updateViewLayout(mView, mParams);
mDownX = moveX;
mDownY = moveY;
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return true;
} 以上代码即可实现号码归属地吐司的拖动了。运行程序,可以拖动吐司进行移动,而且拖动后再次来电时,显示的位置是上次拖动结束时的位置。
自定义对话框
上面我们所定义的吐司是默认的背景,为了实现更好的美观效果,这里需要提供不同的样式风格给用户,如下所示,在弹出的对话框中为用户提供了多种样式的选择:
为了这样的对话框样式效果,我们需要进行自定义对话框,在com.itheima.mobilesafe.view 包创建AddressStyleDialog.java 文件,在该文件中负责对话框样式的设计。 首先,创建一个没有标题的对话框,即如下图样式的对话框,然后使用数据适配器进行数据填充对话框AddressStyleDialog的代码如下所示:
[Java] 纯文本查看 复制代码 /**
* 自定义归属地风格选择的对话框
*/
public class AddressStyleDialog extends Dialog {
//屏幕宽度
private int mScreenWidth;
public AddressStyleDialog(Context context) {
super(context);
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
mScreenWidth = metrics.widthPixels;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 去掉默认的titlebar
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.dialog_address_style);// 设置dialog的布局
Window window = getWindow();
LayoutParams params = window.getAttributes();
params.gravity = Gravity.BOTTOM | Gravity.CENTER;
params.width = mScreenWidth;
window.setAttributes(params);
}
}
对话框加载的布局文件dialog_address_style.xml 中是一个TextView 和ListView,具体的代码如下所示:
[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" >
<TextView
style="@style/textNormal"
android:layout_width="match_parent"
android:background="#55000000"
android:padding="4dp"
android:text="选择归属地样式"
android:textColor="#ffffffff" />
<ListView
android:id="@+id/dialog_listview"
android:layout_width="match_parent"
android:layout_height="200dp" >
</ListView>
</LinearLayout>
在常用设置界面,我们加入了归属地显示风格这一功能,对应的布局文件也有了一定的改变 res/layout/activity_setting.xml: [Java] 纯文本查看 复制代码 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:itheima="http://schemas.android.com/apk/res/com.itheima.zphuanlove"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@color/title_bg"
android:gravity="center"
android:text="设置中心"
android:textColor="@android:color/white"
android:textSize="24sp" />
<com.itheima.zphuanlove.view.SettingItemView
android:id="@+id/mSivAutoUpdate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
itheima:sivBackground="start"
itheima:sivText="自动更新设置" />
<com.itheima.zphuanlove.view.SettingItemView
android:id="@+id/mSivCallSmsSafe"
android:layout_width="match_parent"
android:layout_height="wrap_content"
itheima:sivBackground="end"
itheima:sivText="骚扰拦截设置" />
<com.itheima.zphuanlove.view.SettingItemView
android:id="@+id/setting_siv_number_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
itheima:sivBackground="start"
itheima:sivText="归属地显示设置"
itheima:sivToggleEnable="true" />
<com.itheima.zphuanlove.view.SettingItemView
android:id="@+id/setting_siv_address_style"
android:layout_width="match_parent"
android:layout_height="wrap_content"
itheima:sivBackground="end"
itheima:sivText="归属地风格设置"
itheima:sivToggleEnable="false" />
</LinearLayout> 在SettingActivity中找到归属地风格设置的条目初始化findViewById,然后设置点击事件,将自定义的AddressStyleDialog给显示出来: [Java] 纯文本查看 复制代码 private void clickAddressStyle(){
AddressStyleDialog dialog = new AddressStyleDialog(this);
dialog.show()
}
|