欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

Android项目:手机安全卫士(16)—— 复杂 ListView浅析

来源: 开发者 投稿于  被查看 6345 次 评论:78

Android项目:手机安全卫士(16)—— 复杂 ListView浅析


Android项目:手机安全卫士(16)—— 复杂 ListView

1 介绍

接着昨天的内容,今天继续完善应用列表,首先,应用分为系统应用和用户应用,安装位置分为手机内存和 sdcard,所以,我们在 ListView 中添加一个分类,分为系统应用和用户应用,每一个 item 显示安装的位置,最终效果如下所示:

ListView标题栏悬浮

2 判断应用类型和安装位置

ApplicationInfo 对象有个 flags 属性,它有很多个状态值,我们用它和 FLAG_EXTERNAL_STORAGE、FLAG_SYSTEM 进行与操作,从而判断是否具有该状态值,代码如下:


    int flag = applicationInfo.flags;
    if((flag&ApplicationInfo.FLAG_EXTERNAL_STORAGE)==ApplicationInfo.FLAG_EXTERNAL_STORAGE){
        //安装在sdcard中
        appInfo.isSdcardApp = true;
    }else {
        //安装在手机内存
        appInfo.isSdcardApp = false;
    }

    if((flag & ApplicationInfo.FLAG_SYSTEM)==ApplicationInfo.FLAG_SYSTEM){
        //系统应用
        appInfo.isUserApp = false;
    }else {
        //用户应用
        appInfo.isUserApp = true;
    }

然后在 AppManagerAdapter 的 getView() 方法中进行判断并赋值,代码如下:


    if (appInfo.isSdcardApp) {
        holder.tvLocation.setText("外置存储器");
    } else {
        holder.tvLocation.setText("手机内存");
    }

3 ListView 添加两种布局

接下来的就是今天的重点了,给 ListView 添加第二种 item 布局,将应用分为两类:系统应用和用户应用,并且用两个列表来存储,代码如下:


    ArrayList installedApp = getInstalledApp();
    ArrayList userList = new ArrayList();
    ArrayList systemList = new ArrayList();
    //拆分成两个列表
    for (AppInfo info : installedApp) {
        if (info.isUserApp) {
            userList.add(info);
        } else {
            systemList.add(info);
        }
    }

给 ListView 添加两种及以上布局,需要重写 Adapter 的两个方法:getViewTypeCount()、getItemViewType(),分别返回布局类型的数量,以及当前 item 的类型,对 AppManagerAdapter 的代码修改如下:


    /**
     * 应用列表适配器
     *
     * Created by XWdoor on 2016/3/22 022 11:44.
     * 博客:http://blog.csdn.net/xwdoor
     */
    public class AppManagerAdapter extends BaseAdapter {
        private final ArrayList mUserList;
        private final ArrayList mSystemList;
        private final Context mContext;

        public AppManagerAdapter(Context ctx, ArrayList userList, ArrayList systemList) {
            this.mContext = ctx;
            this.mUserList = userList;
            this.mSystemList = systemList;
        }

        @Override
        public int getCount() {
            return mUserList.size() + mSystemList.size() + 2;//增加两个标题栏
        }

        @Override
        public AppInfo getItem(int position) {
            // 遇到标题栏,直接返回null
            if (position == 0 || position == mUserList.size() + 1) {
                return null;
            }

            if (position < mUserList.size() + 1) {
                return mUserList.get(position - 1);//去掉标题栏的占位
            } else {
                return mSystemList.get(position - mUserList.size() - 2);//需要减掉两个标题栏的占位
            }
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        // 返回布局类型的个数, 就会缓存两种convertView
        @Override
        public int getViewTypeCount() {
            return 2;
        }

        // 根据当前位置,返回相应布局类型, 必须从0开始计算
        @Override
        public int getItemViewType(int position) {
            if (position == 0 || position == mUserList.size() + 1) {// 遇到标题栏
                return 0;
            } else {
                return 1;
            }
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            // 首先要判断当前布局类型, 系统会缓存多种convertView, 然后根据当前布局类型,返回对应的convertView
            // 根据类型,加载不同布局
            switch (getItemViewType(position)){
                case 0://标题
                    HeaderHolder headerHolder = null;
                    if(convertView==null){
                        convertView = View.inflate(mContext,R.layout.item_app_manager_header,null);
                        headerHolder = new HeaderHolder();
                        headerHolder.tvHeader = (TextView) convertView.findViewById(R.id.tv_header);

                        convertView.setTag(headerHolder);
                    }else {
                        headerHolder = (HeaderHolder) convertView.getTag();
                    }

                    if(position == 0){
                        headerHolder.tvHeader.setText("用户应用(" + mUserList.size() + ")");
                    }else {
                        headerHolder.tvHeader.setText("系统应用(" + mSystemList.size() + ")");
                    }
                    break;
                case 1://应用item
                    ViewHolder holder = null;
                    if (convertView == null) {
                        convertView = View.inflate(mContext, R.layout.item_app_manager_adapter, null);
                        holder = new ViewHolder();
                        holder.tvName = (TextView) convertView.findViewById(R.id.tv_name);
                        holder.tvLocation = (TextView) convertView.findViewById(R.id.tv_location);
                        holder.ivIcon = (ImageView) convertView.findViewById(R.id.iv_icon);
                        convertView.setTag(holder);
                    } else {
                        holder = (ViewHolder) convertView.getTag();
                    }
                    AppInfo appInfo = getItem(position);
                    holder.tvName.setText(appInfo.appName);
                    holder.ivIcon.setImageDrawable(appInfo.icon);
                    if (appInfo.isSdcardApp) {
                        holder.tvLocation.setText("外置存储器");
                    } else {
                        holder.tvLocation.setText("手机内存");
                    }
                    break;
            }

            return convertView;
        }

        static class ViewHolder {
            public TextView tvName;
            public ImageView ivIcon;
            public TextView tvLocation;
        }

        static class HeaderHolder{
            public TextView tvHeader;
        }
    }

首先是构造函数需要传入两个应用列表,getCount() 方法需要返回两个列表总数再加上两个标题栏;getItem() 方法根据情况返回,若是标题栏,则返回 null,若是具体的某个 item,需要去掉标题栏的占位;同理,在 getView() 方法中,需要根据不同的类型,加载不同的布局,这里需要说一下关于 View 的复用问题,根据 getViewTypeCount() 方法的返回值,系统会保存多种 convertView, 然后根据当前布局类型,返回对应的 convertView。最好的效果如图:

复杂ListView-标题栏

4 ListView 标题栏悬浮

每当标题栏滑动到顶端时,都会有一个悬浮效果,表示以下 item 都是属于该标题类型,其实这只是一种障眼法,那个标题栏一直都在,只是在适当的时候修改它的显示文字即可,需要修改 AppManagerActivity 的布局文件,将 ListView 放在一个 FrameLayout 布局中,然后添加一个 TextView 覆盖在上面,同时增加一个加载数据提示进度条,代码如下:


    <framelayout android:layout_height="match_parent" android:layout_width="match_parent">

        

        

        

            

            
        
    </framelayout>

然后添加 ListView 的滚动监听,用于更改标题栏的文字显示,代码如下:


    final TextView tvHeader = (TextView) findViewById(R.id.tv_header);

    lvList.setOnScrollListener(new AbsListView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {

        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            // firstVisibleItem 第一个可见元素位置
            // visibleItemCount 可见元素数量
            // totalItemCount 元素总数
            if (mUserList != null && mSystemList != null) {
                if (firstVisibleItem >= mUserList.size() + 1) {//算上标题栏占位
                    tvHeader.setText("系统应用(" + mSystemList.size() + ")");
                } else {
                    tvHeader.setText("用户应用(" + mUserList.size() + ")");
                }
            }
        }
    });

效果如下:

ListView标题栏悬浮

5 在线程中加载数据

获取手机所有安装的应用是一个耗时操作,所以我们需要在线程中加载数据,为了用户体验,防止白屏出现,所以加上了一个进度条,代码如下:


    @Override
    protected void loadData() {
        llLoading.setVisibility(View.VISIBLE);
        //在线程中加载数据
        new Thread() {
            @Override
            public void run() {
                String availRom = getAvailSpace(Environment.getExternalStorageDirectory().getAbsolutePath());
                String availSdcard = getAvailSpace(Environment.getDataDirectory().getAbsolutePath());
                tvRomAvail.setText("内部存储可用:" + availRom);
                tvSdcardAvail.setText("sdcard可用:" + availSdcard);

                ArrayList installedApp = getInstalledApp();
                mUserList = new ArrayList();
                mSystemList = new ArrayList();
                for (AppInfo info : installedApp) {
                    if (info.isUserApp) {
                        mUserList.add(info);
                    } else {
                        mSystemList.add(info);
                    }
                }

                //更新UI数据
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        lvList.setAdapter(new AppManagerAdapter(AppManagerActivity.this, mUserList, mSystemList));
                        llLoading.setVisibility(View.GONE);
                    }
                });
            }
        }.start();

    }

一般在线程中更新 UI,我们都会使用 Handler 来完成,但是只有一两行语句,感觉大材小用了,这里我们可以使用 runOnUiThread() 方法,它是在主线程中运行的,所以可以放心使用。

6 总结

今天的内容还是比较充实的,学到了两个知识点:

ListView 多种 item 的实现线程中方便的更新 UI
 

用户评论