[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即可了。

 

本文转自天天_byconan博客园博客,原文链接:http://www.cnblogs.com/tiantianbyconan/p/3992843.html ,如需转载请自行联系原作者
相关文章
|
4月前
|
开发工具 Android开发 开发者
Android平台如何不推RTMP|不发布RTSP流|不实时录像|不回传GB28181数据时实时快照?
本文介绍了一种在Android平台上实现实时截图快照的方法,尤其适用于无需依赖系统接口的情况,如在RTMP推送、RTSP服务或GB28181设备接入等场景下进行截图。通过底层模块(libSmartPublisher.so)实现了截图功能,封装了`SnapShotImpl.java`类来管理截图流程。此外,提供了关键代码片段展示初始化SDK实例、执行截图、以及在Activity销毁时释放资源的过程。此方案还考虑到了快照数据的灵活处理需求,符合GB/T28181-2022的技术规范。对于寻求更灵活快照机制的开发者来说,这是一个值得参考的设计思路。
|
2月前
|
存储 大数据 数据库
Android经典面试题之Intent传递数据大小为什么限制是1M?
在 Android 中,使用 Intent 传递数据时存在约 1MB 的大小限制,这是由于 Binder 机制的事务缓冲区限制、Intent 的设计初衷以及内存消耗和性能问题所致。推荐使用文件存储、SharedPreferences、数据库存储或 ContentProvider 等方式传递大数据。
78 0
|
4月前
|
前端开发 JavaScript UED
element-ui 表格数据究竟隐藏着怎样的神秘样式与格式化技巧?快来揭开谜底!
【8月更文挑战第22天】《element-ui 表格数据样式及格式化案例》展示了如何利用 element-ui 的表格组件实现美观且易读的数据展示。通过简单配置,可以自定义表格样式,如边框、背景色等,并通过 formatter 实现数据格式化,例如将成绩保留一位小数。此外,还能依据条件设置行样式,如成绩达优则高亮显示,从而增强用户体验和数据可读性。
68 1
|
4月前
|
JSON Java Android开发
Android 开发者必备秘籍:轻松攻克 JSON 格式数据解析难题,让你的应用更出色!
【8月更文挑战第18天】在Android开发中,解析JSON数据至关重要。JSON以其简洁和易读成为首选的数据交换格式。开发者可通过多种途径解析JSON,如使用内置的`JSONObject`和`JSONArray`类直接操作数据,或借助Google提供的Gson库将JSON自动映射为Java对象。无论哪种方法,正确解析JSON都是实现高效应用的关键,能帮助开发者处理网络请求返回的数据,并将其展示给用户,从而提升应用的功能性和用户体验。
103 1
|
4月前
|
缓存 API Android开发
Android经典实战之Kotlin Flow中的3个数据相关的操作符:debounce、buffer和conflate
本文介绍了Kotlin中`Flow`的`debounce`、`buffer`及`conflate`三个操作符。`debounce`过滤快速连续数据,仅保留指定时间内的最后一个;`buffer`引入缓存减轻背压;`conflate`仅保留最新数据。通过示例展示了如何在搜索输入和数据流处理中应用这些操作符以提高程序效率和用户体验。
54 6
|
4月前
|
编解码 开发工具 Android开发
Android平台RTSP|RTMP播放器如何实现TextureView渲染
本文介绍了在Android平台上使用TextureView进行RTSP和RTMP视频流渲染的技术背景和实现方法。TextureView相较于SurfaceView具备更高性能、更强功能性和更灵活的绘制方式等优势,但也有必须在硬件加速环境下运行和较高内存占用等局限。文中详细展示了如何在SmartPlayerV2工程中创建和配置TextureView,并通过代码示例解释了如何根据视频分辨率信息调整显示比例,以及处理TextureView的各种生命周期回调。此外,还列举了该播放器SDK支持的多项高级功能,如多实例播放、多种编码格式支持、硬解码能力等,旨在帮助开发者更好地理解和实现高性能的直播播放器。
|
4月前
|
编解码 网络协议 前端开发
如何实现Android平台GB28181设备接入模块按需打开摄像头并回传数据
后台采集摄像头,如果想再进一步扩展,可以把android平台gb28181的camera2 demo,都移植过来,实现功能更强大的国标设备侧,这里主要是展示,收到国标平台侧的回传请求后,才打开摄像头,才开始编码打包,最大限度的减少资源的占用
|
4月前
|
编解码 网络协议 Android开发
Android平台GB28181设备接入模块实现后台service按需回传摄像头数据到国标平台侧
我们在做Android平台GB28181设备对接模块的时候,遇到这样的技术需求,开发者希望能以后台服务的形式运行程序,国标平台侧没有视频回传请求的时候,仅保持信令链接,有发起视频回传请求或语音广播时,打开摄像头,并实时回传音视频数据或接收处理国标平台侧发过来的语音广播数据。
|
4月前
|
算法 数据处理 开发工具
Android平台RTSP|RTMP播放器如何回调YUV或RGB数据
在开发Android平台上的RTSP或RTMP播放器时,开发者不仅追求低延迟播放,还希望获取解码后的视频数据(如YUV或RGB格式),以便进行视觉算法分析。使用大牛直播SDK中的SmartPlayer,可在确保播放流畅的同时,通过设置外部渲染器(`SmartPlayerSetExternalRender`)来高效地回调原始视频数据。例如,对于RGBA数据,需实现`NTExternalRender`接口,并重写相关方法以处理数据和尺寸变化。同样地,对于I420(YUV)数据,也需要相应地实现接口以满足需求。这种方式使得开发者能在不影响常规播放功能的情况下,进行定制化的视频处理任务。
|
4月前
|
存储 缓存 Java
Android项目架构设计问题之优化业务接口数据的加载效率如何解决
Android项目架构设计问题之优化业务接口数据的加载效率如何解决
47 0