zoukankan      html  css  js  c++  java
  • [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即可了。

  • 相关阅读:
    权限管理UI
    NET Core 2.0 介绍和使用
    Spring MVC Integration,Spring Security
    微服务系统中的认证策略
    使用git建立远程仓库,让别人git clone下来
    回顾2016,展望2017
    spring security之httpSecurity 专题
    Android VelocityTracker简介
    Android-onInterceptTouchEvent()和onTouchEvent()总结
    两分钟彻底让你明白Android中onInterceptTouchEvent与onTouchEvent(图文)!
  • 原文地址:https://www.cnblogs.com/tiantianbyconan/p/3992843.html
Copyright © 2011-2022 走看看