@TOC

目录

一、控件概述

1.1、介绍

CommentView是一个简洁、高效、可自定义的开源的Android评论控件,支持盖楼评论回复模式下拉刷新评论上拉加载更多评论加载更多回复等功能,可自定义(样式、数据模型、布局)、可轻松按需求扩展功能。

1.2、功能特点

功能特点
支持下拉刷新评论
支持上拉加载更多评论
支持加载更多回复
支持盖楼评论回复模式
可设置空数据视图、错误视图
支持自定义数据模型
支持自定义样式配置器
支持自定义布局

1.3、效果图

GIF压缩后效果变差,想亲自体验可以到下面的仓库下载安装包。

想要什么的样式效果可以自定义实现

默认样式效果(GIF)

自定义样式效果(自定义仿微信公众号评论)(GIF)

1.4、源码仓库

仓库 网址
码云 https://gitee.com/jidcoo/CommentView
GitHub https://github.com/Jidcoo/CommentView
Jcenter(Maven) https://bintray.com/longyun/maven/CommentView

二、框架类解析

2.1、项目目录结构

2.2、项目整体框架

CommentView只负责对外提供业务方法,所有的业务实现和核心代码都在com.jidcoo.android.widget.commentview.operator包和com.jidcoo.android.widget.commentview.adapter包。com.jidcoo.android.widget.commentview.callback包是业务和控件的桥接包。

2.3、框架分解

2.3.1、数据模型类(Model)

整体框架

2.3.1.1、PagerEnable(分页)
/** * 分页模型
*
* 在Android端只实现为对分页数据的Get/Set操作
*
* 具体的分页逻辑应该由后端完成,客户端只负责对数据的处理
*
* @author Jidcoo */public class PagerEnable { /** * 当前页码 */ public int currentPage; /** * 每一页数据的大小 */ private int pageSize; /** * 总页数 */ public int totalPages; /** * 数据总数 */ public int totalDataSize; /** * 下一个页码 */ public int nextPage; /** * 上一个页码 */ public int prefPage; // //省略大量Get()/Set() 方法 //}

PagerEnable类是一个分页模型,是项目数据模型中非常重要的一个基类,评论数据抽象模型AbstractCommentModel、CommentEnable都继承自PagerEnable类。在业务类的核心代码中都需要用到PagerEnable类中的页码数据和方法。

在Android端不涉及具体分页逻辑,具体分页逻辑在后端完成。注意:在实际使用中,如果要实现分页加载(更多评论数据、更多回复数据),那么要展示的数据必须包含这些分页数据字段。 比如在json数据中必须包含这些数据,分页功能才能真正有效。也就是说无论你用什么作为数据,都必须在数据中包含相关分页数据字段。继承了PagerEnable类的所有类对于的数据都要包含相关分页数据字段

2.3.1.2、ReplyEnable(回复数据基类)
/** * 自定义回复模型必须继承自此类 * @author Jidcoo */public class ReplyEnable { }

ReplyEnable类是回复数据模型的基类,自定义回复模型必须继承自此类。可以看出此类没有任何的继承关系

可能有人会问,回复数据不是需要分页吗?为什么ReplyEnable类没有继承自PagerEnable分页模型类?没有分页数据回复数据如何分页?首先如果要实现回复数据分页加载,那么回复数据是需要分页的。但是回复数据的分页数据应该属于它所在评论(CommentEnable)的那一级中,而不是在回复数据本身上,因为在分页数据在回复模型中没有任何意义。是通过回复数据所在的那个评论模型(CommentEnable)中表现回复数据的分页情况

所有回复数据的分页加载都是基于它所在的评论模型所决定和控制的。而CommentEnable类正是继承自PagerEnable类,继承自PagerEnable类的唯一目的就是用来控制回复数据的分页功能。理解这段话就能够知道为什么ReplyEnable类只是一个空类了。

自定义回复数据模型例子:

/** * 首先继承自ReplyEnable类 * 然后设置回复模型中所需要的数据就可以了 */public class Reply extends ReplyEnable {      public String userName;      public String reply;      .....      设置更多具体的属性,比如时间、点赞数之类的数据      .....}
2.3.1.3、CommentEnable(评论数据抽象类)
/** * 

自定义评论模型抽象类

* 自定义评论模型必须继承自此抽象类 * @author Jidcoo */public abstract class CommentEnable extends PagerEnable{ /** * 必须实现该方法并确保返回值非NULL * @return */ public abstract List getReplies();}

CommentEnable是一个继承自PagerEnable类的抽象类。继承自PagerEnable类的唯一目的就是用来控制回复数据的分页功能,所以继承自此类的实体类就具可以支持所在评论的回复数据的分页加载。

自定义评论数据模型必须继承自CommentEnable类并实现getReplies()抽象方法getReplies()方法非常重要,必须实现。该方法返回一个List<? extend ReplyEnable>的list,这个list的元素是继承自ReplyEnable的自定义的回复数据模型实体类。如第二点中的例子Reply类。

自定义评论数据模型例子:

/** * 首先继承自CommentEnable类 * 然后实现抽象方法getReplies(),返回一个回复数据的List * 然后设置回复模型中所需要的数据就可以了 */public class Comment extends CommentEnable{      public List replies;      public String userName;      public String comment;      .....      设置更多具体的属性,比如时间、点赞数之类的数据      .....      /*@Override      public List getReplies() {            return null;      }*/      //必须实现getReplies()方法。      @Override      public  List getReplies() {            return replies;      }      /**       * 首先继承自ReplyEnable类       * 然后设置回复模型中所需要的数据就可以了       */      public class Reply extends ReplyEnable {            public String userName;            public String reply;            .....            设置更多具体的属性,比如时间、点赞数之类的数据            .....      }}

注意:CommentEnable类中getReplies()的原型是这样的:

@Overridepublic List getReplies() {            return null;      }

所以实现getReplies()方法的时候只需要把泛型R改为自己定义的回复数据模型类就可以了,然后在方法中返回这个实体类的集合。

2.3.1.4、AbstractCommentModel(评论数据总抽象类)
/** * 数据抽象模型

* 自定义模型必须继承此抽象模型,并实现其中的方法

* 注意:使用自定义数据类型就必须自定义布局实现,否则会抛出数据模型的java.lang.ClassCastException异常

* AbstractCommentModel中传入的泛型必须继承自CommentEnable,查看{@link CommentEnable} * @param 自定义的评论数据模型 * @author Jidcoo */public abstract class AbstractCommentModel extends PagerEnable{ /** * 必须实现该方法并确保返回值非NULL * @return */ public abstract List getComments() ;}

AbstractCommentModel类是评论模型抽象类,继承自PagerEnable类用来控制评论数据的分页功能。同时它是一个泛型抽象类,泛型接受的类必须是继承自CommentEnable的类,比如第三点中的例子Comment类。

同样的,实现自定义的数据模型必须继承自AbstractCommentModel类并实现getComments抽象方法(设计思想与CommentEnable类一样)。getComments()方法非常重要,必须实现。该方法返回一个List<? extend CommentEnable>的list,这个list的元素是继承自CommentEnable的自定义的评论数据模型实体类。如第三点中的例子Comment类。
注意:使用自定义数据类型就必须自定义布局实现,否则会抛出数据模型的java.lang.ClassCastException异常。原因可以查看defaults包下的DefaultItemBuilder类对于默认布局的实现方法。

AbstractCommentModel实际上是业务方法中使用到的最外层的数据模型类了。查看CommentView的源码中的业务方法就可以知道,主要的有关数据的业务方法接受的参数都是AbstractCommentModel,也就是说接受继承自此抽象类的数据模型。

自定义模型例子:

/** * 首先继承自抽象泛型类AbstractCommentModel * 其中泛型C用自定义的评论模型类替换就可以了 * 然后实现抽象方法getComments(),返回一个评论数据的List */public class CommentModel extends AbstractCommentModel {    public List comments;    //必须实现getComments()方法并确保返回值非NULL    @Override    public List getComments() {        return comments;    }    /** * 首先继承自CommentEnable类 * 然后实现抽象方法getReplies(),返回一个回复数据的List * 然后设置回复模型中所需要的数据就可以了 */public class Comment extends CommentEnable{      public String userName;      public String comment;      public List replies;      /*@Override      public List getReplies() {            return null;      }*/      @Override      public  List getReplies() {            return replies;      }      /**       * 首先继承自ReplyEnable类       * 然后设置回复模型中所需要的数据就可以了       */      public class Reply extends ReplyEnable {            public String userName;            public String reply;      }}}

在自定义模型中要继承自抽象泛型类AbstractCommentModel < C extends CommentEnable>

public class CommentModel extends AbstractCommentModel
    @Override    public List getComments() {        return null;    }

这里的泛型C用自定义的评论数据模型类(如第三点的例子Comment类)替换就可以了。

其实在自定义模型类中做的事情并不多,因为需要的Comment类、Reply类之前都已经自定义好了。所以现在就只需要在自定义模型类中继承自 AbstractCommentModel,并把泛型C替换成自己Comment类,最后实现抽象方法getComments()就可以了。注意:getComments()方法必须实现并返回对应的List,被确保返回的这个List非null

2.3.1.5、总结

好了,到现在为止,CommentView控件的数据模型就分析完了。好长篇幅。为什么呢?因为这个数据模型对于整个框架来说太重要了。必须理解好数据模型才能轻松使用这个控件。所以必须详细分析整个数据模型。

2.3.2、ViewHolder类

ViewHolder类是一个抽象类,是对于自定义回复布局的时候使用的,自定义评论布局不需要继承自此类。ViewHolder位于com.jidcoo.android.widget.commentview.view包下。

自定义评论布局ViewHolder类没有任何限制和要求不需要任何继承。所以这里不展示例子代码了,例子可以查看defaults包下的DefaultCommentHolder类。

但是对于自定义回复布局,有四点必须不按要求就报错):
1、必须使用ViewHolder机制
2、使用的ViewHolder必须继承自com.jidcoo.android.widget.commentview.view包下ViewHolder抽象类。
3、自定义的回复布局中最外层布局必须为LinearLayout
4、并且这个最外层的LinearLayout的android:id属性必须设置为“reply_rootView”,即android:id="@+id/reply_rootView"

这四点必须怎么来的呢,下面看ViewHolder类的代码:

/** * 自定义回复布局必须继承此抽象类并在构造方法中调用父类构造方法,否则会报错。

*

* 在自定义回复布局中最外层必须使用LinearLayout并将布局id设置为“reply_rootView”,否则会报错。

* @author Jidcoo */public abstract class ViewHolder { /** * 自定义布局这个view必须为非空 */ public LinearLayout rootView; public ViewHolder(View view){ try { rootView=view.findViewById(R.id.reply_rootView); }catch (Exception e){ throw new RuntimeException("If you use a custom layout, the outermost layout must be a LinearLayout, and you need to set its id attribute value to \"reply_rootView\""); } }}

在ViewHolder这个抽象类中有一个LinearLayout变量rootView,然后在构造方法中会在传进来的自定义布局的view里面通过findViewById()实例化rootView。所以当找不到这个id或者这个id对应的类型不是LinearLayout的时候会抛出RuntimeException异常

至于为什么要实例化rootView这个变量,可以看com.jidcoo.android.widget.commentview.adapter包下的ViewAdapter类的具体代码实现就知道了。这里不具体展开。

自定义回复布局时使用的ViewHolder的例子:

/** * 创建类继承自抽象类ViewHolder * 然后在带参构造方法中显式调用父级构造方法super(view)把view传进去 * 然后再实例化自己自定义的布局控件 */public class ReplyItemViewHolder extends ViewHolder {    public TextView replyTextView;    //……添加自己需要的控件    //……    //……添加自己需要的控件        public ReplyItemViewHolder(View view) {        super(view);        //实例化对应的控件        replyTextView=view.findViewById(R.id.replyTextView);            }}

2.3.3、回调类(Callback)

2.3.3.1、CustomCommentItemCallback(自定义评论布局回调)
/** * 自定义评论布局的回调

* 相当于Adapter的getView()方法

* 与Adapter的getView()方法使用一样

* 泛型接口中的泛型对应评论数据模型,即继承在CommentEnable,查看{@link CommentEnable} * @author Jidcoo */public interface CustomCommentItemCallback { /** * 相当于adapter中的getView()方法 * @param groupPosition Item所在groupPosition * @param comment 泛型评论数据 * @param inflater LayoutInflater实例(非空) * @param convertView View * @param parent * @return */ View buildCommentItem(int groupPosition, C comment, LayoutInflater inflater, View convertView, ViewGroup parent);}

CustomCommentItemCallback是一个自定义评论布局的泛型回调,非必须回调,当需要自定义评论布局的时候实现这个回调,回调中的buildCommentItem()方法相当于Adapter中的getView()方法,在buildCommentItem()中使用自定义的布局即可。

CustomCommentItemCallback< C extends CommentEnable>中的泛型C是你使用的评论数据模型,即该回调接受的类必须继承自CommentEnable。引入自定义布局需要的LayoutInflater实例已经保证是非null的,直接使用即可,不需要外创建一个新的实例降低性能。注意:在实现该方法时尽量使用ViewHolder机制,否则控件的性能可能会下降。

当需要自定义评论布局的时候实现这个回调。

例子

/** * 自定义评论布局 * 实现CustomCommentItemCallback回调 * 把自定义评论模型Comment放入泛型参数中 * 然后实现buildCommentItem()方法进行自定义布局 */public class CustomCommentItem implements CustomCommentItemCallback {    @Override    public View buildCommentItem(int groupPosition,Comment comment, LayoutInflater inflater, View convertView, ViewGroup parent) {        //使用ViewHolder机制        //自定义评论布局的ViewHolder没有任何要求,不需要任何继承        //为了更好的性能,尽量使用ViewHolder机制        CommentViewHolder commentViewHolder=null;        if(convertView==null){            //引入自定义布局            convertView=inflater.inflate(R.layout.my_commentItem_layout);            //实例化CommentViewHolder            commentViewHolder=new CommentViewHolder(convertView);            convertView.setTag(commentViewHolder);        }else{            commentViewHolder=(CommentViewHolder)convertView.getTag();        }        //对控件进行操作        //setText()……        //对控件进行操作        return convertView;    }}
2.3.3.2、CustomReplyItemCallback(自定义回复布局回调)
/** * 自定义回复布局的回调

* 相当于Adapter的getView()方法

* 与Adapter的getView()方法使用一样

* 泛型接口中的泛型对应回复数据模型,即继承在ReplyEnable,查看{@link ReplyEnable}

* 注意: *

* 布局:

* 1、自定义布局xml中最外层布局必须是LinearLayout并且把最外层布局id设置为reply_rootView

* ViewHolder:

* 1、在buildReplyItem()中自定义布局初始化时,必须使用ViewHolder机制

* 2、初始化过程中,自定义Holder必须继承自com.jidcoo.android.widget.commentview.view.ViewHolder,查看{@link ViewHolder}

* @author Jidcoo */public interface CustomReplyItemCallback { /** * 相当于adapter中的getView()方法 * @param groupPosition 所在父级位置 * @param childPosition 所在位置 * @param isLastReply 是否是最后一条评论 * @param convertView View * @param reply 泛型回复数据 * @param inflater LayoutInflater实例(非空) * @param parent * @return */ View buildReplyItem(int groupPosition, int childPosition, boolean isLastReply, R reply, LayoutInflater inflater, View convertView, ViewGroup parent);}

CustomReplyItemCallback是一个自定义回复布局的泛型回调,非必须回调,当需要自定义回复布局的时候实现这个回调,回调中的buildReplyItem()方法相当于Adapter中的getView()方法,在buildReplyItem()中使用自定义的布局即可。

CustomReplyItemCallback< R extends ReplyEnable>中的泛型R是你使用的回复数据模型,即该回调接受的类必须继承自ReplyEnable。引入自定义布局需要的LayoutInflater实例已经保证是非null的,直接使用即可,不需要外创建一个新的实例降低性能。注意:在实现buildReplyItem()方法时有四点必须(不按要求就报错):

1、必须使用ViewHolder机制
2、使用的ViewHolder必须继承自com.jidcoo.android.widget.commentview.view包下ViewHolder抽象类。
3、自定义的回复布局中最外层布局必须为LinearLayout
4、并且这个最外层的LinearLayout的android:id属性必须设置为“reply_rootView”,即android:id="@+id/reply_rootView"

这里的ViewHolder上边说过了,这里不再重复。在ViewAdapter中有一段代码是用来对ViewHolder做校验的:

        if(convertView.getTag() == null) {            throw new RuntimeException("You should call convertView.getTag() method to use Holder instance as the tag of convertView");        }else{            Object object = convertView.getTag();            if(object instanceof ViewHolder) {                //some core code            }else{                throw new RuntimeException("The ReplyHolder must extent from ViewHolder");            }        }

当使用自定义布局时,它会根据你返回的的自定义convertView进行getTag(),如果返回null,就抛出异常,所以在使用自定义布局时必须使用convertView.setTag()把Holder保存起来(ViewHolder机制)

当getTag()不为空的时候,又会去判断返回的对象是不是继承自ViewHolder,如果不是,就抛出异常

当需要自定义回复布局的时候实现这个回调。

例子

/** * 自定义评论布局 * 实现CustomReplyItemCallback回调 * 把自定义回复模型Reply放入泛型参数中 * 然后实现buildReplyItem()方法进行自定义布局 */public class CustomReplyItem implements CustomReplyItemCallback {    @Override    public View buildReplyItem(int groupPosition, int childPosition, boolean isLastReply, Reply reply, LayoutInflater inflater, View convertView, ViewGroup parent) {        //必须使用ViewHolder机制        //自定义ReplyItemViewHolder必须继承自com.jidcoo.android.widget.commentview.view.ViewHolder        //在自定义回复布局中最外层必须使用LinearLayout        //并将这个LinearLayout的id设置为“reply_rootView”,否则会报错。        ReplyItemViewHolder replyItemViewHolder=null;        if(convertView==null){            //引入自定义布局            convertView=inflater.inflate(R.layout.my_replyItem_layout);            //实例化ReplyItemViewHolder            replyItemViewHolder=new ReplyItemViewHolder(convertView);            //必须把ReplyItemViewHolder缓存在convertView中            //否则报错            convertView.setTag(replyItemViewHolder);            //必须把ReplyItemViewHolder缓存在convertView中        }else{            replyItemViewHolder=(ReplyItemViewHolder)convertView.getTag();        }        //对控件进行操作        //setText()……        //对控件进行操作        return convertView;    }}
2.3.3.3、OnPullRefreshCallback(下拉刷新回调)
/** * CommentView下拉刷新回调 * @author Jidcoo */public interface OnPullRefreshCallback {    /**     * 刷新回调     */    void refreshing();    /**     * 刷新完成回调     */    void complete();    /**     * 刷新失败     * @param msg 错误信息     */    void failure(String msg);}

OnPullRefreshCallback是下拉刷新回调,非必须回调。当实现了这个回调后可以进行下拉刷新,不实现就无法下拉刷新。看具体需求。

需要下拉刷新功能时实现这个回调。

例子:

public class MyOnPullRefreshCallback implements OnPullRefreshCallback {    @Override    public void refreshing() {        //该方法被调用,表示控件正在刷新状态        //实现你的刷新逻辑        //commentView.refreshComplete(AbstractCommentModel model): 刷新数据完成后调用        //commentView.refreshFailed(String,boolean): 刷新出现错误时调用,可以控制是否显示错误视图    }    @Override    public void complete() {    }    @Override    public void failure(String msg) {        //当刷新失败后显示调用commentView.refreshFailed(String,boolean)方法后        //这个方法会被调用        //msg是refreshFailed()方法中传进来的错误信息    }}
2.3.3.4、OnCommentLoadMoreCallback(上拉加载更多回调)
/** * CommentView加载更多评论回调 * @author Jidcoo */public interface OnCommentLoadMoreCallback{    /**     * 上拉加载更多评论回调     * @param currentPage 当前页码     * @param willLoadPage 下一个页码     * @param isLoadedAllPages 是否已经加载完所有数据     */    void loading(int currentPage,int willLoadPage,boolean isLoadedAllPages);    /**     * 上拉加载更多评论完成     */    void complete();    /**     * 加载失败     * @param msg 错误信息     */    void failure(String msg);}

OnCommentLoadMoreCallback是上拉加载更多评论回调,非必须回调。当实现了这个回调后可以进行上拉加载更多评论,不实现就无法上拉加载更多评论。看具体需求。

需要上拉加载更多评论功能时实现这个回调就可以了,和OnPullRefreshCallback的例子基本一致。具体使用例子可以查看相关源码。

2.3.3.5、OnReplyLoadMoreCallback(加载更多回复回调)
/** * CommentView加载更多回复回调

* 泛型接口中的泛型R是回复数据模型,必须继承自ReplyEnable,查看{@link ReplyEnable} * @author Jidcoo */public interface OnReplyLoadMoreCallback { /** * 加载更多回复回调 * @param reply 对应回复的数据 * @param willLoadPage */ void loading(R reply, int willLoadPage); /** * 加载更多回复完成回调 */ void complete(); /** * 加载失败 * @param msg 错误信息 */ void failure(String msg);}

OnReplyLoadMoreCallback是一个泛型回调,是加载更多回复回调,非必须回调。OnReplyLoadMoreCallback< R extends ReplyEnable>接受一个继承自ReplyEnable类的自定义回复数据模型类。具体使用例子可以查看相关源码。

2.3.3.6、OnScrollCallback(滚动事件回调)
/** * CommentView 滚动事件回调 * @author Jidcoo */public interface OnScrollCallback {    /**     * 滚动回调     * @param view     * @param firstVisibleItem     * @param visibleItemCount     * @param totalItemCount     */    void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int            totalItemCount);    /**     * 滚动状态回调     * @param view     * @param scrollState     */    void onScrollStateChanged(AbsListView view,int scrollState);    /**     * 滚动回调     * @param v     * @param scrollX     * @param scrollY     * @param oldScrollX     * @param oldScrollY     */    void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY);}

OnScrollCallback提供了控件的所有滚动事件的回调,非必须回调。可以通过实现这个回调来监听控件滚动事件。

2.3.3.7、OnItemClickCallback(Item点击事件回调)
/** * CommentView中评论Item和回复Item点击事件回调 * 泛型接口中的C、R泛型:

* 1、C是评论数据模型,必须继承自CommentEnable,查看{@link CommentEnable}

* 2、R是回复数据模型,必须继承自ReplyEnable,查看{@link ReplyEnable}

* @author Jidcoo */public interface OnItemClickCallback { /** * 评论Item点击回调事件 * @param position 被点击Item的位置 * @param comment 被点击Item所对应的数据 * @param view 被点击Item的View */ void commentItemOnClick(int position, C comment, View view); /** * 回复Item点击回调事件 * @param c_position 被点击Item所在的父级位置 * @param r_position 被点击Item所在位置 * @param reply 被点击Item所对应的数据 * @param view 被点击Item的View */ void replyItemOnClick(int c_position, int r_position, R reply, View view);}

OnItemClickCallback类是一个接受泛型参数的Item点击事件回调,OnItemClickCallback中泛型C、R对应的就是自定义评论数据模型和回复数据模型。当控件中评论Item和回复Item被点击时,回调中相对应的commentItemOnClick和replyItemOnClick方法就会被调用。

需要监听控件Item的点击事件就实现这个接口。具体使用例子可以查看相关源码。

2.3.3.8、CallbackBuilder(回调集)
    public final class CallbackBuilder {        ///////***Callback***//////        public OnPullRefreshCallback onPullRefreshCallback;//下拉刷新回调        public OnCommentLoadMoreCallback onCommentLoadMoreCallback;//加载更多评论回调        public OnReplyLoadMoreCallback onReplyLoadMoreCallback;//加载更多回复回调        public OnItemClickCallback onItemClickCallback;//点击事件回调        public OnScrollCallback onScrollCallback;//滚动事件回调        public CustomCommentItemCallback customCommentItemCallback;//自定义评论布局回调        public CustomReplyItemCallback customReplyItemCallback;//自定义回复布局回调        ///////***Callback***//////        public CallbackBuilder setOnPullRefreshCallback(OnPullRefreshCallback onPullRefreshCallback) {            this.onPullRefreshCallback = onPullRefreshCallback;            return this;        }        public CallbackBuilder setOnCommentLoadMoreCallback(OnCommentLoadMoreCallback onCommentLoadMoreCallback) {            this.onCommentLoadMoreCallback = onCommentLoadMoreCallback;            return this;        }        public CallbackBuilder setOnReplyLoadMoreCallback(OnReplyLoadMoreCallback onReplyLoadMoreCallback) {            this.onReplyLoadMoreCallback = onReplyLoadMoreCallback;            return this;        }        public CallbackBuilder setOnItemClickCallback(OnItemClickCallback onItemClickCallback) {            this.onItemClickCallback = onItemClickCallback;            return this;        }        public CallbackBuilder setOnScrollCallback(OnScrollCallback onScrollCallback) {            this.onScrollCallback = onScrollCallback;            return this;        }        public CallbackBuilder customCommentItem(CustomCommentItemCallback customCommentItemCallback) {            this.customCommentItemCallback = customCommentItemCallback;            return this;        }        public CallbackBuilder customReplyItem(CustomReplyItemCallback customReplyItemCallback) {            this.customReplyItemCallback = customReplyItemCallback;            return this;        }        public CommentView buildCallback() {            return initialize(this);        }    }

CallbackBuilder是一个构建控件的所有回调的Builder类,是CommentView类中的内部类。上面说到的所有的Callback都需要通过这个Builder类进行设置,构建回调需要获取这个Builder类的实例,然后再往这个Builder类设置需要的回调。

获取这个Builder类的实例通过CommentView.callbackBuilder()方法获取。当Builder实例为NULL时返回一个新实例,不为NULL就返回原来构建的实例。

可以看到,在buildCallback()方法中会调用CommentView的initialize()方法对Builder类的回调实例进行初始化和进一步装载。注意:无论是否需要设置回调,buildCallback()方法必须调用,如果不调用buildCallback()方法,控件处于未初始化状态,不会展示任何数据和效果。。具体原因可自行查看com.jidcoo.android.widget.commentview.operator包下的CommentViewOperator类源码。

也就是说,就算在不需要设置任何回调的情况下,buildCallback()都必须调用。调用如下:

commentView.callbackBuilder().buildCallback();

初始化回调例子:

CommentView commentView;commentView.callbackBuilder()           .setOnPullRefreshCallback(你的回调实例)           .onItemClickCallback(你的回调实例)           ......           //设置完成后必须调用CallbackBuilder的buildCallback()方法,否则设置的回调无效,控件也无法正常显示。           //无论是否设置回调,buildCallback()方法都必须调用。           .buildCallback();
2.3.3.9、总结

好了,到现在为止,CommentView控件的回调类基本介绍完成。写的时候感觉十分啰嗦,但最后还是写下来了。因为这些回调类虽然都是非必须回调,但是想实现更多功能都需要用到这些回调

2.4、样式配置器

/** * 非可自定义控件样式配置器 * 

该样式配置器针对非自定义控件的样式的具体配置

*

可根据具体需求配置这部分的控件的样式

*

自定义样式配置器必须继承自本类

*

*

样式变量说明:

*

1、refreshViewColor

*

类型:String

*

说明:下拉刷新后出现的圆形滚动条的颜色

*

赋值样例:#000000

*

2、lmv_showText(正常显示状态)

*

类型:String

*

说明:当回复数据Item可以加载更多(分页加载)时,在最后一条回复Item的View下面显示的文字

*

赋值样例:展开更多回复

*

3、lmv_textSize

*

类型:int

*

说明:第二条说明中的文字大小

*

赋值样例:14

*

4、lmv_textColor

*

类型:String

*

说明:第二条说明中的文字颜色

*

赋值样例:#000000

*

5、lmv_textStyle

*

类型:Typeface,查看{@link Typeface}

*

说明:第二条说明中的文字样式(正常、斜体、加粗)

*

赋值样例:Typeface.defaultFromStyle(Typeface.NORMAL)

*

6、lmv_loading_showText(加载中状态)

*

类型:String

*

说明:当回复数据Item可以加载更多(分页加载)时,在最后一条回复Item的加载更多View被点击后显示的文字

*

赋值样例:加载中

*

7、lmv_loading_textSize

*

类型:int

*

说明:第六条说明中的文字大小

*

赋值样例:14

*

8、lmv_loading_textColor

*

类型:String

*

说明:第六条说明中的文字颜色

*

赋值样例:#000000

*

9、lmv_loading_textStyle

*

类型:Typeface,查看{@link Typeface}

*

说明:第六条说明中的文字样式(正常、斜体、加粗)

*

赋值样例:Typeface.defaultFromStyle(Typeface.NORMAL)

*

10、lmv_loading_progressBarColor

*

类型:String

*

说明:当最后一条回复ItemView的可加载更多View状态为正在加载状态时, * 第六条说明中的文字右边会显示一个圆形progressBar以增强UI加载效果, * 该属性是这个progressBar的颜色值

*

赋值样例:#000000

*

11、lmv_loading_progressBarSize

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:第十条说明中的progressBar控件的大小尺寸

*

赋值样例:ViewUtil.dpToPx(14)

*

12、lmv_adjustMargins

*

类型:Boolean(默认为false)

*

说明:是否调整最后一条回复Item的加载更多View的边距,如果自定义回复布局,可能需要 * 调整这个View的边距来使布局更美观,一般情况下都需要调整边距,所以这个属性一般都为true

*

赋值样例:true

*

13、lmv_adjustMarginsLeft

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:第十二条说明中的view的左边距,如果lmv_adjustMargins为false,则此属性无效

*

赋值样例:ViewUtil.dpToPx(14)

*

14、lmv_adjustMarginsTop

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:第十二条说明中的view的上边距,如果lmv_adjustMargins为false,则此属性无效

*

赋值样例:ViewUtil.dpToPx(14)

*

15、lmv_adjustMarginsRight

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:第十二条说明中的view的右边距,如果lmv_adjustMargins为false,则此属性无效

*

赋值样例:ViewUtil.dpToPx(14)

*

16、lmv_adjustMarginsBottom

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:第十二条说明中的view的下边距,如果lmv_adjustMargins为false,则此属性无效

*

赋值样例:ViewUtil.dpToPx(14)

*

17、dividerColor

*

类型:String

*

说明:控件Item的分割线颜色(包含一级分割线和二级分割线)

*

赋值样例:#000000

*

18、dividerHeight

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:控件Item的分割线高度(包含一级分割线和二级分割线)

*

赋值样例:ViewUtil.dpToPx(1),高度一般为1dp

*

19、isDrawChildDivider

*

类型:Boolean(默认为false)

*

说明:是否绘制某一条评论下的最后一条回复Item与下一条评论的Item的分割线,默认不绘制, * 可根据具体需要选择是否绘制

*

赋值样例:true

*

20、c_divider_adjustMargins

*

类型:Boolean(默认为false)

*

说明:是否调整第十七条说明中的分割线的边距,如果isDrawDivider为false,则此属性无效。 * 因为第十九条说明中的分割线宽度固定为match_parent(即铺满全屏),但是可以通过调整分割线的边距 * 来间接调整分割线的宽度和位置,可根据具体需要选择是否调整分割线的边距

*

赋值样例:true

*

21、c_divider_adjustMarginsLeft

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:第十九条说明中的分割线的左边距,如果divider_adjustMargins为false,则此属性无效

*

赋值样例:ViewUtil.dpToPx(1)

*

22、c_divider_adjustMarginsTop

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:第十九条说明中的分割线的上边距,如果divider_adjustMargins为false,则此属性无效

*

赋值样例:ViewUtil.dpToPx(1)

*

23、c_divider_adjustMarginsRight

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:第十九条说明中的分割线的右边距,如果divider_adjustMargins为false,则此属性无效

*

赋值样例:ViewUtil.dpToPx(1)

*

24、c_divider_adjustMarginsBottom

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:第十九条说明中的分割线的下边距,如果divider_adjustMargins为false,则此属性无效

*

赋值样例:ViewUtil.dpToPx(1)

*

25、f_divider_adjustMargins

*

类型:Boolean(默认为false)

*

说明:是否调整评论item的分割线的边距

*

赋值样例:true

*

26、f_divider_adjustMarginsLeft

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:评论item的分割线的左边距

*

赋值样例:ViewUtil.dpToPx(1)

*

27、f_divider_adjustMarginsTop

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:评论item的分割线的上边距

*

赋值样例:ViewUtil.dpToPx(1)

*

28、f_divider_adjustMarginsRight

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:评论item的分割线的右边距

*

赋值样例:ViewUtil.dpToPx(1)

*

29、f_divider_adjustMarginsBottom

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:评论item的分割线的下边距

*

赋值样例:ViewUtil.dpToPx(1)

*

30、lm_footerProgressBarSize

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:上拉刷新时,控件底部会有一个圆形ProgressBar来增加加载中的UI效果,此属性为ProgressBar的大小

*

赋值样例:ViewUtil.dpToPx(1)

*

31、lm_footerProgressBarColor

*

类型:String

*

说明:第三十条说明中的ProgressBar的颜色

*

赋值样例:#000000

*

32、lm_footerProgressBar_adjustMargins

*

类型:Boolean(默认为false)

*

说明:是否调整第三十条说明中的ProgressBar的边距

*

赋值样例:true

*

33、lm_footerProgressBar_adjustMarginsLeft

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:第三十条说明中的ProgressBar的左边距,如果lm_footerProgressBar_adjustMargins为false,则此属性无效

*

赋值样例:ViewUtil.dpToPx(14)

*

34、lm_footerProgressBar_adjustMarginsTop

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:第三十条说明中的ProgressBar的上边距,如果lm_footerProgressBar_adjustMargins为false,则此属性无效

*

赋值样例:ViewUtil.dpToPx(14)

*

35、lm_footerProgressBar_adjustMarginsRight

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:第三十条说明中的ProgressBar的右边距,如果lm_footerProgressBar_adjustMargins为false,则此属性无效

*

赋值样例:ViewUtil.dpToPx(14)

*

36、lm_footerProgressBar_adjustMarginsBottom

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:第三十条说明中的ProgressBar的下边距,如果lm_footerProgressBar_adjustMargins为false,则此属性无效

*

赋值样例:ViewUtil.dpToPx(14)

*

37、lm_footer_text

*

类型:String

*

说明:当所有数据加载完成后控件底部textView显示的文字

*

赋值样例:哈哈,所有评论都在这了

*

38、lm_footer_textColor

*

类型:String

*

说明:第三十七条说明中的textView文字的颜色

*

赋值样例:#666666

*

39、lm_footer_textSize

*

类型:int

*

说明:第三十七条说明中的textView文字的大小

*

赋值样例:14

*

40、lm_footer_textStyle

*

类型:Typeface

*

说明:第三十七条说明中的textView文字的样式(正常,斜体、加粗)

*

赋值样例:Typeface.defaultFromStyle(Typeface.NORMAL)

*

41、lm_footer_text_adjustMargins

*

类型:Boolean(默认为false)

*

说明:是否调整第三十七条说明中的textView的边距

*

赋值样例:true

*

42、lm_footer_text_adjustMarginsLeft

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:第三十七条说明中的textView的左边距,如果lm_footer_text_adjustMargins为false,则此属性无效

*

赋值样例:ViewUtil.dpToPx(14)

*

43、lm_footer_text_adjustMarginsTop

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:第三十七条说明中的textView的上边距,如果lm_footer_text_adjustMargins为false,则此属性无效

*

赋值样例:ViewUtil.dpToPx(14)

*

44、lm_footer_text_adjustMarginsRight

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:第三十七条说明中的textView的右边距,如果lm_footer_text_adjustMargins为false,则此属性无效

*

赋值样例:ViewUtil.dpToPx(14)

*

45、lm_footer_text_adjustMarginsBottom

*

类型:int(要求单位:px,因为需要把(dp/dip值转换为px值))

*

说明:第三十七条说明中的textView的下边距,如果lm_footer_text_adjustMargins为false,则此属性无效

*

赋值样例:ViewUtil.dpToPx(14)

*

* @author Jidcoo */public abstract class ViewStyleConfigurator{ /** * 下拉刷新控件颜色 */ public String refreshViewColor; //*********//回复加载更多控件样式//*************// //Part1// public String lmv_showText; public int lmv_textSize; public String lmv_textColor; public Typeface lmv_textStyle; //Part2// public String lmv_loading_showText; public int lmv_loading_textSize; public String lmv_loading_textColor; public Typeface lmv_loading_textStyle; public String lmv_loading_progressBarColor; public int lmv_loading_progressBarSize; //Part3// public boolean lmv_adjustMargins; public int lmv_adjustMarginsLeft;//dp public int lmv_adjustMarginsTop;//dp public int lmv_adjustMarginsRight;//dp public int lmv_adjustMarginsBottom;//dp //*********//回复加载更多控件样式//*************// //*********//分割线总样式//*************// public String dividerColor; public int dividerHeight;//dp //*********//分割线总样式//*************// //*********//回复Item最后一项的下划线(分样式)//*************// public boolean isDrawChildDivider; public boolean c_divider_adjustMargins; public int c_divider_adjustMarginsLeft;//dp public int c_divider_adjustMarginsTop;//dp public int c_divider_adjustMarginsRight;//dp public int c_divider_adjustMarginsBottom;//dp //*********//回复Item最后一项的下划线(分样式)//*************// //*********//评论Item的分割线(分样式)//*************// /** * 评论Item的分割线颜色、高度与回复Item的分割线一样 */ public boolean f_divider_adjustMargins; public int f_divider_adjustMarginsLeft;//dp public int f_divider_adjustMarginsTop;//dp public int f_divider_adjustMarginsRight;//dp public int f_divider_adjustMarginsBottom;//dp //*********//评论Item的分割线(分样式)//*************// //*********//底部上拉加载更多圆形ProgressBar的大小和颜色//*************// public int lm_footerProgressBarSize=12;//dp public String lm_footerProgressBarColor; public boolean lm_footerProgressBar_adjustMargins; public int lm_footerProgressBar_adjustMarginsLeft;//dp public int lm_footerProgressBar_adjustMarginsTop;//dp public int lm_footerProgressBar_adjustMarginsRight;//dp public int lm_footerProgressBar_adjustMarginsBottom;//dp //*********//底部上拉加载更多圆形ProgressBar的大小和颜色//*************// //*********//底部上拉加载更多完成后的textView//*************// //文本,颜色、大小、样式、边距 public String lm_footer_text; public String lm_footer_textColor; public int lm_footer_textSize=12; public Typeface lm_footer_textStyle; public boolean lm_footer_text_adjustMargins; public int lm_footer_text_adjustMarginsLeft;//dp public int lm_footer_text_adjustMarginsTop;//dp public int lm_footer_text_adjustMarginsRight;//dp public int lm_footer_text_adjustMarginsBottom;//dp //*********//底部上拉加载更多完成后的textView//*************// }

ViewStyleConfigurator是样式配置器抽象类。该样式配置器针对固有控件的样式的具体配置。抽象类中的所有变量都是用来设置固有控件的样式。所有的样式变量的解释和说明都有详细充分的注释。在这就不一一说明了。

在自定义样式的时候创建一个类继承自这个抽象类,然后对需要自定义的样式变量赋值就可以了。

例子:(这里贴出的是项目中的默认样式配置器,自定义样式配置器可以参照defaults包下的DefaultViewStyleConfigurator类)

/** * 默认样式配置器 * 

此配置器为标准模板

*

自定义样式配置器必须继承自ViewStyleConfigurator,查看{@link ViewStyleConfigurator}

*

样式变量说明,请查看{@link ViewStyleConfigurator}

*

实际上,自定义样式配置器可以直接复制本类,然后再根据具体把样式修改就好了

* @author Jidcoo */public class DefaultViewStyleConfigurator extends ViewStyleConfigurator { public DefaultViewStyleConfigurator(Context context) { /** * 下拉刷新控件颜色 */ refreshViewColor = "#D81B60"; //*********//回复加载更多控件样式//*************// //Part1// lmv_showText = "展开更多回复"; lmv_textSize = 14; lmv_textColor = "#D81B60"; lmv_textStyle = Typeface.defaultFromStyle(Typeface.NORMAL); //Part2// lmv_loading_showText = "加载中"; lmv_loading_textSize = 14; lmv_loading_textColor = "#666666"; lmv_loading_textStyle = Typeface.defaultFromStyle(Typeface.NORMAL); lmv_loading_progressBarColor = "#666666"; lmv_loading_progressBarSize = ViewUtil.dpToPx(14, context); //Part3// lmv_adjustMargins = true; lmv_adjustMarginsLeft = ViewUtil.dpToPx(88, context);//dp lmv_adjustMarginsTop = ViewUtil.dpToPx(5, context);//dp lmv_adjustMarginsRight = 0;//dp lmv_adjustMarginsBottom = ViewUtil.dpToPx(5, context);//dp //*********//回复加载更多控件样式//*************// //*********//分割线总样式//*************// dividerColor = "#f0f0f0"; dividerHeight = ViewUtil.dpToPx(1, context);//dp //*********//分割线总样式//*************// //*********//回复Item最后一项的下划线//*************// isDrawChildDivider = true; c_divider_adjustMargins = true; c_divider_adjustMarginsLeft = ViewUtil.dpToPx(88, context);//dp c_divider_adjustMarginsTop = ViewUtil.dpToPx(5, context);//dp c_divider_adjustMarginsRight = 0;//dp c_divider_adjustMarginsBottom = ViewUtil.dpToPx(5, context);//dp //*********//回复Item最后一项的下划线//*************// //*********//评论Item的分割线//*************// f_divider_adjustMargins = false; f_divider_adjustMarginsLeft = 0;//dp f_divider_adjustMarginsTop = 0;//dp f_divider_adjustMarginsRight = 0;//dp f_divider_adjustMarginsBottom = 0;//dp //*********//评论Item的分割线//*************// //*********//底部上拉加载更多圆形ProgressBar的大小和颜色//*************// lm_footerProgressBarSize = ViewUtil.dpToPx(20, context);//dp lm_footerProgressBarColor = "#c3c8cb"; lm_footerProgressBar_adjustMargins = false; lm_footerProgressBar_adjustMarginsLeft = 0;//dp lm_footerProgressBar_adjustMarginsTop = 0;//dp lm_footerProgressBar_adjustMarginsRight = 0;//dp lm_footerProgressBar_adjustMarginsBottom = 0;//dp //*********//底部上拉加载更多圆形ProgressBar的大小和颜色//*************// //*********//底部上拉加载更多完成后的textView//*************// //文本,颜色、大小、样式、边距 lm_footer_text = "评论都给你看完了哦~"; lm_footer_textColor = "#c3c8cb"; lm_footer_textSize = 14; lm_footer_textStyle = Typeface.defaultFromStyle(Typeface.NORMAL); lm_footer_text_adjustMargins = false; lm_footer_text_adjustMarginsLeft = 0;//dp lm_footer_text_adjustMarginsTop = 0;//dp lm_footer_text_adjustMarginsRight = 0;//dp lm_footer_text_adjustMarginsBottom = 0;//dp //*********//底部上拉加载更多完成后的textView//*************// }}

2.5、总结

超长的篇幅解释说明使用控件中需要用到的框架类,可以说是使用CommentView的理论部分。超长超啰嗦,但是这些说明都是非常重要的,也是必须的。为什么这么说呢?因为上面的这些框架类的说明和例子都是在使用CommentView过程中业务层需要用到的,也就是说理解了上面长篇大论的解释后,使用CommentView就非常简单了

所以尽管十分啰嗦,但是有些东西需要特别说明一下。以上都是理论部分(十分枯燥、又长又臭……)有了上述这些对整个控件框架的整体了解,使用这个控件库就非常容易了

注意:项目是使用AndroidX库开发,部分控件是AndroidX库的控件,如果你的项目没有迁移到AndroidX库开发,可以把CommentView控件库中的那些AndroidX库控件修改为你当前项目的Android库的控件就可以了。

下面就要讲CommentView实际使用部分了(理解了上面的理论,说实话使用没有任何技术含量)。

三、控件的使用

3.1、控件方法介绍

以下是控件对外公布的所有业务方法。
方法 参数 说明
.callbackBuilder() \ 获取CallbackBuilder实例设置需要的回调, 无论是否设置回调,都必须调用.callbackBuilder().buildCallback()方法完成初始化。并且初始化必须在loadComplete()调用之前完成。
.loadComplete() AbstractCommentModel 首次加载数据时调用此方法, 注意:设置回调,设置空数据视图、设置错误视图、设置自定义样式配置器,设置头布局,这5个方法都必须要在loadComplete()这个方法调用之前调用,否则这5个方法失效。
.loadFailed() boolean isShowErrorView 首次加载数据失败时调用,boolean参数表示是否显示错误视图。
.refreshComplete() AbstractCommentModel 刷新数据完成后调用,传入刷新后的数据实体类。该方法调用后OnPullRefreshCallback的complete()方法会被调用。
.refreshFailed() String msg,boolean isShowErrorView 数据刷新失败时调用,msg表示错误信息,boolean参数表示是否显示错误视图。该方法调用后OnPullRefreshCallback的failure(String msg)方法会被调用。
.loadMoreComplete() AbstractCommentModel 加载更多评论数据完成后调用,传入一个数据实体类。该方法调用后OnCommentLoadMoreCallback的complete()方法会被调用。
.loadMoreFailed() String msg,boolean isShowErrorView 加载更多评论数据失败时调用,msg表示错误信息,boolean参数表示是否显示错误视图。该方法调用后OnCommentLoadMoreCallback的failure(String msg)方法会被调用。
.loadMoreReplyComplete() AbstractCommentModel 加载更多回复数据完成后调用,传入一个数据实体类。该方法调用后OnReplyLoadMoreCallback的complete()方法会被调用。
.loadMoreReplyFailed() String msg,boolean isShowErrorView 加载更多评论数据失败时调用,msg表示错误信息,boolean参数表示是否显示错误视图。该方法调用后OnReplyLoadMoreCallback的failure(String msg)方法会被调用。
.getCommentList() \ 获取所有评论数据返回List
.getReplyList() int position 根据position所在的评论获取所在评论的所有回复数据返回List
.addComment() C<?extend CommentEnable> comment 添加一条评论数据到当前的评论数据集合中
.addReply() R<?extend ReplyEnable> comment,int position 添加一条回复数据到position所在的评论的回复数据集合中
.setEmptyView() View view 为控件设置空数据视图,注意:此方法必须在loadComplete()方法调用前调用,也就是说要在首次加载数据前调用,否则此方法无效。
.setErrorView() View view 为控件设置错误视图,注意:此方法必须在loadComplete()方法调用前调用,也就是说要在首次加载数据前调用,否则此方法无效。
.addHeaderView() View view,boolean canClickable 为控件添加头视图,并且设置该视图是否响应点击事件。注意:此方法建议在loadComplete()方法调用前调用。
.removeHeaderView() View view 移除当前控件的对应的头视图
.setViewStyleConfigurator() ViewStyleConfigurator 设置自定义的样式配置器,注意:此方法必须在loadComplete()方法调用前调用,也就是说要在首次加载数据前调用,否则此方法无效。

3.2、基本使用

第一步:引入控件库:

有两种方法:

1、远程仓库

在module的build.gradle中添加jcenter仓库:

buildscript {    repositories {        jcenter()    }}

然后在dependencies模块中添加依赖即可:

implementation 'com.jidcoo.android.widget.commentview:CommentView:1.0.0'

2、本地仓库

把源码包下载下来,把commentview库放在与当前module的同级。

然后在dependencies模块中添加本地依赖即可:

implementation project(path: ':commentview')

第二步:引入控件:

控件的引入方法有两种:

1、XML布局文件中引入

<?xml version="1.0" encoding="utf-8"?>  

然后在Activity中实例化控件。

2、Java代码中动态创建

//控件容器LinearLayout mContainer;CommentView commentView=new CommentView(this,mContainer);

CommentView的构造方法:
public CommentView(Context context, ViewGroup attachTo)

第一个参数是Activity的上下文。第二个参数是ViewGroup,也就是将控件挂靠在这个指定的布局上。注意:当attachTo参数为空时,需要手动把控件添加到布局中,否则控件将不会显示

第三步:初始化控件:

设置自定义样式配置器

如果使用默认样式的话就不需要调用这个方法,如果使用自定义样式配置器时调用该方法必须在loadComplete()调用前调用,否则该方法无效。

commentView.setViewStyleConfigurator(你的样式配置器);

设置空数据视图
如果不需要设置空数据视图就不需要调用这个方法,如果需要设置空数据视图时调用该方法必须在loadComplete()调用前调用,否则该方法无效。

commentView.setEmptyView(你的空数据视图);

设置错误视图
如果不需要设置错误视图就不需要调用这个方法,如果需要设置错误视图时调用该方法必须在loadComplete()调用前调用,否则该方法无效。

commentView.setErrorView(你的错误视图);

添加控件头视图
如果不需要添加控件头视图就不需要调用这个方法,如果需要添加控件头视图时调用该方法建议在loadComplete()调用前调用。

commentView.addHeaderView(你的错误视图,是否响应点击事件);

第四步:初始化回调(非常重要,必须初始化):

无论是否需要设置回调,都要调用.buildCallback()方法完成初始化。

并且初始化回调的工作必须要在loadComplete()方法调用之前(即首次加载数据之前)完成,否则控件将无法正常使用。

支持的回调:
1、CustomCommentItemCallback:自定义评论布局回调
2、CustomReplyItemCallback:自定义回复布局回调
3、OnPullRefreshCallback:上拉刷新回调
4、OnCommentLoadMoreCallback:下拉加载更多评论回调
5、OnReplyLoadMoreCallback:加载更多回复回调
6、OnItemClickCallback:Item的点击事件回调
7、OnScrollCallback:控件滚动事件回调

当需要设置回调时:

设置完回调后必须调用.buildCallback()方法,否则回调不会生效,控件也无法正常使用。

commentView.callbackBuilder()           .setOnPullRefreshCallback(你的回调实例)           .onItemClickCallback(你的回调实例)           ......设置更多回调           ......设置更多回调           //设置完成后必须调用CallbackBuilder的buildCallback()方法,否则设置的回调无效,控件也无法正常显示。           //无论是否设置回调,buildCallback()方法都必须调用。           .buildCallback();

当不需要设置回调时:

必须调用.buildCallback()方法,否则控件也无法正常使用。

commentView.callbackBuilder().buildCallback();

第五步:设置数据:

当所有的初始化工作都完成后,就可以请求后台返回评论数据或加载本地数据为控件设置数据了。完成设置数据后,控件就能正确显示评论数据了。

commentView.loadComplete(你的数据模型实体类);

3.3、具体实例(自定义仿微信公众号文章评论)

布局文件:custom_use.xml

<?xml version="1.0" encoding="utf-8"?>  

自定义评论布局:custom_item_comment.xml

<?xml version="1.0" encoding="utf-8"?>                                            

自定义回复布局:custom_item_reply.xml

<?xml version="1.0" encoding="utf-8"?>                                            

自定义数据模型:CustomCommentModel.java

import com.jidcoo.android.widget.commentview.model.AbstractCommentModel;import com.jidcoo.android.widget.commentview.model.CommentEnable;import com.jidcoo.android.widget.commentview.model.ReplyEnable;import java.util.List;public class CustomCommentModel extends AbstractCommentModel {    public List comments;    @Override    public List getComments() {        return comments;    }    public class CustomComment extends CommentEnable{        public List replies;        public String posterName;        public String data;        public CustomComment() {                }                public void setReplies(List replies) {            this.replies = replies;        }        public String getPosterName() {            return posterName;        }        public void setPosterName(String posterName) {            this.posterName = posterName;        }        public String getData() {            return data;        }        public void setData(String data) {            this.data = data;        }                @Override        public  List getReplies() {            return replies;        }        public class CustomReply extends ReplyEnable{            public String replierName;            public String data;            public CustomReply() {            }            public String getReplierName() {                return replierName;            }            public void setReplierName(String replierName) {                this.replierName = replierName;            }            public String getData() {                return data;            }            public void setData(String data) {                this.data = data;            }        }    }}

评论布局ViewHolder:CustomCommentViewHolder.java

import android.view.View;import android.widget.TextView;import com.jidcoo.android.widgettest.R;public class CustomCommentViewHolder {      public TextView userName,prizes,comment;      public SampleCircleImageView ico;    public CustomCommentViewHolder(View view) {        userName=view.findViewById(R.id.user);        prizes=view.findViewById(R.id.prizes);        comment=view.findViewById(R.id.data);       ico=view.findViewById(R.id.ico);    }}

回复布局ViewHolder:CustomReplyViewHolder.java

import android.view.View;import android.widget.TextView;import com.jidcoo.android.widget.commentview.view.ViewHolder;import com.jidcoo.android.widgettest.R;public class CustomReplyViewHolder extends ViewHolder {    public TextView userName,prizes,reply;    public CustomReplyViewHolder(View view) {        super(view);        userName=view.findViewById(R.id.user);        prizes=view.findViewById(R.id.prizes);        reply=view.findViewById(R.id.data);    }}

自定义样式配置器:CustomViewStyleConfigurator.java

import android.content.Context;import android.graphics.Typeface;import com.jidcoo.android.widget.commentview.utils.ViewUtil;import com.jidcoo.android.widget.commentview.view.ViewStyleConfigurator;public class CustomViewStyleConfigurator extends ViewStyleConfigurator {    public CustomViewStyleConfigurator(Context context) {        /**         * 下拉刷新控件颜色         */        refreshViewColor = "#D81B60";        //*********//回复加载更多控件样式//*************//        //Part1//        lmv_showText = "展开更多回复";        lmv_textSize = 14;        lmv_textColor = "#D81B60";        lmv_textStyle = Typeface.defaultFromStyle(Typeface.NORMAL);        //Part2//        lmv_loading_showText = "加载中";        lmv_loading_textSize = 14;        lmv_loading_textColor = "#666666";        lmv_loading_textStyle = Typeface.defaultFromStyle(Typeface.NORMAL);        lmv_loading_progressBarColor = "#666666";        lmv_loading_progressBarSize = ViewUtil.dpToPx(14, context);        //Part3//        lmv_adjustMargins = true;        lmv_adjustMarginsLeft = ViewUtil.dpToPx(52, context);//dp        lmv_adjustMarginsTop = ViewUtil.dpToPx(5, context);//dp        lmv_adjustMarginsRight = 0;//dp        lmv_adjustMarginsBottom = ViewUtil.dpToPx(5, context);//dp        //*********//回复加载更多控件样式//*************//        //*********//分割线总样式//*************//        dividerColor = "#00000000";        dividerHeight = ViewUtil.dpToPx(1, context);//dp        //*********//分割线总样式//*************//        //*********//回复Item最后一项的下划线//*************////        isDrawChildDivider = false;//        c_divider_adjustMargins = true;//        c_divider_adjustMarginsLeft = ViewUtil.dpToPx(88, context);//dp//        c_divider_adjustMarginsTop = ViewUtil.dpToPx(5, context);//dp//        c_divider_adjustMarginsRight = 0;//dp//        c_divider_adjustMarginsBottom = ViewUtil.dpToPx(5, context);//dp//        //*********//回复Item最后一项的下划线//*************////        //*********//评论Item的分割线//*************////        f_divider_adjustMargins = false;//        f_divider_adjustMarginsLeft = 0;//dp//        f_divider_adjustMarginsTop = 0;//dp//        f_divider_adjustMarginsRight = 0;//dp//        f_divider_adjustMarginsBottom = 0;//dp        //*********//评论Item的分割线//*************//        //*********//底部上拉加载更多圆形ProgressBar的大小和颜色//*************//        lm_footerProgressBarSize = ViewUtil.dpToPx(20, context);//dp        lm_footerProgressBarColor = "#666666";        lm_footerProgressBar_adjustMargins = false;        lm_footerProgressBar_adjustMarginsLeft = 0;//dp        lm_footerProgressBar_adjustMarginsTop = 0;//dp        lm_footerProgressBar_adjustMarginsRight = 0;//dp        lm_footerProgressBar_adjustMarginsBottom = 0;//dp        //*********//底部上拉加载更多圆形ProgressBar的大小和颜色//*************//        //*********//底部上拉加载更多完成后的textView//*************//        //文本,颜色、大小、样式、边距        lm_footer_text = "到底了(⊙o⊙)!";        lm_footer_textColor = "#c3c8cb";        lm_footer_textSize = 14;        lm_footer_textStyle = Typeface.defaultFromStyle(Typeface.NORMAL);        lm_footer_text_adjustMargins = false;        lm_footer_text_adjustMarginsLeft = 0;//dp        lm_footer_text_adjustMarginsTop = 0;//dp        lm_footer_text_adjustMarginsRight = 0;//dp        lm_footer_text_adjustMarginsBottom = 0;//dp        //*********//底部上拉加载更多完成后的textView//*************//    }}

Avtivity:CustomUseInLocalActivity.java

import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.Toast;import androidx.annotation.Nullable;import androidx.appcompat.app.AppCompatActivity;import com.google.gson.Gson;import com.jidcoo.android.widget.commentview.CommentView;import com.jidcoo.android.widget.commentview.callback.CustomCommentItemCallback;import com.jidcoo.android.widget.commentview.callback.CustomReplyItemCallback;import com.jidcoo.android.widget.commentview.callback.OnCommentLoadMoreCallback;import com.jidcoo.android.widget.commentview.callback.OnItemClickCallback;import com.jidcoo.android.widget.commentview.callback.OnPullRefreshCallback;import com.jidcoo.android.widget.commentview.callback.OnReplyLoadMoreCallback;import com.jidcoo.android.widgettest.R;import com.jidcoo.android.widgettest.simple.LocalServer;import java.lang.ref.WeakReference;/** * 对于CommentView的自定义数据类型和布局的使用实例(使用本地测试数据) * 使用自定义样式配置器,自定义数据模型,自定义布局 * 注意:使用自定义数据类型就必须自定义布局实现,否则会抛出数据模型的java.lang.ClassCastException异常

* @author Jidcoo */public class CustomUseInLocalActivity extends AppCompatActivity { private CommentView commentView; private Gson gson; private LocalServer localServer; // private static class ActivityHandler extends Handler { private final WeakReference mActivity; public ActivityHandler(CustomUseInLocalActivity activity) { mActivity = new WeakReference(activity); } @Override public void handleMessage(Message msg) { CustomUseInLocalActivity activity = mActivity.get(); if (activity != null) { switch (msg.what){ case 1: //commentView.loadFailed(true);//实际网络请求中如果加载失败调用此方法 activity.commentView.loadComplete(activity.gson.fromJson((String)msg.obj,CustomCommentModel.class)); break; case 2: //commentView.refreshFailed();//实际网络请求中如果加载失败调用此方法 activity.commentView.refreshComplete(activity.gson.fromJson((String)msg.obj, CustomCommentModel.class)); break; case 3: //commentView.loadFailed();//实际网络请求中如果加载失败调用此方法 activity.commentView.loadMoreComplete(activity.gson.fromJson((String)msg.obj,CustomCommentModel.class)); break; case 4: //commentView.loadMoreReplyFailed();//实际网络请求中如果加载失败调用此方法 activity.commentView.loadMoreReplyComplete(activity.gson.fromJson((String)msg.obj,CustomCommentModel.class)); break; } } } } private final ActivityHandler activityHandler = new ActivityHandler(this); // @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.custom_use); gson=new Gson(); localServer=new LocalServer(this,"api2"); commentView=findViewById(R.id.commentView); //设置空视图 //commentView.setEmptyView(view); //设置错误视图 //commentView.setErrorView(view); //添加控件头布局 // commentView.addHeaderView(); commentView.setViewStyleConfigurator(new CustomViewStyleConfigurator(this)); commentView.callbackBuilder() //自定义评论布局(必须使用ViewHolder机制)--CustomCommentItemCallback 泛型C为自定义评论数据类 .customCommentItem(new CustomCommentItemCallback() { @Override public View buildCommentItem(int groupPosition, CustomCommentModel.CustomComment comment, LayoutInflater inflater, View convertView, ViewGroup parent) { //使用方法就像adapter里面的getView()方法一样 final CustomCommentViewHolder holder; if(convertView==null){ //使用自定义布局 convertView=inflater.inflate(R.layout.custom_item_comment,parent,false); holder=new CustomCommentViewHolder(convertView); //必须使用ViewHolder机制 convertView.setTag(holder); }else { holder= (CustomCommentViewHolder) convertView.getTag(); } holder.prizes.setText("100"); holder.userName.setText(comment.getPosterName()); holder.comment.setText(comment.getData()); return convertView; } }) //自定义评论布局(必须使用ViewHolder机制) // 并且自定义ViewHolder类必须继承自com.jidcoo.android.widget.commentview.view.ViewHolder // --CustomReplyItemCallback 泛型R为自定义回复数据类 .customReplyItem(new CustomReplyItemCallback() { @Override public View buildReplyItem(int groupPosition, int childPosition, boolean isLastReply, CustomCommentModel.CustomComment.CustomReply reply, LayoutInflater inflater, View convertView, ViewGroup parent) { //使用方法就像adapter里面的getView()方法一样 //此类必须继承自com.jidcoo.android.widget.commentview.view.ViewHolder,否则报错 CustomReplyViewHolder holder=null; //此类必须继承自com.jidcoo.android.widget.commentview.view.ViewHolder,否则报错 if(convertView==null){ //使用自定义布局 convertView=inflater.inflate(R.layout.custom_item_reply,parent,false); holder=new CustomReplyViewHolder(convertView); //必须使用ViewHolder机制 convertView.setTag(holder); }else { holder= (CustomReplyViewHolder) convertView.getTag(); } holder.userName.setText(reply.getReplierName()); holder.reply.setText(reply.getData()); holder.prizes.setText("100"); return convertView; } }) //下拉刷新回调 .setOnPullRefreshCallback(new MyOnPullRefreshCallback()) //评论、回复Item的点击回调(点击事件回调) .setOnItemClickCallback(new MyOnItemClickCallback()) //回复数据加载更多回调(加载更多回复) .setOnReplyLoadMoreCallback(new MyOnReplyLoadMoreCallback()) //上拉加载更多回调(加载更多评论数据) .setOnCommentLoadMoreCallback(new MyOnCommentLoadMoreCallback()) //设置完成后必须调用CallbackBuilder的buildCallback()方法,否则设置的回调无效 .buildCallback(); load(1,1); } private void load(int code,int handlerId){ localServer.get(code,activityHandler,handlerId); } /** * 下拉刷新回调类 */ class MyOnPullRefreshCallback implements OnPullRefreshCallback { @Override public void refreshing() { load(1,2); } @Override public void complete() { //加载完成后的操作 } @Override public void failure(String msg) { } } /** * 上拉加载更多回调类 */ class MyOnCommentLoadMoreCallback implements OnCommentLoadMoreCallback { @Override public void loading(int currentPage, int willLoadPage, boolean isLoadedAllPages) { //因为测试数据写死了,所以这里的逻辑也是写死的 if (!isLoadedAllPages){ if(willLoadPage==2){ load(2,3); }else if(willLoadPage==3){ load(3,3); } } } @Override public void complete() { //加载完成后的操作 } @Override public void failure(String msg) { } } /** * 回复加载更多回调类 */ class MyOnReplyLoadMoreCallback implements OnReplyLoadMoreCallback { @Override public void loading(CustomCommentModel.CustomComment.CustomReply reply, int willLoadPage) { if(willLoadPage==2){ load(5,4); }else if(willLoadPage==3){ load(6,4); } } @Override public void complete() { } @Override public void failure(String msg) { } } /** * 点击事件回调 */ class MyOnItemClickCallback implements OnItemClickCallback { @Override public void commentItemOnClick(int position, CustomCommentModel.CustomComment comment, View view) { Toast.makeText(CustomUseInLocalActivity.this,"你点击的评论:"+comment.getData(),Toast.LENGTH_SHORT).show(); } @Override public void replyItemOnClick(int c_position, int r_position, CustomCommentModel.CustomComment.CustomReply reply, View view) { Toast.makeText(CustomUseInLocalActivity.this,"你点击的回复:"+reply.getData(),Toast.LENGTH_SHORT).show(); } }}

3.4、总结

以上就是控件的使用方法和具体使用例子,更多用法可以查看测试应用的源码中的例子。到这里,文章就差不多要结尾了。

四、后话

哈哈,有人可能会问为什么要做这个控件呀?

因为前一段时间做项目的时候需要开发文章的评论功能,然后需要用到类似的控件,本来想在网上找找有没有开源的,但是全网搜遍以后没有发现合适的。所以就自己手动写了一个。

但是我用在项目中的是这个开源的CommentView再进行深度优化和深度定制过的。也就是说目前这个开源出来的版本其实是比较通用适配版本的,可以优化的地方还有很多很多很多。同时也存在不少BUG。还有那个测试用例WidgetTest也有很多可以优化的地方,只是现在没有太多时间去专门做优化,有空我会再去优化。大家有兴趣的可以一起到github优化这个控件,也可以提出对这个控件的一些问题、想法以及可优化的点。哈哈,欢迎大家一起来把这个开源控件库做的更好更强大。

后面有时间的话,这个开源控件库的安排:
1、优化
2、优化
3、扩大自定义开发的范围
4、结合市面上的APP评论模块开发更多通用的样式框架
5、添加更多功能(嵌套滑动等等更多实用功能)
6、更多的往架构为一个轮子发展,成为能够适配绝大多数需求的控件库
……
……
6、优化

有兴趣、有时间的可以一起加入这个项目的完善,非常欢迎大家呀!!

顺便提一下,目前用在我工作的项目中的版本显示10000+的数据性能都挺高的,在大数据量时内存开销也稳定在一个相对的值。毕竟是在这个开源版本基础上做了很多的深度优化。所以说,所以说,如果使用的场景是数据量比较大的情况下,优化是很有必要的哦,不然数据量比较大很容易OOM。

五、关于作者

我叫Jidcoo,一名理工男,一个专注于Java后端同时又热爱Android开发的花心大萝卜。我热爱阅读、热爱算法、热爱分享……喜欢交流、喜欢团队合作。

有任何有关这个项目或者有任何其他问题、想法,都可以POST我:

我的邮箱:[email protected]

我的GitHub: https://github.com/Jidcoo/

更多相关文章

  1. 用Fiddler可以设置浏览器的UA 和 手动 --Chrome模拟手机浏览器(iO
  2. Android(安卓)7.1.1 通话记录数据库详解
  3. 关于真机测试的android选择图片在ImageView上显示,调用setImageBi
  4. android 使用aapt方法
  5. android碎片Fragment简介详解
  6. android 控件之ProgressBar实现双进度条(通常用于流媒体的缓冲区
  7. Android(安卓)Ap 开发 设计模式第三篇:模板方法模式
  8. 超级简单的android通过view自带的方法进行裁剪视图,实现圆形,矩形
  9. Android中自定义View的研究(四) -- 在XML中定义View

随机推荐

  1. android开发之interpolator的用法详解
  2. 初学者之Android(安卓)HelloWorld项目
  3. Android(安卓)context源码详解及深入分析
  4. Android的两种数据存储方式分析(一)
  5. Android简单类型资源(Bool、Color、Dimens
  6. android jni介绍
  7. 解决Your content must have a ListView
  8. Android用户注册界面
  9. android TextView 不显示文字
  10. android实现视频播放的几种方式