[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 ,如需转载请自行联系原作者
相关文章
|
21天前
|
消息中间件 安全 数据处理
Android为什么不能在子线程更新UI
Android为什么不能在子线程更新UI
25 0
|
1月前
使用 SAP UI5 Event Bus 机制,修复 SAP UI5 分页显示数据的一个 bug 试读版
使用 SAP UI5 Event Bus 机制,修复 SAP UI5 分页显示数据的一个 bug 试读版
20 0
|
7天前
|
Android开发 开发者
Android网络和数据交互: 请解释Android中的AsyncTask的作用。
Android&#39;s AsyncTask simplifies asynchronous tasks for brief background work, bridging UI and worker threads. It involves execute() for starting tasks, doInBackground() for background execution, publishProgress() for progress updates, and onPostExecute() for returning results to the main thread.
9 0
|
7天前
|
网络协议 安全 API
Android网络和数据交互: 什么是HTTP和HTTPS?在Android中如何进行网络请求?
HTTP和HTTPS是网络数据传输协议,HTTP基于TCP/IP,简单快速,HTTPS则是加密的HTTP,确保数据安全。在Android中,过去常用HttpURLConnection和HttpClient,但HttpClient自Android 6.0起被移除。现在推荐使用支持TLS、流式上传下载、超时配置等特性的HttpsURLConnection进行网络请求。
8 0
|
10天前
|
编解码 Android开发 UED
安卓UI/UX设计原则:打造引人入胜的用户体验
【4月更文挑战第13天】本文探讨了安卓UI/UX设计的关键原则,包括一致性、简洁性、反馈、清晰性、效率和适应性。一致性要求视觉和行为保持一致,利用系统UI;简洁性减少用户行动,简化导航;反馈需即时且明确;清晰性强调表达清晰,布局有序;效率关注性能优化和任务简化;适应性涉及多设备适配和用户多样性。遵循这些原则,可创建出色应用,提供无缝用户体验。设计应持续迭代,适应技术发展和用户需求。
|
11天前
【UI】 element ui 表格没有数据时用--填充
【UI】 element ui 表格没有数据时用--填充
19 2
|
13天前
|
XML 移动开发 Android开发
构建高效安卓应用:采用Jetpack Compose实现动态UI
【4月更文挑战第10天】 在现代移动开发中,用户界面的流畅性和响应性对于应用的成功至关重要。随着技术的不断进步,安卓开发者寻求更加高效和简洁的方式来构建动态且吸引人的UI。本文将深入探讨Jetpack Compose这一革新性技术,它通过声明式编程模型简化了UI构建过程,并提升了性能与跨平台开发的可行性。我们将从基本概念出发,逐步解析如何利用Jetpack Compose来创建具有数据动态绑定能力的安卓应用,同时确保应用的高性能和良好用户体验。
15 0
|
15天前
|
XML Java Android开发
Android之UI基础控件
Android之UI基础控件
|
16天前
|
XML 开发工具 Android开发
构建高效的安卓应用:使用Jetpack Compose优化UI开发
【4月更文挑战第7天】 随着Android开发不断进化,开发者面临着提高应用性能与简化UI构建流程的双重挑战。本文将探讨如何使用Jetpack Compose这一现代UI工具包来优化安卓应用的开发流程,并提升用户界面的流畅性与一致性。通过介绍Jetpack Compose的核心概念、与传统方法的区别以及实际集成步骤,我们旨在提供一种高效且可靠的解决方案,以帮助开发者构建响应迅速且用户体验优良的安卓应用。
|
21天前
|
XML Java Android开发
Android每点击一次按钮就添加一条数据
Android每点击一次按钮就添加一条数据
22 1

热门文章

最新文章