以下內容為原創,轉載請注明:
來自天天博客:http://www.cnblogs.com/tiantianbyconan/p/3992843.html
本文講的工具均放在AndroidBucket開源項目中,歡迎大家star/fork,地址:https://github.com/wangjiegulu/AndroidBucket
主要實現聊天功能中的發送不同類型的信息,比如純文本、圖片、語音、圖文混排多媒體的數據等(具體效果看微信)。
這里使用AdapterTypeRender在BaseTypeAdapter(這個之后會講到)中實現。
這里主要的實現方式是在ChatAdapter(繼承BaseTypeAdapter)中根據每個position的item的type,來使用不同的AdapterTypeRender渲染器進行渲染。渲染的過程當然是在getView方法中進行。
1. AdapterTypeRender
先來看看AdapterTypeRender這個接口。它有3個方法:getConvertView()、fitEvents()、fitDatas()三個方法。
package com.wangjie.androidbucket.adapter; import android.view.View; /** * 用於對不同類型item數據到UI的渲染 * Author: wangjie * Email: tiantian.china.2@gmail.com * Date: 9/14/14. */ public interface AdapterTypeRender { /** * 返回一個item的convertView,也就是BaseAdapter中getView方法中返回的convertView * @return */ View getConvertView(); /** * 填充item中各個控件的事件,比如按鈕點擊事件等 */ void fitEvents(); /** * 對指定position的item進行數據的適配 * @param position */ void fitDatas(int position); }
-getconvertView()方法用於返回給BaseTypeAdapter一個convertView,一個AdapterTypeRender實現類對應一個convertView實例,該AdapterTypeRender可以被重用,所以convertView也可以被重用了。
-fitEvents()方法用於給當前的item中的各個控件注冊事件,比如點擊事件、touch事件等(具體的注冊事件后面回講到),因為這個方法是在getView中只有convertView為null時才會調用,所以只會調用一次,所以在這里添加事件是比較好的。
-fitDatas()方法用於把數據適配到item的各個view中進行顯示。這個方法只要getView得到調用,就會被調用。
2. BaseTypeAdapter
這是一個抽象類,是繼承於BaseAdapter的,重寫了里面的getView方法。會自動根據指定position的item獲取對應的type,然后通過type實例化一個AdapterTypeRender的實現,然后又使用了BaseAdapter中自帶的convertView的重用機制進行對view的重用,同樣也是對AdapterTypeRender的重用。
package com.wangjie.androidbucket.adapter.typeadapter; import android.annotation.TargetApi; import android.os.Build; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import com.wangjie.androidbucket.R; /** * Author: wangjie * Email: tiantian.china.2@gmail.com * Date: 9/25/14. */ public abstract class BaseTypeAdapter extends BaseAdapter{ @TargetApi(Build.VERSION_CODES.DONUT) @Override public View getView(int position, View convertView, ViewGroup parent) { AdapterTypeRender typeRender; if(null == convertView){ typeRender = getAdapterTypeRender(position); convertView = typeRender.getConvertView(); convertView.setTag(R.id.ab__id_adapter_item_type_render, typeRender); typeRender.fitEvents(); }else{ typeRender = (AdapterTypeRender) convertView.getTag(R.id.ab__id_adapter_item_type_render); } convertView.setTag(R.id.ab__id_adapter_item_position, position); if(null != typeRender){ typeRender.fitDatas(position); } return convertView; } /** * 根據指定position的item獲取對應的type,然后通過type實例化一個AdapterTypeRender的實現 * @param position * @return */ public abstract AdapterTypeRender getAdapterTypeRender(int position); }
為了實現AdapterTypeRender的重用,一旦生成了一個AdapterTypeRender實現類的實例,則使用setTag的方法進行對convertView和AdapterTypeRender的綁定(R.id.ab__id_adapter_item_type_render這個id是在AndroidBucket中定義了的),這個可以參考以前的ViewHolder的寫法。
為了實現在同一個item中的事件(這里以view的點擊事件為例)響應都共用一個觀察者的實例,需要在convertView中保存對應的position。這是因為同一個convertView因為使用了view的重用,是被非顯示頁面的很多個item所共用的。所以只需要,在當前顯示的一屏中,每個convertView對應的postion即可,畢竟這些事件觸發只有在當前顯示的一屏中才會被觸發。保存postion的方式依然使用了setTag的方式(R.id.ab__id_adapter_item_position這個id是在AndroidBucket中定義了的),注意:子類一般不需要重寫getView方法了,其他的數據適配UI渲染都交給Render吧!
除了重寫了getView方法之外,還定義了一個抽象方法:getAdapterTypeRender。這個方法需要子類去實現,需要告訴BaseTypeAdapter,指定position的item,它的type對應的AdapterTypeRender的實例是什么。
3. 自定義AdapterTypeRender的實現
接下來,就嘗試自己實現幾個不同布局的Render吧。這里假設需要實現兩種:文本布局(TypeTextRender)、圖片布局(TypeImageRender)。
a) TypeTextRender的實現:
package com.wangjie.activities.typerendertest.adapter; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import com.wangjie.androidbucket.adapter.typeadapter.AdapterTypeRender; import com.wangjie.androidbucket.adapter.listener.OnConvertViewClickListener; import com.wangjie.androidbucket.thread.ThreadPool; import com.wangjie.androidbucket.utils.ABTimeUtil; import com.wangjie.androidbucket.utils.ABViewUtil; import com.wangjie.imageloadersample.imageloader.ImageLoader; /** * Author: wangjie * Email: tiantian.china.2@gmail.com * Date: 9/14/14. */ public class TypeTextRender implements AdapterTypeRender { private Context context; private ChatAdapter adapter; private View contentView; public TypeTextRender(Context context, ChatAdapter adapter) { this.context = context; this.adapter = adapter; // 解析文本類型的布局 contentView = LayoutInflater.from(context).inflate(R.layout.item_type_text, null); } @Override public View getConvertView() { // 返回文本類型的布局 return contentView; } /** * 這個方法同一個convertView只會被調用一次,所以可以放心地在這里執行事件地綁定,不用擔心生成過多的OnClickListener等 */ @Override public void fitEvents() { /** * 生成一個在convertView中使用的clickListener */ OnConvertViewClickListener onConvertViewClickListener = new OnConvertViewClickListener(contentView, R.id.ab__id_adapter_item_position) { @Override public void onClickCallBack(View registedView, int... positionIds) { ChatAdapter.OnChatItemListener onChatItemListener = adapter.getOnChatItemListener(); switch (registedView.getId()) { case R.id.item_type_text_view: if (null != onChatItemListener && null != positionIds && positionIds.length > 0) { onChatItemListener.onItemClicked(positionIds[0]); } break; case R.id.item_type_text_head_iv: if (null != onChatItemListener && null != positionIds && positionIds.length > 0) { onChatItemListener.onHeadClicked(positionIds[0]); } break; } } }; // 通過ABViewUtil從contentView中獲取對應id的控件,然后設置OnClickListener ABViewUtil.obtainView(contentView, R.id.item_type_text_view) .setOnClickListener(onConvertViewClickListener); ABViewUtil.obtainView(contentView, R.id.item_type_text_head_iv) .setOnClickListener(onConvertViewClickListener); } private ImageView headIv; private View rootView; private TextView contentTv; @Override public void fitDatas(int position) { // 通過ABViewUtil從contentView中獲取對應id的控件,然后設置OnClickListener headIv = ABViewUtil.obtainView(contentView, R.id.item_type_text_head_iv); contentTv = ABViewUtil.obtainView(contentView, R.id.item_type_text_content_tv); /** * 在這里適配數據到ui */ Message message = adapter.getItem(position); contentTv.setText(message.getContent()); ImageLoader.getInstances().displayImage(message.getHeadUrl(), headIv, 100, null, R.drawable.default_head); } }
如上代碼,該TypeTextRender實現了AdapterTypeRender接口,實現了其中的3個方法。注意:需要在構造方法中解析出convertView,用於提供給BaseTypeAdapter在getView中返回。
然后在fitEvents中注冊各種事件,這里只注冊了點擊事件(一個rootView、一個headIv注冊了點擊事件)。這里的OnConvertViewClickListener是一個實現了View.OnClickListener的一個抽象類,生成一個OnConvertViewClickListener時需要傳入convertView和positions的id,這樣由於Render被重用后,convertView也是被重用了,導致onClickListener也是被重用了,這會導致響應點擊事件的時候,回調的onClicked方法中無法得知點擊的是哪個View,所以,需要把converView和positions的id傳入,positions的id可以用來在convertView中綁定postion作為tag。positonsIds是一個可變長的參數,因為可能是一個ExpandableListView,需要groupPosition和childPosition兩個positon來確定。回調的onClickCallBack中的參數registedView表示被點擊的view,positionIds,被點擊的item的positions(這個也是可以在AndroidBucket中找到,以后有時間會針對這個詳細說明)。
“ABViewUtil.obtainView...”這個方法對viewHolder進行了封裝,把convertView中的控件都緩存在了一個SparseArray<View>中(作用跟常用的ViewHolder相同)。
然后在fitDatas方法中就可以進行對數據的適配了,相當於我們以前在BaseAdapter的getView方法中的操作了。
b) TypeImageRender的實現(與TypeTextRender大同小異,不做過多的說明了):
package com.wangjie.activities.typerendertest.adapter; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import com.wangjie.androidbucket.adapter.typeadapter.AdapterTypeRender; import com.wangjie.androidbucket.adapter.listener.OnConvertViewClickListener; import com.wangjie.androidbucket.thread.ThreadPool; import com.wangjie.androidbucket.utils.ABTimeUtil; import com.wangjie.androidbucket.utils.ABViewUtil; import com.wangjie.imageloadersample.imageloader.ImageLoader; /** * Author: wangjie * Email: tiantian.china.2@gmail.com * Date: 9/14/14. */ public class ChatTypeImageRender implements AdapterTypeRender { private Context context; private ChatAdapter adapter; private View contentView; public ChatTypeImageRender(Context context, ChatAdapter adapter) { this.context = context; this.adapter = adapter; // 解析圖片類型的布局 contentView = LayoutInflater.from(context).inflate(R.layout.item_type_text, null); } @Override public View getConvertView() { // 返回文本類型的布局 return contentView; } /** * 這個方法同一個convertView只會被調用一次,所以可以放心地在這里執行事件地綁定,不用擔心生成過多的OnClickListener等 */ @Override public void fitEvents() { /** * 生成一個在convertView中使用的clickListener */ OnConvertViewClickListener onConvertViewClickListener = new OnConvertViewClickListener(contentView, R.id.ab__id_adapter_item_position) { @Override public void onClickCallBack(View registedView, int... positionIds) { ChatAdapter.OnChatItemListener onChatItemListener = adapter.getOnChatItemListener(); switch (registedView.getId()) { case R.id.item_type_text_view: if (null != onChatItemListener && null != positionIds && positionIds.length > 0) { onChatItemListener.onItemClicked(positionIds[0]); } break; case R.id.item_type_text_head_iv: if (null != onChatItemListener && null != positionIds && positionIds.length > 0) { onChatItemListener.onHeadClicked(positionIds[0]); } break; case R.id.item_type_text_content_iv: if (null != onChatItemListener && null != positionIds && positionIds.length > 0) { onChatItemListener.onImageClicked(positionIds[0]); } break; } } }; // 通過ABViewUtil從contentView中獲取對應id的控件,然后設置OnClickListener ABViewUtil.obtainView(contentView, R.id.item_type_text_view) .setOnClickListener(onConvertViewClickListener); ABViewUtil.obtainView(contentView, R.id.item_type_text_head_iv) .setOnClickListener(onConvertViewClickListener); ABViewUtil.obtainView(contentView, R.id.item_type_text_content_iv) .setOnClickListener(onConvertViewClickListener); } private ImageView headIv; private View rootView; private ImageView contentIv; @Override public void fitDatas(int position) { // 通過ABViewUtil從contentView中獲取對應id的控件,然后設置OnClickListener headIv = ABViewUtil.obtainView(contentView, R.id.item_type_text_head_iv); contentIv = ABViewUtil.obtainView(contentView, R.id.item_type_text_content_iv); /** * 在這里適配數據到ui */ Message message = adapter.getItem(position); ImageLoader.getInstances().displayImage(message.getHeadUrl(), headIv, 100, null, R.drawable.default_head); ImageLoader.getInstances().displayImage(message.getContentUrl(), headIv, 100, null, R.drawable.default_pic); } }
3. BaseTypeAdapter的實現
到這里,我們已經定義好了各種type的Render了,現在需要在Adapter中去使用它,方法之前講過,只要繼承BaseTypeAdapter,然后實現里面的getAdapterTypeRender方法即可:
package com.wangjie.activities.typerendertest.adapter; import android.content.Context; import com.wangjie.androidbucket.adapter.typeadapter.AdapterTypeRender; import com.wangjie.androidbucket.adapter.typeadapter.BaseTypeAdapter; import java.util.List; /** * Author: wangjie * Email: tiantian.china.2@gmail.com * Date: 9/14/14. */ public class MessageAdapter extends BaseTypeAdapter { public static interface OnChatItemListener{ void onImageClicked(int position); void onHeadClicked(int position); void onItemClicked(int position); } private OnChatItemListener onChatItemListener; public void setOnChatItemListener(OnChatItemListener onChatItemListener) { this.onChatItemListener = onChatItemListener; } public OnChatItemListener getOnChatItemListener() { return onChatItemListener; } private Context context; private List<Message> list; public List<Message> getList() { return list; } public MessageAdapter(Context context, List<Message> list) { this.context = context; this.list = list; } @Override public int getCount() { return list.size(); } @Override public DoctorFriendMessageViewModelProxy getItem(int position) { return list.get(position); } @Override public long getItemId(int position) { return position; } @Override public int getItemViewType(int position) { return list.get(position).getTyp(); } @Override public int getViewTypeCount() { return 2; } @Override public AdapterTypeRender getAdapterTypeRender(int position){ AdapterTypeRender typeRender = null; switch(getItemViewType(position)){ case MessageConstants.MessageType.IMAGE: typeRender = new ChatTypeImageRender(context, this); break; case MessageConstants.MessageType.TEXT: default: typeRender = new ChatTypeTextRender(context, this); break; } return typeRender; } }
如上代碼所示,通過實現getAdapterTypeRender來獲取對應類型的Render即可了。
