前言
在Android系統中,針對大量數據的展示,可以使用ListView以列表的形式的呈現。雖然現在ListView在很多地方都被RecyclerView取代了,但是在一些合適的場景中依舊有用武之地。本文將詳細講解ListView的使用和常用技巧。
基本使用
ListView的使用還是很簡單的,重點在於數據由Adapter(適配器)提供的,ListView並不直接訪問數據源。因此,可以將ListView的使用分爲3步:
- 獲得數據源(如數組,List等)
- 通過數據源建立適配器(如ArrayAdapter等)
- 爲ListView設置適配器
使用系統提供的佈局
針對一些簡單的場景(如只需要展示字符串),使用系統提供的ArrayAdapter即可。ArrayAdapter使用數組或List作爲數據源,常用的兩個構造方法如下:
//resource:列表項的佈局文件
//objects:數據源
public ArrayAdapter(Context context,@LayoutRes int resource,T[] objects);
public ArrayAdapter(Context context,@LayoutRes int resource,List<T> objects)
使用ListView的示例代碼如下:
//初始化普通佈局的ListView
String[] dataArray=new String[]{"coding","ending","CodingEnding","Github","coder","Android"};//1.建立數據源
ArrayAdapter<String> normalAdapter=new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,dataArray);//2.建立適配器
normalListView.setAdapter(normalAdapter);//3.設置適配器
ArrayAdapter中使用的android.R.layout.simple_list_item_1
是系統提供的佈局文件,其實就是一個TextView。
效果截圖:
監聽點擊事件
//監聽單擊事件
normalListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.i(TAG,"當前位置:"+position);
String msg= (String) parent.getAdapter().getItem(position);//獲取選中對象
Toast.makeText(ListViewActivity.this,msg,Toast.LENGTH_SHORT).show();
}
});
//監聽長按事件
normalListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(ListViewActivity.this,"發生長按事件",Toast.LENGTH_SHORT).show();
return true;
}
});
可以看到,在監聽器中通過parent.getAdapter().getItem(position)
獲取選中對象。注意,這個方法的返回值是Object對象,因此需要進行強制轉換。
相關屬性
android:divider:設置ListView各項之間的分割線 [color或drawable資源]
android:dividerHeight:分割線的高度
android:headerDividersEnabled:是否繪製每個HeaderView後的分割線 [默認爲true]
android:footerDividersEnabled:是否繪製每個FooterView前的分割線 [默認爲true]
android:listSelector:設置列表項被選中時的效果 [color或drawable資源]
android:fastScrollEnabled:是否在快速滑動的是否顯示右側的滑動塊
android:scrollbars:設置滑動條的展示方式 [horizontal|vertical|none]
android:stackFromBottom:是否在初始狀態時顯示ListView的最底部。 [默認爲false]
stackFromBottom這個屬性需要簡單解釋一下:如果設置爲true,那麼打開ListView首先看到的就是最底部的內容,看起來就像是ListView已經滾動到了最後一行;如果設置爲false,就和默認狀態一樣,首先看到第一行的內容。
自定義列表佈局
如果需要展示的內容比較複雜(比如圖片加文字),我們就應該自定義適配器,使用自己的佈局去展示列表項。
基本步驟
首先,建立一個實體類Book:
public class Book {
private String name;
private int imageRes;//圖片資源
public Book(String name, int imageRes) {
this.name = name;
this.imageRes = imageRes;
}
@Override
public String toString() {
return name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getImageRes() {
return imageRes;
}
public void setImageRes(int imageRes) {
this.imageRes = imageRes;
}
}
接着,自定義一個佈局文件(左側圖片,右側文字),本例中命名爲listview_custom_item.xml
,代碼如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:gravity="center_vertical">
<ImageView
android:id="@+id/book_image"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_marginLeft="8dp" />
<TextView
android:id="@+id/book_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:textAllCaps="false"
android:textSize="16sp" />
</LinearLayout>
然後,通過繼承BaseAdapter
實現我們自己的適配器,本例中命名爲StyleListViewAdapter
:
public class StyleListViewAdapter extends BaseAdapter{
private Context context;
private List<Book> dataList;
public StyleListViewAdapter(Context context, List<Book> dataList) {
this.context = context;
this.dataList = dataList;
}
@Override
public int getCount() {
return dataList.size();
}
@Override
public Object getItem(int position) {
return dataList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Book book=dataList.get(position);
View view= LayoutInflater.from(context).inflate(R.layout.listview_custom_item,parent,false);
ImageView bookImageView=view.findViewById(R.id.book_image);
TextView bookNameView=view.findViewById(R.id.book_name);
bookImageView.setImageResource(book.getImageRes());
bookNameView.setText(book.getName());
return view;
}
}
可以看到,需要重寫getCount、getItem、getItemId、getView
這四個方法。此外,還要提供一個構造方法用於外界傳入Context和數據源(本例中爲List<Book>
)。
使用ViewHolder提升運行效率
在實際使用中,通常會使用ViewHolder
提升ListView的運行效率,這一方式將充分利用ListView中View的複用機制。
首先,在Adapter中建立一個靜態內部類ViewHolder:
static class ViewHolder{
ImageView bookImageView;
TextView bookNameView;
}
然後,修改Adapter中的getView
方法,複用已有的View:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Book book=dataList.get(position);
ViewHolder viewHolder;
if(convertView==null){
convertView= LayoutInflater.from(context).inflate(
R.layout.listview_custom_item,parent,false);
viewHolder=new ViewHolder();
viewHolder.bookImageView=convertView.findViewById(R.id.book_image);
viewHolder.bookNameView=convertView.findViewById(R.id.book_name);
convertView.setTag(viewHolder);//存儲ViewHolder
}else{//複用已有的View
viewHolder= (ViewHolder) convertView.getTag();
}
viewHolder.bookImageView.setImageResource(book.getImageRes());
viewHolder.bookNameView.setText(book.getName());
return convertView;
}
最後,在代碼中爲ListView設置自定義的適配器即可,代碼如下:
//初始化自定義佈局的ListView
List<Book> dataList=new ArrayList<>();
dataList.add(new Book("《小王子》",R.mipmap.ic_launcher));
dataList.add(new Book("《資本論》",R.mipmap.ic_launcher));
dataList.add(new Book("《三體》",R.mipmap.ic_launcher));
StyleListViewAdapter styleAdapter=new StyleListViewAdapter(this,dataList);
customListView.setAdapter(styleAdapter);
效果截圖:
實現多佈局列表
在實際使用中,列表項可能不止一種佈局形式,典型的如通訊錄列表就有聯繫人、標題(如A、B、C等)這兩種形式的列表項。通過對Adapter的修改,可以通過ListView實現多佈局列表。在這裏,將介紹如何實現一個簡單的多佈局列表,最終的效果如下:
準備佈局文件
在本例中,主要有兩種列表項,即標題項和內容項。因此,準備兩個對應的佈局文件,分別命名爲listview_multi_title.xml
和listview_multi_item.xml
,代碼如下:
listview_multi_title.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="6dp" />
</LinearLayout>
listview_multi_item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:gravity="center_vertical">
<ImageView
android:id="@+id/item_image"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_marginLeft="8dp" />
<TextView
android:id="@+id/item_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:textAllCaps="false"
android:textSize="16sp"
android:textColor="#000000"/>
</LinearLayout>
準備實體類
對於不同的佈局而言,應該使用不同的實體類。在本例中,有兩種列表項,因此需要兩個實體類。首先可以建立一個基類,本例中命名爲BaseMultiBean
,代碼如下:
public abstract class BaseMultiBean {
public static final int TYPE_TITLE=0;//標題項
public static final int TYPE_ITEM=1;//內容項
protected int type;//類型
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
可以看到,基類中主要是封裝了實體的類型屬性,這一屬性將用於確定要使用的列表項佈局。然後,再建立兩個繼承自基類的實體類,分別對應標題項和內容項,本例中命名爲TitleBean
和ItemBean
,代碼如下:
TitleBean
public class TitleBean extends BaseMultiBean{
private String title;
public TitleBean(String title) {
this.title = title;
this.type=TYPE_TITLE;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
ItemBean
public class ItemBean extends BaseMultiBean{
private int imageRes;//圖片資源
private String content;//內容
public ItemBean(int imageRes, String content) {
this.imageRes = imageRes;
this.content = content;
this.type=TYPE_ITEM;
}
public int getImageRes() {
return imageRes;
}
public void setImageRes(int imageRes) {
this.imageRes = imageRes;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
創建適配器
有了佈局和實體類,就可以開始着手創建適配器了,本例中命名爲MultiListViewAdapter
。和前面提到的適配器相比,還需要實現getViewTypeCount
和getItemViewType
這兩個方法。此外,getView
也需要修改,以及還要提供兩種不同的ViewHolder分別對應兩種列表項。示例代碼如下:
public class MultiListViewAdapter extends BaseAdapter{
......
@Override
public int getViewTypeCount() {//返回類型種類數
return 2;
}
@Override
public int getItemViewType(int position) {//返回當前項的類型
BaseMultiBean bean=dataList.get(position);
return bean.getType();
}
static class TitleViewHolder{//針對標題項的複用
TextView titleView;
}
static class ItemViewHolder{//針對內容項的複用
ImageView itemImageView;
TextView itemContentView;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TitleViewHolder titleViewHolder;
ItemViewHolder itemViewHolder;
switch(getItemViewType(position)){//根據Item的類型不同,執行相應的操作
case BaseMultiBean.TYPE_TITLE:
TitleBean titleBean= (TitleBean) dataList.get(position);
if(convertView==null){
convertView=inflater.inflate(R.layout.listview_multi_title,parent,false);
titleViewHolder=new TitleViewHolder();
titleViewHolder.titleView=convertView.findViewById(R.id.item_title);
convertView.setTag(titleViewHolder);
}else{
titleViewHolder= (TitleViewHolder) convertView.getTag();
}
titleViewHolder.titleView.setText(titleBean.getTitle());
break;
case BaseMultiBean.TYPE_ITEM:
ItemBean itemBean= (ItemBean) dataList.get(position);
if(convertView==null){
convertView=inflater.inflate(R.layout.listview_multi_item,parent,false);
itemViewHolder=new ItemViewHolder();
itemViewHolder.itemImageView=convertView.findViewById(R.id.item_image);
itemViewHolder.itemContentView=convertView.findViewById(R.id.item_content);
convertView.setTag(itemBean);
}else{
itemViewHolder= (ItemViewHolder) convertView.getTag();
}
itemViewHolder.itemImageView.setImageResource(itemBean.getImageRes());
itemViewHolder.itemContentView.setText(itemBean.getContent());
break;
default:break;
}
return convertView;
}
}
爲ListView設置適配器
有了前面三步的準備工作,現在就可以着手爲ListView設置適配器了,示例代碼如下:
//初始化多狀態佈局的ListView(未設置點擊監聽)
List<BaseMultiBean> multiDataList=new ArrayList<>();
multiDataList.add(new TitleBean("第一個區域"));
multiDataList.add(new ItemBean(R.mipmap.ic_launcher,"《小王子》"));
multiDataList.add(new ItemBean(R.mipmap.ic_launcher,"《獅子王》"));
multiDataList.add(new TitleBean("第二個區域"));
multiDataList.add(new ItemBean(R.mipmap.ic_launcher,"《資本論》"));
multiDataList.add(new ItemBean(R.mipmap.ic_launcher,"《三體》"));
multiDataList.add(new ItemBean(R.mipmap.ic_launcher,"《孤獨的進化者》"));
MultiListViewAdapter multiAdapter=new MultiListViewAdapter(this,multiDataList);
multiListView.setAdapter(multiAdapter);
常用技巧
設置空數據佈局
public void setEmptyView(View emptyView);//在ListView的數據爲空時顯示emptyView
首先,在ListView所在的XML文件中定義一個EmptyView的佈局,示例代碼如下:
<FrameLayout
android:id="@+id/empty_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="20sp"
android:text="暫無數據"/>
</FrameLayout>
提示: EmptyView的width
和height
屬性可以和ListView保持一致,這樣在空數據時剛好可以讓EmptyView佔據ListView的空間。
然後,在代碼中爲ListView設置EmptyView,示例代碼如下:
View view=findViewById(R.id.empty_view);
normalListView.setEmptyView(view);
效果截圖:
隱藏滾動條
只需將android:scrollbars
屬性設置爲none就可以隱藏ListView的滾動條。
去掉默認的選中顏色
只需將android:listSelector
屬性設置爲#00000000
就可以去掉默認的選中顏色(其實是設置爲透明色)。
設置入場動畫
android:layoutAnimation:爲ListView設置佈局動畫。 [使用layoutAnimation資源]
爲ListView設置layoutAnimation屬性後,ListView的所有可見項都會執行指定的動畫,有多少可見項就會執行多少次動畫。主要的使用步驟如下:
首先,在res文件夾下的anim
文件夾中建立一個set
動畫資源,本例中命名爲listview_anim.xml
,示例代碼如下:
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha android:fromAlpha="0" android:toAlpha="1" android:duration="1000"/>
<translate android:fromXDelta="1000" android:toXDelta="0" android:duration="1000"/>
</set>
這個動畫的作用是讓View從右側飛入,且有一個由淺入深的漸變效果,每個動畫持續1000ms。
然後,在anim文件夾下建立一個layoutAnimation
資源,本例中命名爲listview_layout_animation.xml
,示例代碼如下:
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:delay="0.3"
android:animationOrder="random"
android:animation="@anim/listview_anim">
</layoutAnimation>
delay
指定下一次動畫相對上一次動畫的延遲倍數,可以是0到1之間的值;animation
指定需要使用的動畫資源。animationOrder
指定子View的動畫執行順序,可選值與含義如下:
- normal:ListView的列表項順序執行動畫(從第一個可見列表項開始執行到最後一個可見列表項)
- random:ListView的列表項隨機執行動畫
- reverse:ListView的列表項逆序執行動畫(從最後一個可見列表項開始執行到第一個可見列表項)
最後,爲ListView指定對應的layoutAnimation資源即可:
<ListView
android:id="@+id/list_view_normal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layoutAnimation="@anim/listview_layout_animation"/>
提示:如果需要手動執行佈局動畫,可以調用ListView的startLayoutAnimation
方法。
效果截圖:
增加列表頭和列表尾
相關方法:
//data:爲HeaderView綁定的數據(可通過ListAdapter#getItem方法獲得)
//isSelectable:指定HeaderView是否可選中(是否觸發OnItemClickListener和OnItemLongClickListener)
public void addHeaderView(View v, Object data, boolean isSelectable);//添加指定列表頭(可調用多次,從上往下逐次添加)
public void addHeaderView(View v);//添加指定列表頭(可調用多次,從上往下逐次添加)
public int getHeaderViewsCount();//獲得列表頭的個數
public boolean removeHeaderView(View v);//移除列表頭
//data:爲FooterView綁定的數據(可通過ListAdapter#getItem方法獲得)
//isSelectable:指定FooterView是否可選中((是否觸發OnItemClickListener和OnItemLongClickListener))
public void addFooterView(View v, Object data, boolean isSelectable);//添加指定列表頭(可調用多次,從上往下逐次添加)
public void addFooterView(View v);//添加指定列表頭(可調用多次,從上往下逐次添加)
public int getFooterViewsCount();//獲得列表尾的個數
public boolean removeFooterView(View v);//移除列表尾
說明:方法中的addHeaderView(View v)
其實是通過調用addHeaderView(view, null, true)
的方式實現的。addFooterView(View v)
方法與之同理。
示例代碼:
//爲ListView添加列表頭/尾
String[] dataArray=new String[]{"coding","ending","CodingEnding","Github","coder","Android"};
ArrayAdapter<String> headerFooterAdapter=new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,dataArray);
LayoutInflater inflater=LayoutInflater.from(this);
View headerView=inflater.inflate(R.layout.listview_header,headerFooterListView,false);//實例化頭佈局
View footerView=inflater.inflate(R.layout.listview_footer,headerFooterListView,false);//實例化尾佈局
headerFooterListView.addHeaderView(headerView,"HeaderView",true);//設置列表頭可選中
headerFooterListView.addFooterView(footerView,"FooterView",false);//設置列表尾不可選中
headerFooterListView.setAdapter(headerFooterAdapter);
效果截圖:
提示: addFooterView和addHeaderView應該在ListView使用setAdapter
設置適配器前調用,否則可能出現異常。
注意:如果爲ListView設置了HeaderView或者FooterView,在OnItemClickListener
的onItemClick方法中,position可能不是我們希望取得的值(因爲算上了HeaderView和FooterView的個數)。此時,如果想要獲得選中項,應該通過AdapterView#getAdapter
獲取,示例代碼如下:
customListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Book book= (Book) parent.getAdapter().getItem(position);
}
});
parent.getAdapter()
獲取的是ListView適配器的包裝類,它的getItem
方法會排除HeaderView和FooterView的影響,返回正確的對象。
動態增加列表項
先向數據源(如ArrayList)中添加數據,然後調用ListAdapter#notifyDataSetChanged
方法通知列表刷新。示例代碼如下:
List<Book> dataList dataList=new ArrayList<>();
.....
dataList.add(new Book("《新的書籍》",R.mipmap.ic_launcher));//爲數據源新增數據
styleAdapter.notifyDataSetChanged();//通知ListView數據已更新
具體代碼請參考demo。
設置列表的滾動模式
android:transcriptMode:設置列表的滾動模式
ListView的滾動模式由transcriptMode
屬性決定,它有三種可選值,含義如下:
- disabled:列表有新的數據增加時,ListView並不發生滾動。[默認值]
- normal:如果最後一個列表項在可視範圍內,當有新的數據增加時,列表會自動滾動到底部。
- alwaysScroll:只要有新的數據增加時,列表就會自動滾動到底部。
在開發中根據實際需求選擇相應的滾動模式即可。
跳轉到指定位置
//跳轉到指定位置(讓這個Item成爲列表當前的第一個可見項)
public void setSelection(int position);
//讓HeaderView成爲列表當前的第一個可見項(如果HeaderView不存在則顯示position爲0的項)
public void setSelectionAfterHeaderView();
小技巧:如果這兩個方法在調用時無效,可以先調用ListView的clearFocus
方法。
平滑滾動到指定位置
//平滑滾動到指定位置
public void smoothScrollToPosition(int position);
//平滑滾動n個列表項的距離
//offset:需要滾動的列表項個數(offset爲正數時ListView向上滾動,爲負數時向下滾動)
public void smoothScrollByOffset(int offset);
注意: smoothScrollToPosition並不保證將指定位置的列表項顯示爲列表當前的第一個可見項,只保證這個列表項在可視範圍內。
效果截圖:
監聽滾動狀態
只需要爲ListView設置OnScrollListener
即可,示例代碼如下:
//監聽ListView的滑動狀態
normalListView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
//滑動狀態發生變化時觸發
//scrollState的可能值:[SCROLL_STATE_IDLE|SCROLL_STATE_TOUCH_SCROLL|SCROLL_STATE_FLING]
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//滑動完成時觸發
//firstVisibleItem:第一個可見項的索引值
//visibleItemCount:可見項的個數
//totalItemCount:列表項的總數
}
});
onScrollStateChanged中的scrollState
可能有三種取值,含義如下:
- SCROLL_STATE_IDLE:靜止狀態
- SCROLL_STATE_TOUCH_SCROLL:滑動狀態(用戶此時觸碰着屏幕且在滑動)
- SCROLL_STATE_FLING:慣性滑動狀態(用戶此時未觸碰屏幕,ListView藉助上一次滑動的慣性滑動)
小技巧:可在OnScrollListener的onScroll
方法中判斷ListView是否已經滑動到末尾,示例代碼如下:
@Override
public void onScroll(AbsListView view,int firstVisibleItem,int visibleItemCount,int totalItemCount) {
if(totalItemCount>0&&firstVisibleItem+visibleItemCount==totalItemCount){
//已經滾動到末尾
}
}
遍歷列表當前所有的可見元素
for(int i=0;i<normalListView.getChildCount();i++){
View view=normalListView.getChildAt(i);//可以強制轉換爲具體的View
}
常見問題
子控件搶奪焦點
問題描述:在自定義ListView列表項佈局的時候,如果列表項中包含Button、CheckBox等需要焦點的控件,就可能導致點擊列表項不起作用。
解決方案:
- 將列表項中Button、CheckBox等控件的
android:focusable
設置爲false。 - 將列表項根佈局的
android:descendantFocusability
屬性設置爲blocksDescendants。
descendantFocusability
屬性的可選值和效果說明如下:
- beforeDescendants:ViewGroup會在所有子View之前獲得焦點
- afterDescendants:ViewGroup會在所有子View之後獲得焦點
- blocksDescendants:ViewGroup會組織子View獲得焦點
上面兩種解決方案任選一種即可。
異步加載時圖片顯示錯位
問題描述:如果列表項中的圖片需要異步加載,由於ListView的View複用機制,在圖片下載完畢時原來的ImageView可能已被複用,就可能導致圖片顯示錯位。
解決方案:首先調用setTag
方法爲列表項中的ImageView設置標籤。在異步加載完畢後,通過ListView的findViewWithTag
方法查找ImageView。如果ImageView已經被複用了,這個方法的返回值就是null。通過判斷這個方法的返回值是否爲null,決定是否爲ImageView設置圖片資源,就可以解決圖片顯示錯位的問題。示例代碼如下:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
.....
imageView.setTag(imageUrl);//將圖片地址作爲ImageView的Tag
.....
}
ImageView imageView=listView.findViewWithTag(imageUrl);
if(imageView!=null){
imageView.setImageDrawable(drawable);
}
更多博客
《 Android UI GridView講解》:詳細講解GridView的使用方法和常用技巧。
《 Android UI 常用控件講解》:包括CheckBox、RadioButton、ToggleButton、Switch、ProgressBar、SeekBar、RatingBar、Spinner、ImageButton。
demo下載地址
參考資料
http://gundumw100.iteye.com/blog/1169065
http://blog.csdn.net/guolin_blog/article/details/45586553
http://blog.csdn.net/yangshangwei/article/details/50322919
http://blog.csdn.net/csdn_aiyang/article/details/70739945
http://blog.csdn.net/zhuwentao2150/article/details/52425334
http://blog.csdn.net/quwei3930921/article/details/51013012