編輯:關於android開發
接著昨天的內容,今天繼續完善應用列表,首先,應用分為系統應用和用戶應用,安裝位置分為手機內存和 sdcard,所以,我們在 ListView 中添加一個分類,分為系統應用和用戶應用,每一個 item 顯示安裝的位置,最終效果如下所示:
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("手機內存");
}
接下來的就是今天的重點了,給 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。最好的效果如圖:
每當標題欄滑動到頂端時,都會有一個懸浮效果,表示以下 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() + ")");
}
}
}
});
效果如下:
獲取手機所有安裝的應用是一個耗時操作,所以我們需要在線程中加載數據,為了用戶體驗,防止白屏出現,所以加上了一個進度條,代碼如下:
@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() 方法,它是在主線程中運行的,所以可以放心使用。
今天的內容還是比較充實的,學到了兩個知識點:
ListView 多種 item 的實現線程中方便的更新 UIFragment的生命周期和Activity之間的通信以及使用,fragmentactivityFragment通俗來講就是碎片,不能單獨存在,意思就是說必須依附於Act
項目需求:自定義倒計時的TextView,倒計時textview現在這麼一個需求: 我有一個ListView,每一個列表項 布局如上圖,頂部一個大圖片展示,中部一個音頻
PyQt5系列教程(五)制作fastboot燒寫器軟硬件環境Windows 7Python 3.4.2PyQt 5.5.1PyCharm 5.0.2前言fastboot是
SVN與Git許久未上CU了,也許自從用上orgmode和git之後,有了更好的方式組織內容。但是總是對這裡情有獨鐘。這裡是我最早發布內容的地方,以後找到更好的方式,希望
Android應用項目中BaseAdapter、SimpleAdapte