Android教你如何一步步打造通用介面卡
前言
在Android開發中ListView是最為常用的控制元件之一,基本每個應用都會涉及到它,要使用ListView列表展示,就不可避免地涉及到另外一個東西——Adapter,我們都知道,Adapter是連線資料和列表介面的一個橋樑,一般專案中一個listview就會有一個Adapter與之對應,然後就是一堆方法的重寫,包括getCount,getItem,getView等等,遇到自定義佈局時還需重寫getView方法,重寫getView的時候邏輯不復雜還好,遇到程式碼邏輯複雜的時候adapter簡直臃腫,並且還需要寫很多次重複的程式碼,比如判斷convertView是否為空,findViewById無數次停不下來
寫了這麼多,你是否想過,可否有一個公用的自定義Adapter基類,將這些經常重複的程式碼和邏輯封裝起來,方便我們呼叫,減少getView中的程式碼邏輯,下面就來一步步將其“包裝”起來成為我們想要的效果。
先走一遍我們之前寫ListView和Adapter的方式:
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <ListView android:id="@+id/listview" android:layout_width="match_parent" android:layout_height="match_parent"> </ListView> </RelativeLayout>
MainActivity:
public class MainActivity extends Activity { private ListView listview; private MyAdapter adapter; private List<String> data; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listview = (ListView)this.findViewById(R.id.listview); getData(); adapter = new MyAdapter(this.getApplicationContext(), data); listview.setAdapter(adapter); } public void getData(){ data = new ArrayList<String>(); for(int i=0; i<20; i++){ data.add("資料"+i); } } }
自定義介面卡 MyAdapter:
public class MyAdapter extends BaseAdapter{
private Context mContext;
private List<String> list;
public MyAdapter(Context context, List<String> list){
this.mContext = context;
this.list = list;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return list.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return list.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder viewholder = null;
if(convertView == null){
convertView = View.inflate(mContext, R.layout.item_listview, null);
viewholder = new ViewHolder();
viewholder.titleTv = (TextView)convertView.findViewById(R.id.titleTv);
convertView.setTag(viewholder);
}
else{
viewholder = (ViewHolder)convertView.getTag();
}
viewholder.titleTv.setText(list.get(position));
return convertView;
}
public static class ViewHolder{
TextView titleTv;
}
}
每個列表項的佈局檔案 item_listview.xml:
<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:id="@+id/titleTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:paddingLeft="15dp"
android:textSize="20sp"
android:textColor="#000"
/>
</LinearLayout>
對於以上這部分程式碼不太明白可以參考我之前的文章ListView基礎篇,ListView優化篇,通過以上程式碼就可以完成一個簡單的列表介面和資料展示:
接下來我們就在這個基礎上進行一步步封裝:
可以看到,我們每次呼叫getView時候,都會新建一個ViewHolder,然後判斷convertView是否為空,以及用
viewholder儲存我們每個列表項的子控制元件,再通過setTag和getTag來複用Viewholder,這一部分邏輯是每次getView
都會呼叫的,所以首先能夠想到將ViewHolder物件的邏輯給封裝起來,封裝ViewHolder首先要考慮以下幾點:
封裝ViewHolder
1.首先這個封裝來肯定要有一個ViewHolder的構造方法,另外,從上面可以看出每次getView都需要初始化convertView,那麼我們可以將convertView的初始化搬到ViewHold的構造方法中來進行,既然convertView要在ViewHolder的構造方法中初始化,那麼必定還需要inflate所需要的引數,以及每一個Item的下標,即context、layoutId、ViewGroup、position:
public class CommonViewHolder {
public View mConvertView;
public CommonViewHolder(Context context, int position, int layoutId, ViewGroup parent){
mConvertView = View.inflate(context, layoutId, null);
mConvertView.setTag(this);
}
}
2.注意到以前的方式每次都需要判斷convertView是否為null,是則new一個新的convertView和ViewHolder例項並且setTag,否則採用getTag重用之前的ViewHolder:
public static CommonViewHolder get(Context context, View convertView, int position, int layoutId, ViewGroup parent){
if(convertView == null){
return new CommonViewHolder(context, position, layoutId, parent);
}
else{
return (CommonViewHolder)convertView.getTag();
}
}
3.不同場景下列表項的元素是不確定的,數量和型別都不一致,既然是打造通用Adapter,那肯定要相容多種情況,數量上我們可以想到使用Map來儲存我們的子控制元件,型別上可以使用Java的泛型來構造,如下:
private HashMap<Integer, View> map = new HashMap<Integer, View>();
public <T extends View> T getView(int viewId){
View view = map.get(viewId);
//如果view為空,則findId找到,並放進map中
if(view == null){
view = mConvertView.findViewById(viewId);
map.put(viewId, view);
}
//如果view不會空,則直接返回
return (T)view;
}
4.當然,以前我們getView方法最後返回的是一個convertView,所以還可以提供一個getConvertView的方法返回每一行對應的convertView:
public View getConvertView(){
return mConvertView;
}
至此,完整的通用ViewHolder已打造完畢,完整程式碼如下:
public class CommonViewHolder {
public HashMap<Integer, View> map;
public View mConvertView;
public CommonViewHolder(Context context, int position, int layoutId, ViewGroup parent){
map = new HashMap<Integer, View>();
mConvertView = View.inflate(context, layoutId, null);
mConvertView.setTag(this);
}
public static CommonViewHolder get(Context context, View convertView, int position, int layoutId, ViewGroup parent){
if(convertView == null){
return new CommonViewHolder(context, position, layoutId, parent);
}
else{
return (CommonViewHolder)convertView.getTag();
}
}
public <T extends View> T getView(int viewId){
View view = map.get(viewId);
if(view == null){
view = mConvertView.findViewById(viewId);
map.put(viewId, view);
}
return (T)view;
}
public View getConvertView(){
return mConvertView;
}
}
如今,我們Adapter中的getView也就變成了這個樣子:
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
CommonViewHolder holder = CommonViewHolder.get(mContext, convertView, position, R.layout.item_listview, parent);
TextView titleTv = holder.getView(R.id.titleTv);
titleTv.setText(list.get(position));
return holder.getConvertView();
}
它的程式碼執行流程如下:
1.首先進入get方法獲取一個ViewHolder例項,如果convertView為空,則進入到構造方法,new一個用來存放這一行的map集合,inflate一個新的View,並且給它setTag,如果convertView不為空,則直接通過getTag獲得ViewHolder例項
2.接著呼叫holder.getView,傳入控制元件ID,如果在該map中還未有過,則通過findViewById找到控制元件,並存放進該行的View集合中,如果已經存在,則可以進行View的複用,即直接map.get(viewId);
3.最後呼叫getConvertView,獲得我們已經處理好的convertView例項
封裝Adapter
上面我們對ViewHolder進行了封裝,讓adapter的getView方法大大簡化,接下來開始封裝我們的Adapter
封裝Adapter成為公共類,我們需要注意以下問題:
平時我們寫Adapter的時候資料型別總是不一樣的,比如一會兒是一個User列表,一會兒是一個Car列表,傳進來的資料來源的型別一般是不一樣的,那如何做到不管傳進來什麼型別都能使用呢?是的沒錯,又是通過泛型來解決:
public abstract class CommonAdapter<T> extends BaseAdapter{
public Context mContext;
public List<T> list;
public CommonAdapter(Context context, List<T> list){
this.mContext = context;
this.list = list;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return list.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return list.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
CommonViewHolder holder = CommonViewHolder.get(mContext, convertView, position, R.layout.item_listview, parent);
TextView titleTv = holder.getView(R.id.titleTv);
titleTv.setText(list.get(position));
return holder.getConvertView();
}
}
可以看到,我們將Adapter的資料型別代替為泛型的形式,並且我們定義的CommonAdapter為一個抽象類,這樣做的原因是在基類先將getCount、getItem等方法給實現了,然後以後的具體Adapter類就只需要繼承該CommonAdapter,但是其實開發中我們的getView是每次的操作都是各有所異的,不可能定死,而且仔細看你會發現getView中的生成holder例項的程式碼和返回convertView例項的程式碼是千篇一律,區別只在於傳進來的item的佈局id以及控制元件的生成不一樣罷了,所以可以將這部分不一樣的提取出來放在一個抽象方法中,留給子類去實現,將
MainActivity中:CommonAdapter修改如下:
public abstract class CommonAdapter<T> extends BaseAdapter{
public Context mContext;
public List<T> list;
public int layoutId;
public CommonAdapter(Context context, List<T> list, int layoutId){
this.mContext = context;
this.list = list;
this.layoutId = layoutId;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return list.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return list.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
CommonViewHolder holder = CommonViewHolder.get(mContext, convertView, position, layoutId, parent);
convert(holder, list.get(position), position);
return holder.getConvertView();
}
//這個就是留給具體Adapter實現的方法
public abstract void convert(CommonViewHolder viewHolder, T data, int position);
}
至此,我們的通用Adapter打造完畢,接下來我們來看看實踐效果:
例子1
先定義一個用於純文字顯示的ListView的Adapter類:
public class TextListViewAdapter extends CommonAdapter<String>{
public TextListViewAdapter(Context context, List<String> list) {
super(context, list);
// TODO Auto-generated constructor stub
}
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
CommonViewHolder holder = CommonViewHolder.get(mContext, convertView, position, R.layout.item_listview, parent);
TextView titleTv = holder.getView(R.id.titleTv);
titleTv.setText(list.get(position));
return holder.getConvertView();
}
}
MainActivity中:
public class MainActivity extends Activity {
private ListView listview;
private CommonAdapter adapter;
private List<String> data;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listview = (ListView)this.findViewById(R.id.listview);
initData();
adapter = new TextListViewAdapter(this.getApplicationContext(), data);
listview.setAdapter(adapter);
}
public void initData(){
data = new ArrayList<String>();
for(int i=0; i<20; i++){
data.add("資料"+i);
}
}
}
可以看到,Adapter的程式碼比以前省去了好多,執行後效果:
例子2
我們再試試多控制元件的情況,將item_listview佈局檔案更改如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginTop="15dp"
android:layout_marginBottom="15dp"
android:orientation="horizontal"
>
<ImageView
android:id="@+id/item_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_launcher"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/titleTv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:gravity="bottom"
android:paddingTop="10dp"
android:paddingBottom="0dp"
android:paddingLeft="15dp"
android:textSize="15sp"
android:textColor="#000"
android:text="標題"
/>
<TextView
android:id="@+id/detailTv"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="2"
android:gravity="top"
android:paddingTop="0dp"
android:paddingBottom="10dp"
android:paddingLeft="15dp"
android:textSize="12sp"
android:textColor="#676767"
android:text="詳細內容"
/>
</LinearLayout>
</LinearLayout>
MainActivity中:
public class MainActivity extends Activity {
private ListView listview;
private CommonAdapter adapter;
private List<ItemBean> data;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listview = (ListView)this.findViewById(R.id.listview);
initData();
adapter = new TextImgAdapter(this.getApplicationContext(), data, R.layout.item_listview);
listview.setAdapter(adapter);
}
public void initData(){
data = new ArrayList<ItemBean>();
for(int i=0; i<20; i++){
ItemBean bean = new ItemBean(R.drawable.ic_launcher, "標題"+i, "詳細內容"+i);
data.add(bean);
}
}
}
可以看到,做了些更改,資料型別更改為了我們自定義的bean,bean中有三個屬性,分別每個ListViewItem中的頭像、標題、內容
ItemBean類:
public class ItemBean {
private int imgid;
private String title;
private String detail;
public ItemBean() {
super();
// TODO Auto-generated constructor stub
}
public ItemBean(int imgid, String title, String detail) {
super();
this.imgid = imgid;
this.title = title;
this.detail = detail;
}
public int getImgid() {
return imgid;
}
public void setImgid(int imgid) {
this.imgid = imgid;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
}
最後再看看我們的Adapter,由於現在的item佈局多了一個ImageView以及一個TextView,所以我們的Adapter相應得變成如下:
public class TextImgAdapter extends CommonAdapter<ItemBean>{
public TextImgAdapter(Context context, List<ItemBean> list, int layoutId) {
super(context, list, layoutId);
// TODO Auto-generated constructor stub
}
@Override
public void convert(CommonViewHolder viewHolder, ItemBean data, int position) {
// TODO Auto-generated method stub
ImageView item_iv = viewHolder.getView(R.id.item_iv);
TextView titleTv = viewHolder.getView(R.id.titleTv);
TextView detailTv = viewHolder.getView(R.id.detailTv);
item_iv.setBackgroundResource(R.drawable.ic_launcher);
titleTv.setText(data.getTitle());
detailTv.setText(data.getDetail());
}
}
只是多了幾行控制元件的生成以及設定值,清晰了很多有木有~~以後有再多的元素,依然只需先生成對應的例項,然後set值,一目瞭然。
執行結果:
成功實現我們的效果,媽媽再也不用擔心我寫Adapter寫到廢寢忘食.........