Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android項目:手機安全衛士(16)—— 復雜 ListView淺析

Android項目:手機安全衛士(16)—— 復雜 ListView淺析

編輯:關於Android編程

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  
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved