[Android]使用AdapterTypeRender對不同類型的item數據到UI的渲染


以下內容為原創,轉載請注明:

來自天天博客: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即可了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM