A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 小鲁哥哥 于 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()
        }



1 个回复

正序浏览
多谢分享
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马