- Android開發:從0到1 (清華開發者書庫)
- 趙志榮
- 3529字
- 2020-11-28 16:13:42
8.1 列表類控件
在數據很多的情況下,需要以列表形式展示,Android提供了多種形式的列表類型的控件,常用的有Spinner和ListView。列表類型的控件有三個要素:控件、Adapter(適配器)和數據源。
8.1.1 適配器
列表類型的控件需要將數據源綁定到控件上,才能看到豐富多彩的界面。而系統能夠為控件提供的數據源是多種形式的,它們可能來源于數據庫、XML、數組對象或集合對象等。Adpater(適配器)是控件和數據源之間的“橋梁”,通過這個“橋梁”可以將不同形式的數據源綁定到控件上,如圖8-1所示。

圖8-1 適配器作用
Android提供了多種適配器類,適配器類圖如8-2所示,CursorAdapter是數據庫適配器類,ArrayAdapter是數組適配器類,SimpleAdapter是Map集合適配器類。有時,系統提供的適配器不能滿足需要,就需要自定義適配器類了,這些自定義適配器更需要自己實現某些適配器接口或繼承某個適配器抽象類。這些適配器類會在后文中逐一介紹。

圖8-2 適配器類圖
8.1.2 Spinner
Spinner也是一種列表類型的控件,它提供了可以打開和關閉形式的列表控件,在用戶需要選擇時打開,選擇完成時關閉。打開Spinner列表有兩種模式:下拉列表風格和對話框風格。圖8-3(a)是下拉列表風格,它是默認情形。圖8-3(b)是對話框風格。打開Spinner列表模式可以通過XML中的android:spinnerMode屬性設置,取值是dropdown(下拉列表)和dialog(對話框風格)。

圖8-3 Spinner樣式
Spinner對應類是android.widget.Spinner,類圖如圖8-4所示,從圖中可見android. widget.Spinner繼承了抽象類android.widget.AdapterView, AdapterView是一種能夠由Adapter管理的控件。AdapterView子類還有ListView、GridView和Gallery等。

圖8-4 Spinner類圖
AdapterView定義了所用列表控件事件處理,AdapterView為列表控件事件處理提供了三個事件監聽器接口:
? AdapterView.OnItemClickListener。當列表項被單擊時觸發。
? AdapterView.OnItemLongClickListener。當列表項被長按時觸發。
? AdapterView.OnItemSelectedListener。當列表項被選擇時觸發。
事件處理者需要實現相應的事件監聽器接口。配合上述事件監聽接口AdapterView,還提供了注冊事件監聽器,三個方法如下:
? voidsetOnItemClickListener(AdapterView.OnItemClickListener listener)。注冊列表項單擊事件監聽器。
? voidsetOnItemLongClickListener(AdapterView.OnItemLongClickListener listener)。注冊列表項長按事件監聽器。
? voidsetOnItemSelectedListener(AdapterView.OnItemSelectedListener listener)。注冊列表項選擇事件監聽器。
注意 列表控件都直接或間接繼承了AdapterView,但在AdapterView中定義的三種事件都適合于所用的列表控件。Spinner只能使用AdapterView.OnItemSelectedListener監聽接口。雖然ListView可以使用上述三個接口,但是最常用的還是AdapterView. OnItemClickListener監聽接口。
8.1.3 實例:使用Spinner進行選擇
使用Spinner控件實現圖8-3所示的界面。
實現布局文件activity_main.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 android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/constellation" android:textSize="20sp"/> <Spinner android:id="@+id/spinner" ① android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout>
上述代碼第①行聲明了Spinner,其中android:spinnerMode屬性是默認值,此代碼運行結果為圖8-3(a)所示的列表模式。Spinner代碼修改如下:
<Spinner android:id="@+id/spinner" android:spinnerMode="dialog" ① android:layout_width="match_parent" android:layout_height="wrap_content"/>
添加代碼第①行android:spinnerMode="dialog",運行結果如圖8-3(b)所示。
MainActivity.java代碼如下:
public class MainActivity extends AppCompatActivity{ static final String TAG ="SpinnerSample"; static final String[]COLORS= new String[]{"紅色", "橙色", "黃色", "綠色", "藍色", "紫色"}; @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final ArrayAdapter CharSequence adapter = new ArrayAdapter CharSequence (this, android.R.layout.simple_spinner_item, COLORS); ① adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); ② Spinner spinner =(Spinner) findViewById(R.id.spinner); ③ spinner.setAdapter(adapter); ④ spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener(){ ⑤ @Override public void onItemSelected(AdapterView ? parent, View view, int position, long id){ ⑥ Log.i(TAG, "選擇:"+ adapter.getItem(position).toString()); } @Override public void onNothingSelected(AdapterView ? parent){ ⑦ Log.i(TAG, "未選中"); } }); } }
上述代碼第①行創建數組適配器ArrayAdapter對象,作為數組類型數據源的適配器,除了提供數組作為數據源以外,還要為Spinner中的列表項提供布局樣式。ArrayAdapter構造方法的第一個參數android.R.layout.simple_spinner_item是列表項布局,使用Android框架提供的simple_spinner_item.xml布局文件。ArrayAdapter構造方法的第二個參數COLORS是數據源,ArrayAdapter需要的數據源是數組。
代碼第②行是通過Spinner的setDropDownViewResource()方法設置彈出的下拉列表的布局樣式,參數android.R.layout.simple_spinner_dropdown_item是使用Android框架提供的simple_spinner_dropdown_item.xml布局文件。
代碼第③行獲得Spinner控件對象,然后再通過代碼第④行spinner.setAdapter(adapter)把適配器與Spinner控件綁定到一起。
代碼第⑤行的setOnItemSelectedListener()方法是注冊Spinner控件的選擇事件監聽器,選擇事件監聽器需要實現AdapterView.OnItemSelectedListener接口,具體實現代碼見第⑥行的選擇列表項方法onItemSelected和第⑦行未選中方法onNothingSelected。在代碼第⑥行onItemSelected中,參數position是選中的列表項位置,id是選項的編號。
8.1.4 ListView
ListView是Android中最為常用的列表類型控件,ListView中的選擇列表項樣式很豐富,有的是純文字,有的還可以帶有圖片等。
ListView對應類是android.widget.ListView,類圖如圖8-5所示,從圖中可見android.widget.ListView繼承了抽象類android.widget.AdapterView。

圖8-5 ListView類圖
8.1.5 實例1:使用ListView實現選擇文本
事實上,所有列表類型控件的技術難點是適配器,適配器一方面管理數據源,另一方面管理列表項的布局樣式。列表項中若只是顯示文本,可以使用ArrayAdapter、SimpleAdapter或CursorAdapter適配器,如果這些適配器不能滿足需要,可以自定義來實現。
本節先介紹在ListView中顯示文本實例,實例的運行效果如圖8-6所示。

圖8-6 實例運行效果
實現布局文件activity_main.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="horizontal"> <ListView android:id="@+id/ListView01" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>
上述代碼中聲明了ListView控件,屬性設置非常簡單。
MainActivity.java代碼如下:
public class MainActivity extends AppCompatActivity{ static final String TAG ="ListViewSample"; private String[] mStrings ={ "北京市","天津市","上海","重慶","烏魯木齊"…}; @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ArrayAdapter String adapter = new ArrayAdapter String (this, android.R.layout.simple_list_item_1,mStrings); ① ListView listview=(ListView) findViewById(R.id.ListView01); ② listview.setAdapter(adapter); ③ listview.setOnItemClickListener(new AdapterView.OnItemClickListener(){ ④ @Override public void onItemClick(AdapterView ? parent,View view,int position,long id){ ⑤ Log.i(TAG,"選擇:"+ mStrings[position]); } }); } }
上述代碼第①行創建數組適配器ArrayAdapter對象,構造方法ArrayAdapter參數android.R.layout.simple_list_item_1是使用Android框架提供的布局simple_list_item_1.xml文件,該布局文件中只有一個TextView控件,每一個列表項只能顯示文本內容。
構造方法ArrayAdapter參數mStrings是數組數據源。
提示 Android系統本身提供了很多這樣的布局文件,但是有的適合于ListView控件,有的適合于Spinner控件,有的適合于它的列表控件,這是使用時需要注意的。例如,8.1.3節的實例Spinner使用了Android框架提供的布局文件simple_spinner_item. xml,該文件就不適合在ListView中使用。
代碼第②行獲得ListView控件對象,然后再通過代碼第③行listview.setAdapter(adapter)把適配器與ListView控件綁定到一起。
代碼第④行的setOnItemClickListener()方法是注冊ListView控件的選擇事件監聽器,選擇事件監聽器需要實現AdapterView.OnItemClickListener接口,具體實現代碼為第⑤行所示的方法,參數position是選中列表項的位置,id是選項的編號。
8.1.6 實例2:使用ListView實現選擇文本+圖片
本節介紹如何自定義適配器實現ListView中顯示文本與圖片。自定義適配器主要是通過繼承BaseAdapter抽象類來實現,本實例的運行效果如圖8-7所示。

圖8-7 實例運行效果
該實例布局文件有兩個,一個是屏幕布局文件activity_main.xml;另一個是列表控件中每一個列表項的布局文件listview_item.xml。
主屏幕布局文件activity_main.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="horizontal"> <ListView android:id="@+id/ListView01" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>
列表項的布局文件listview_item.xml代碼如下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/icon" android:layout_width="48dp" android:layout_height="48dp" android:layout_marginLeft="5dp"/> <TextView android:id="@+id/textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toEndOf="@id/icon" ① android:layout_marginLeft="15dp" android:layout_marginTop="10dp" android:textSize="20sp"/> </RelativeLayout>
列表項的布局采用相對布局,代碼第①行設定了TextView在ImageView后面。
在該實例中,Java源代碼文件有兩個:屏幕Activity類MainActivity.java和自定義適配器類EfficientAdapter.java。
MainActivity.java代碼如下:
public class MainActivity extends AppCompatActivity{ static final String TAG ="ListViewSample"; String[] DATA ={"北京市", "天津市", "上海", "重慶", "哈爾濱", …}; ① int[] icons ={R.mipmap.beij ing, R.mipmap.tianj ing, R.mipmap.shanghai, R.mipmap.chongqing, R.mipmap.haerbing, …}; ② @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); EfficientAdapter adapter = new EfficientAdapter(this, R.layout.listview_item, DATA, icons); ③ ListView listview=(ListView) findViewById(R.id.ListView01); listview.setAdapter(adapter); listview.setOnItemClickListener(new AdapterView.OnItemClickListener(){ @Override public void onItemClick(AdapterView ? parent, View view, int position, long id){ Log.i(TAG, "選擇:"+ DATA[position]); } }); } }
上述代碼第①行是定義數據源中城市名稱數組。代碼第②行是與城市名稱數組對應的城市圖標數組,該數組是int類型,保存放置在res/mipmap目錄圖標id。
注意 DATA和icons兩個數組元素是一一對應的,即DATA第一個元素對應icons第一個元素,以此類推,所以它們兩個數組的長度也是相等的。如果讀者感覺兩個相互關聯的數組不好管理,可以使用Map數據結構保存城市名稱和城市圖標數據。
上述代碼第③行是實例化定義的適配器EfficientAdapter類,構造方法需要提供4個參數。
下面看看EfficientAdapter.java代碼:
public class EfficientAdapter extends BaseAdapter{ ① private LayoutInflater mInflater; //布局填充器 ② private String[] mDataSource; //數據源數組 private int[] mIcons; //與數據源數組對應的圖標id private int mResource; //列表項布局文件 private Context mContext; //所在上下文 ③ public EfficientAdapter(Context context, int resource, String[] dataSource, int[] icons){ mContext = context; mResource = resource; mDataSource = dataSource; mIcons = icons; //通過上下文對象創建布局填充器 mInflater = LayoutInflater.from(context); ④ } //返回總數據源中總的記錄數 @Override public int getCount(){ return mDataSource.length; } //根據選擇列表項位置,返回列表項所需數據 @Override public Object getItem(int position){ return mDataSource[position]; } //根據選擇列表項位置,返回列表項id @Override public long getItemId(int position){ return position; } //返回列表項所在視圖對象 @Override public View getView(int position, View convertView, ViewGroup parent){ ViewHolder holder; ⑤ if(convertView== null){ ⑥ convertView= mInflater.inflate(mResource, null); ⑦ holder = new ViewHolder(); holder.textView =(TextView) convertView.findViewById(R.id.textview); holder.imageView =(ImageView) convertView.findViewById(R.id.icon); convertView.setTag(holder); ⑧ }else{ holder =(ViewHolder) convertView.getTag(); ⑨ } holder.textView.setText(mDataSource[position]); Bitmap icon= BitmapFactory .decodeResource(mContext.getResources(), mIcons[position]); ⑩ holder.imageView.setImageBitmap(icon); return convertView; } //保存列表項中控件的封裝類 static class ViewHolder{ ? TextView textView; //列表項中Textview ImageView imageView; //列表項中ImageView } }
上述代碼第①行聲明繼承抽象類BaseAdapter。代碼第②行是定義成員變量mInflater,它是LayoutInflater類型,LayoutInflater是布局填充器,通過布局填充器類可以從XML文件創建視圖。代碼第③行是定義成員變量mContext,它是Context類型,Context類稱為“上下文”,上下文描述了當前組件的信息,Context是抽象類,它的子類有Activity、Service和廣播接收器等,在本例中就是當前的Activity對象。
代碼第④行LayoutInflater.from(context)是通過上下文對象創建布局填充器,這是一種工廠設計模式。
繼承BaseAdapter重寫getView()方法比較麻煩。getView()方法是ListView的每個列表項顯示到屏幕上時被調用的,getView()方法返回值View是列表項顯示的視圖。
注意 getView()方法的convertView參數非常重要!當用戶向上滑動屏幕翻動列表時,屏幕上的列表項會退出屏幕,屏幕下面原來不可見的列表項會進入屏幕,列表項在屏幕中顯示時會調用getView()方法獲得列表項視圖,如果每次都實例化列表項視圖,那么必然會導致大量對象的創建,消耗大量的內存。參數convertView就是為了解決這個問題而設計的,它是一個可重用的列表項視圖對象。如果convertView為空值(一般是剛進入屏幕),則實例化convertView(見代碼第⑥行)。如果convertView不為空值,直接返回convertView。實例化convertView對象,見代碼第⑦行,它通過布局填充器的inflate方法從布局文件創建,mResource是布局文件id。
代碼第⑤行是聲明ViewHolder類型的變量holder, ViewHolder是代碼第?行聲明的內部類用來保存列表項中控件的封裝類。holder保存在convertView的tag屬性中,見代碼⑧行。每一個View都有tag屬性,屬性類型是Obj ect,因此tag屬性可以保存任何對象。如果convertView不為空,可以通過代碼第⑨行的convertView.getTag()方法取出holder對象,但是這個holder是個舊的對象,保存了上次顯示列表項所需的內容。所以,要通過holder.textView.setText(mDataSource[position])和holder.imageView.setImageBitmap(icon)語句重新設置本次要顯示列表項所需內容。
代碼第⑩行是通過BitmapFactory工廠類decodeResource創建Bitmap圖片對象,decodeResource方法可以通過圖片資源id獲得圖片對象。
- OpenCV實例精解
- BeagleBone Media Center
- Bootstrap Essentials
- C++程序設計基礎教程
- Access 2010數據庫應用技術(第2版)
- LabVIEW虛擬儀器程序設計從入門到精通(第二版)
- JavaScript動態網頁編程
- Python函數式編程(第2版)
- Mastering Adobe Captivate 7
- Three.js權威指南:在網頁上創建3D圖形和動畫的方法與實踐(原書第4版)
- FusionCharts Beginner’s Guide:The Official Guide for FusionCharts Suite
- 創新工場講AI課:從知識到實踐
- Instant Pygame for Python Game Development How-to
- Learning TypeScript
- Java面試一戰到底(基礎卷)