@TOCjava
CommentView是一个简洁、高效、可自定义的开源的Android评论控件,支持盖楼评论回复模式、下拉刷新评论、上拉加载更多评论、加载更多回复等功能,可自定义(样式、数据模型、布局)、可轻松按需求扩展功能。android
功能特色 |
---|
支持下拉刷新评论 |
支持上拉加载更多评论 |
支持加载更多回复 |
支持盖楼评论回复模式 |
可设置空数据视图、错误视图 |
支持自定义数据模型 |
支持自定义样式配置器 |
支持自定义布局 |
GIF压缩后效果变差,想亲自体验能够到下面的仓库下载安装包。想要什么的样式效果能够自定义实现git
默认样式效果(GIF)github
自定义样式效果(自定义仿微信公众号评论)(GIF)算法
仓库 | 网址 |
---|---|
码云 | https://gitee.com/jidcoo/CommentView |
GitHub | https://github.com/Jidcoo/CommentView |
Jcenter(Maven) | https://bintray.com/longyun/maven/CommentView |
CommentView只负责对外提供业务方法,全部的业务实现和核心代码都在com.jidcoo.android.widget.commentview.operator包和com.jidcoo.android.widget.commentview.adapter包。com.jidcoo.android.widget.commentview.callback包是业务和控件的桥接包。
总体框架json
/** * 分页模型<br> * <br> * 在Android端只实现为对分页数据的Get/Set操做<br> * <br> * 具体的分页逻辑应该由后端完成,客户端只负责对数据的处理<br> * <br> * @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类的全部类对于的数据都要包含相关分页数据字段。api
/** * 自定义回复模型必须继承自此类 * @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; ..... 设置更多具体的属性,好比时间、点赞数之类的数据 ..... }
/** * <p>自定义评论模型抽象类</p> * 自定义评论模型必须继承自此抽象类 * @author Jidcoo */ public abstract class CommentEnable extends PagerEnable{ /** * 必须实现该方法并确保返回值非NULL * @return */ public abstract <R extends ReplyEnable> List<R> getReplies(); }
CommentEnable是一个继承自PagerEnable类的抽象类。继承自PagerEnable类的惟一目的就是用来控制回复数据的分页功能,因此继承自此类的实体类就具能够支持所在评论的回复数据的分页加载。
自定义评论数据模型必须继承自CommentEnable类并实现getReplies()抽象方法。getReplies()方法很是重要,必须实现。该方法返回一个List<? extend ReplyEnable>的list,这个list的元素是继承自ReplyEnable的自定义的回复数据模型实体类。如第二点中的例子Reply类。
自定义评论数据模型例子:
/** * 首先继承自CommentEnable类 * 而后实现抽象方法getReplies(),返回一个回复数据的List * 而后设置回复模型中所须要的数据就能够了 */ public class Comment extends CommentEnable{ public List<Reply> replies; public String userName; public String comment; ..... 设置更多具体的属性,好比时间、点赞数之类的数据 ..... /*@Override public List<R> getReplies() { return null; }*/ //必须实现getReplies()方法。 @Override public List<Reply> getReplies() { return replies; } /** * 首先继承自ReplyEnable类 * 而后设置回复模型中所须要的数据就能够了 */ public class Reply extends ReplyEnable { public String userName; public String reply; ..... 设置更多具体的属性,好比时间、点赞数之类的数据 ..... } }
注意:CommentEnable类中getReplies()的原型是这样的:
@Override public List<R> getReplies() { return null; }
因此实现getReplies()方法的时候只须要把泛型R改成本身定义的回复数据模型类就能够了,而后在方法中返回这个实体类的集合。
/** * 数据抽象模型<p></p> * 自定义模型必须继承此抽象模型,并实现其中的方法<p></p> * <u>注意:使用自定义数据类型就必须自定义布局实现,不然会抛出数据模型的java.lang.ClassCastException异常</u><br></br> * AbstractCommentModel中传入的泛型<C extends CommentEnable>必须继承自CommentEnable,查看{@link CommentEnable} * @param <C> 自定义的评论数据模型 * @author Jidcoo */ public abstract class AbstractCommentModel <C extends CommentEnable> extends PagerEnable{ /** * 必须实现该方法并确保返回值非NULL * @return */ public abstract List<C> 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> * 其中泛型C用自定义的评论模型类替换就能够了 * 而后实现抽象方法getComments(),返回一个评论数据的List */ public class CommentModel extends AbstractCommentModel<CommentModel.Comment> { public List<Comment> comments; //必须实现getComments()方法并确保返回值非NULL @Override public List<Comment> getComments() { return comments; } /** * 首先继承自CommentEnable类 * 而后实现抽象方法getReplies(),返回一个回复数据的List * 而后设置回复模型中所须要的数据就能够了 */ public class Comment extends CommentEnable{ public String userName; public String comment; public List<Reply> replies; /*@Override public List<R> getReplies() { return null; }*/ @Override public List<Reply> getReplies() { return replies; } /** * 首先继承自ReplyEnable类 * 而后设置回复模型中所须要的数据就能够了 */ public class Reply extends ReplyEnable { public String userName; public String reply; } } }
在自定义模型中要继承自抽象泛型类AbstractCommentModel < C extends CommentEnable>
public class CommentModel extends AbstractCommentModel<C>
@Override public List<C> getComments() { return null; }
这里的泛型C用自定义的评论数据模型类(如第三点的例子Comment类)替换就能够了。
其实在自定义模型类中作的事情并很少,由于须要的Comment类、Reply类以前都已经自定义好了。因此如今就只须要在自定义模型类中继承自 AbstractCommentModel<C>,并把泛型C替换成本身Comment类,最后实现抽象方法getComments()就能够了。注意:getComments()方法必须实现并返回对应的List,被确保返回的这个List非null。
好了,到如今为止,CommentView控件的数据模型就分析完了。好长篇幅。为何呢?由于这个数据模型对于整个框架来讲过重要了。必须理解好数据模型才能轻松使用这个控件。因此必须详细分析整个数据模型。
ViewHolder类是一个抽象类,是对于自定义回复布局的时候使用的,自定义评论布局不须要继承自此类。ViewHolder位于com.jidcoo.android.widget.commentview.view包下。
自定义评论布局ViewHolder类没有任何限制和要求,不须要任何继承。因此这里不展现例子代码了,例子能够查看defaults包下的DefaultCommentHolder类。
可是对于自定义回复布局,有四点必须(不按要求就报错):
一、必须使用ViewHolder机制。
二、使用的ViewHolder必须继承自com.jidcoo.android.widget.commentview.view包下ViewHolder抽象类。
三、自定义的回复布局中最外层布局必须为LinearLayout。
四、而且这个最外层的LinearLayout的android:id属性必须设置为“reply_rootView”,即android:id="@+id/reply_rootView"。
这四点必须怎么来的呢,下面看ViewHolder类的代码:
/** * 自定义回复布局必须继承此抽象类并在构造方法中调用父类构造方法,不然会报错。<p> * <br></br> * 在自定义回复布局中最外层必须使用LinearLayout并将布局id设置为“reply_rootView”,不然会报错。<p> * @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); } }
/** * 自定义评论布局的回调<p></p> * 至关于Adapter的getView()方法<p></p> * 与Adapter的getView()方法使用同样<p></p> * 泛型接口中的泛型对应评论数据模型,即继承在CommentEnable,查看{@link CommentEnable} * @author Jidcoo */ public interface CustomCommentItemCallback<C extends CommentEnable> { /** * 至关于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<C>回调 * 把自定义评论模型Comment放入泛型参数中 * 而后实现buildCommentItem()方法进行自定义布局 */ public class CustomCommentItem implements CustomCommentItemCallback<Comment> { @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; } }
/** * 自定义回复布局的回调<p></p> * 至关于Adapter的getView()方法<p></p> * 与Adapter的getView()方法使用同样<p></p> * 泛型接口中的泛型对应回复数据模型,即继承在ReplyEnable,查看{@link ReplyEnable}<p></p> * 注意: * <br></br> * 布局:<p> * 一、自定义布局xml中最外层布局必须是LinearLayout而且把最外层布局id设置为reply_rootView<p> * ViewHolder:<p> * 一、在buildReplyItem()中自定义布局初始化时,必须使用ViewHolder机制<p> * 二、初始化过程当中,自定义Holder必须继承自com.jidcoo.android.widget.commentview.view.ViewHolder,查看{@link ViewHolder}<p> * @author Jidcoo */ public interface CustomReplyItemCallback<R extends ReplyEnable> { /** * 至关于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()方法时有四点必须(不按要求就报错):
一、必须使用ViewHolder机制。
二、使用的ViewHolder必须继承自com.jidcoo.android.widget.commentview.view包下ViewHolder抽象类。
三、自定义的回复布局中最外层布局必须为LinearLayout。
四、而且这个最外层的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<R>回调 * 把自定义回复模型Reply放入泛型参数中 * 而后实现buildReplyItem()方法进行自定义布局 */ public class CustomReplyItem implements CustomReplyItemCallback<Reply> { @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; } }
/** * 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()方法中传进来的错误信息 } }
/** * 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的例子基本一致。具体使用例子能够查看相关源码。
/** * CommentView加载更多回复回调<p></p> * 泛型接口中的泛型R是回复数据模型,必须继承自ReplyEnable,查看{@link ReplyEnable} * @author Jidcoo */ public interface OnReplyLoadMoreCallback<R extends ReplyEnable> { /** * 加载更多回复回调 * @param reply 对应回复的数据 * @param willLoadPage */ void loading(R reply, int willLoadPage); /** * 加载更多回复完成回调 */ void complete(); /** * 加载失败 * @param msg 错误信息 */ void failure(String msg); }
OnReplyLoadMoreCallback是一个泛型回调,是加载更多回复回调,非必须回调。OnReplyLoadMoreCallback< R extends ReplyEnable>接受一个继承自ReplyEnable类的自定义回复数据模型类。具体使用例子能够查看相关源码。
/** * 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提供了控件的全部滚动事件的回调,非必须回调。能够经过实现这个回调来监听控件滚动事件。
/** * CommentView中评论Item和回复Item点击事件回调 * 泛型接口中的C、R泛型:<p></p> * 一、C是评论数据模型,必须继承自CommentEnable,查看{@link CommentEnable}<p></p> * 二、R是回复数据模型,必须继承自ReplyEnable,查看{@link ReplyEnable}<p></p> * @author Jidcoo */ public interface OnItemClickCallback<C extends CommentEnable,R extends ReplyEnable> { /** * 评论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 extends CommentEnable,R extends ReplyEnable>中泛型C、R对应的就是自定义评论数据模型和回复数据模型。当控件中评论Item和回复Item被点击时,回调中相对应的commentItemOnClick和replyItemOnClick方法就会被调用。
须要监听控件Item的点击事件就实现这个接口。具体使用例子能够查看相关源码。
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();
好了,到如今为止,CommentView控件的回调类基本介绍完成。写的时候感受十分啰嗦,但最后仍是写下来了。由于这些回调类虽然都是非必须回调,可是想实现更多功能都须要用到这些回调。
/** * 非可自定义控件样式配置器 * <p>该样式配置器针对非自定义控件的样式的具体配置</p> * <p>可根据具体需求配置这部分的控件的样式</p> * <p>自定义样式配置器必须继承自本类</p> * <br></br> * <h3>样式变量说明:</h3> * <p>一、refreshViewColor </p> * <p>类型:String</p> * <p>说明:下拉刷新后出现的圆形滚动条的颜色</p> * <p>赋值样例:#000000</p> * <p>二、lmv_showText(正常显示状态) </p> * <p>类型:String</p> * <p>说明:当回复数据Item能够加载更多(分页加载)时,在最后一条回复Item的View下面显示的文字</p> * <p>赋值样例:展开更多回复</p> * <p>三、lmv_textSize </p> * <p>类型:int</p> * <p>说明:第二条说明中的文字大小</p> * <p>赋值样例:14</p> * <p>四、lmv_textColor </p> * <p>类型:String</p> * <p>说明:第二条说明中的文字颜色</p> * <p>赋值样例:#000000</p> * <p>五、lmv_textStyle </p> * <p>类型:Typeface,查看{@link Typeface}</p> * <p>说明:第二条说明中的文字样式(正常、斜体、加粗)</p> * <p>赋值样例:Typeface.defaultFromStyle(Typeface.NORMAL)</p> * <p>六、lmv_loading_showText(加载中状态) </p> * <p>类型:String</p> * <p>说明:当回复数据Item能够加载更多(分页加载)时,在最后一条回复Item的加载更多View被点击后显示的文字</p> * <p>赋值样例:加载中</p> * <p>七、lmv_loading_textSize </p> * <p>类型:int</p> * <p>说明:第六条说明中的文字大小</p> * <p>赋值样例:14</p> * <p>八、lmv_loading_textColor </p> * <p>类型:String</p> * <p>说明:第六条说明中的文字颜色</p> * <p>赋值样例:#000000</p> * <p>九、lmv_loading_textStyle </p> * <p>类型:Typeface,查看{@link Typeface}</p> * <p>说明:第六条说明中的文字样式(正常、斜体、加粗)</p> * <p>赋值样例:Typeface.defaultFromStyle(Typeface.NORMAL)</p> * <p>十、lmv_loading_progressBarColor </p> * <p>类型:String</p> * <p>说明:当最后一条回复ItemView的可加载更多View状态为正在加载状态时, * 第六条说明中的文字右边会显示一个圆形progressBar以加强UI加载效果, * 该属性是这个progressBar的颜色值</p> * <p>赋值样例:#000000</p> * <p>十一、lmv_loading_progressBarSize </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:第十条说明中的progressBar控件的大小尺寸</p> * <p>赋值样例:ViewUtil.dpToPx(14)</p> * <p>十二、lmv_adjustMargins </p> * <p>类型:Boolean(默认为false)</p> * <p>说明:是否调整最后一条回复Item的加载更多View的边距,若是自定义回复布局,可能须要 * 调整这个View的边距来使布局更美观,通常状况下都须要调整边距,因此这个属性通常都为true</p> * <p>赋值样例:true</p> * <p>1三、lmv_adjustMarginsLeft </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:第十二条说明中的view的左边距,若是lmv_adjustMargins为false,则此属性无效</p> * <p>赋值样例:ViewUtil.dpToPx(14)</p> * <p>1四、lmv_adjustMarginsTop </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:第十二条说明中的view的上边距,若是lmv_adjustMargins为false,则此属性无效</p> * <p>赋值样例:ViewUtil.dpToPx(14)</p> * <p>1五、lmv_adjustMarginsRight </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:第十二条说明中的view的右边距,若是lmv_adjustMargins为false,则此属性无效</p> * <p>赋值样例:ViewUtil.dpToPx(14)</p> * <p>1六、lmv_adjustMarginsBottom </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:第十二条说明中的view的下边距,若是lmv_adjustMargins为false,则此属性无效</p> * <p>赋值样例:ViewUtil.dpToPx(14)</p> * <p>1七、dividerColor </p> * <p>类型:String</p> * <p>说明:控件Item的分割线颜色(包含一级分割线和二级分割线)</p> * <p>赋值样例:#000000</p> * <p>1八、dividerHeight </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:控件Item的分割线高度(包含一级分割线和二级分割线)</p> * <p>赋值样例:ViewUtil.dpToPx(1),高度通常为1dp</p> * <p>1九、isDrawChildDivider </p> * <p>类型:Boolean(默认为false)</p> * <p>说明:是否绘制某一条评论下的最后一条回复Item与下一条评论的Item的<u>分割线</u>,默认不绘制, * 可根据具体须要选择是否绘制</p> * <p>赋值样例:true</p> * <p>20、c_divider_adjustMargins </p> * <p>类型:Boolean(默认为false)</p> * <p>说明:是否调整第十七条说明中的分割线的边距,若是isDrawDivider为false,则此属性无效。 * 由于第十九条说明中的分割线宽度固定为match_parent(即铺满全屏),可是能够经过调整分割线的边距 * 来间接调整分割线的宽度和位置,可根据具体须要选择是否调整分割线的边距</p> * <p>赋值样例:true</p> * <p>2一、c_divider_adjustMarginsLeft </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:第十九条说明中的分割线的左边距,若是divider_adjustMargins为false,则此属性无效</p> * <p>赋值样例:ViewUtil.dpToPx(1)</p> * <p>2二、c_divider_adjustMarginsTop </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:第十九条说明中的分割线的上边距,若是divider_adjustMargins为false,则此属性无效</p> * <p>赋值样例:ViewUtil.dpToPx(1)</p> * <p>2三、c_divider_adjustMarginsRight </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:第十九条说明中的分割线的右边距,若是divider_adjustMargins为false,则此属性无效</p> * <p>赋值样例:ViewUtil.dpToPx(1)</p> * <p>2四、c_divider_adjustMarginsBottom </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:第十九条说明中的分割线的下边距,若是divider_adjustMargins为false,则此属性无效</p> * <p>赋值样例:ViewUtil.dpToPx(1)</p> * <p>2五、f_divider_adjustMargins </p> * <p>类型:Boolean(默认为false)</p> * <p>说明:是否调整评论item的分割线的边距</p> * <p>赋值样例:true</p> * <p>2六、f_divider_adjustMarginsLeft </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:评论item的分割线的左边距</p> * <p>赋值样例:ViewUtil.dpToPx(1)</p> * <p>2七、f_divider_adjustMarginsTop </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:评论item的分割线的上边距</p> * <p>赋值样例:ViewUtil.dpToPx(1)</p> * <p>2八、f_divider_adjustMarginsRight </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:评论item的分割线的右边距</p> * <p>赋值样例:ViewUtil.dpToPx(1)</p> * <p>2九、f_divider_adjustMarginsBottom </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:评论item的分割线的下边距</p> * <p>赋值样例:ViewUtil.dpToPx(1)</p> * <p>30、lm_footerProgressBarSize </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:上拉刷新时,控件底部会有一个圆形ProgressBar来增长加载中的UI效果,此属性为ProgressBar的大小</p> * <p>赋值样例:ViewUtil.dpToPx(1)</p> * <p>3一、lm_footerProgressBarColor </p> * <p>类型:String</p> * <p>说明:第三十条说明中的ProgressBar的颜色</p> * <p>赋值样例:#000000</p> * <p>3二、lm_footerProgressBar_adjustMargins </p> * <p>类型:Boolean(默认为false)</p> * <p>说明:是否调整第三十条说明中的ProgressBar的边距</p> * <p>赋值样例:true</p> * <p>3三、lm_footerProgressBar_adjustMarginsLeft </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:第三十条说明中的ProgressBar的左边距,若是lm_footerProgressBar_adjustMargins为false,则此属性无效</p> * <p>赋值样例:ViewUtil.dpToPx(14)</p> * <p>3四、lm_footerProgressBar_adjustMarginsTop </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:第三十条说明中的ProgressBar的上边距,若是lm_footerProgressBar_adjustMargins为false,则此属性无效</p> * <p>赋值样例:ViewUtil.dpToPx(14)</p> * <p>3五、lm_footerProgressBar_adjustMarginsRight </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:第三十条说明中的ProgressBar的右边距,若是lm_footerProgressBar_adjustMargins为false,则此属性无效</p> * <p>赋值样例:ViewUtil.dpToPx(14)</p> * <p>3六、lm_footerProgressBar_adjustMarginsBottom </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:第三十条说明中的ProgressBar的下边距,若是lm_footerProgressBar_adjustMargins为false,则此属性无效</p> * <p>赋值样例:ViewUtil.dpToPx(14)</p> * <p>3七、lm_footer_text</p> * <p>类型:String</p> * <p>说明:当全部数据加载完成后控件底部textView显示的文字</p> * <p>赋值样例:哈哈,全部评论都在这了</p> * <p>3八、lm_footer_textColor</p> * <p>类型:String</p> * <p>说明:第三十七条说明中的textView文字的颜色</p> * <p>赋值样例:#666666</p> * <p>3九、lm_footer_textSize</p> * <p>类型:int</p> * <p>说明:第三十七条说明中的textView文字的大小</p> * <p>赋值样例:14</p> * <p>40、lm_footer_textStyle</p> * <p>类型:Typeface</p> * <p>说明:第三十七条说明中的textView文字的样式(正常,斜体、加粗)</p> * <p>赋值样例:Typeface.defaultFromStyle(Typeface.NORMAL)</p> * <p>4一、lm_footer_text_adjustMargins </p> * <p>类型:Boolean(默认为false)</p> * <p>说明:是否调整第三十七条说明中的textView的边距</p> * <p>赋值样例:true</p> * <p>4二、lm_footer_text_adjustMarginsLeft </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:第三十七条说明中的textView的左边距,若是lm_footer_text_adjustMargins为false,则此属性无效</p> * <p>赋值样例:ViewUtil.dpToPx(14)</p> * <p>4三、lm_footer_text_adjustMarginsTop </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:第三十七条说明中的textView的上边距,若是lm_footer_text_adjustMargins为false,则此属性无效</p> * <p>赋值样例:ViewUtil.dpToPx(14)</p> * <p>4四、lm_footer_text_adjustMarginsRight </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:第三十七条说明中的textView的右边距,若是lm_footer_text_adjustMargins为false,则此属性无效</p> * <p>赋值样例:ViewUtil.dpToPx(14)</p> * <p>4五、lm_footer_text_adjustMarginsBottom </p> * <p>类型:int(要求单位:px,由于须要把(dp/dip值转换为px值))</p> * <p>说明:第三十七条说明中的textView的下边距,若是lm_footer_text_adjustMargins为false,则此属性无效</p> * <p>赋值样例:ViewUtil.dpToPx(14)</p> * <br><br> * @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类)
/** * 默认样式配置器 * <p>此配置器为标准模板</p> * <p>自定义样式配置器必须继承自ViewStyleConfigurator,查看{@link ViewStyleConfigurator}</p> * <p>样式变量说明,请查看{@link ViewStyleConfigurator}</p> * <p>实际上,自定义样式配置器能够直接复制本类,而后再根据具体把样式修改就行了</p> * @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//*************// } }
超长的篇幅解释说明使用控件中须要用到的框架类,能够说是使用CommentView的理论部分。超长超啰嗦,可是这些说明都是很是重要的,也是必须的。为何这么说呢?由于上面的这些框架类的说明和例子都是在使用CommentView过程当中业务层须要用到的,也就是说理解了上面长篇大论的解释后,使用CommentView就很是简单了。
因此尽管十分啰嗦,可是有些东西须要特别说明一下。以上都是理论部分(十分枯燥、又长又臭……)。有了上述这些对整个控件框架的总体了解,使用这个控件库就很是容易了。
注意:项目是使用AndroidX库开发,部分控件是AndroidX库的控件,若是你的项目没有迁移到AndroidX库开发,能够把CommentView控件库中的那些AndroidX库控件修改成你当前项目的Android库的控件就能够了。
下面就要讲CommentView实际使用部分了(理解了上面的理论,说实话使用没有任何技术含量)。
如下是控件对外公布的全部业务方法。
方法 | 参数 | 说明 | |
---|---|---|---|
.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()方法调用前调用,也就是说要在首次加载数据前调用,不然此方法无效。 |
第一步:引入控件库:
有两种方法:
一、远程仓库
在module的build.gradle中添加jcenter仓库:
buildscript { repositories { jcenter() } }
而后在dependencies模块中添加依赖便可:
implementation 'com.jidcoo.android.widget.commentview:CommentView:1.0.0'
二、本地仓库
把源码包下载下来,把commentview库放在与当前module的同级。
而后在dependencies模块中添加本地依赖便可:
implementation project(path: ':commentview')
第二步:引入控件:
控件的引入方法有两种:
一、XML布局文件中引入
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:id="@+id/container" android:layout_height="match_parent"> <com.jidcoo.android.widget.commentview.CommentView android:id="@+id/commentView" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>
而后在Activity中实例化控件。
二、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()方法调用以前(即首次加载数据以前)完成,不然控件将没法正常使用。
支持的回调:
一、CustomCommentItemCallback:自定义评论布局回调
二、CustomReplyItemCallback:自定义回复布局回调
三、OnPullRefreshCallback:上拉刷新回调
四、OnCommentLoadMoreCallback:下拉加载更多评论回调
五、OnReplyLoadMoreCallback:加载更多回复回调
六、OnItemClickCallback:Item的点击事件回调
七、OnScrollCallback:控件滚动事件回调
当须要设置回调时:
设置完回调后必须调用.buildCallback()方法,不然回调不会生效,控件也没法正常使用。
commentView.callbackBuilder() .setOnPullRefreshCallback(你的回调实例) .onItemClickCallback(你的回调实例) ......设置更多回调 ......设置更多回调 //设置完成后必须调用CallbackBuilder的buildCallback()方法,不然设置的回调无效,控件也没法正常显示。 //不管是否设置回调,buildCallback()方法都必须调用。 .buildCallback();
当不须要设置回调时:
必须调用.buildCallback()方法,不然控件也没法正常使用。
commentView.callbackBuilder().buildCallback();
第五步:设置数据:
当全部的初始化工做都完成后,就能够请求后台返回评论数据或加载本地数据为控件设置数据了。完成设置数据后,控件就能正确显示评论数据了。
commentView.loadComplete(你的数据模型实体类);
布局文件:custom_use.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:background="#f0f0f0" android:layout_height="match_parent"> <com.jidcoo.android.widget.commentview.CommentView android:id="@+id/commentView" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>
自定义评论布局:custom_item_comment.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <com.jidcoo.android.widgettest.custom.SampleCircleImageView android:id="@+id/ico" android:layout_width="28dp" android:layout_height="28dp" android:layout_marginLeft="14dp" android:layout_marginTop="18dp" app:radius="2dp" app:src="@drawable/teaser" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:layout_marginRight="14dp" android:layout_marginBottom="4dp"> <TextView android:id="@+id/user" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:textColor="#666666" android:textSize="13sp" /> <ImageView android:id="@+id/comment_item_like" android:layout_width="18dp" android:layout_height="18dp" android:layout_alignParentRight="true" android:layout_marginRight="30dp" android:padding="3dp" android:src="@drawable/pxjh" android:visibility="visible" /> <TextView android:id="@+id/prizes" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:textColor="#666666" android:textSize="13sp" /> <TextView android:id="@+id/data" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:layout_marginTop="18dp" android:textColor="#1C1C1C" /> </RelativeLayout> </LinearLayout>
自定义回复布局:custom_item_reply.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:id="@+id/reply_rootView" android:layout_height="wrap_content" android:orientation="vertical"> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <View android:id="@+id/line" android:layout_width="2dp" android:layout_height="wrap_content" android:layout_alignTop="@id/right" android:layout_alignBottom="@id/right" android:layout_marginLeft="52dp" android:layout_marginTop="2dp" android:layout_marginBottom="2dp" android:background="#c3c8cb" /> <RelativeLayout android:id="@+id/right" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:layout_marginRight="14dp" android:layout_marginBottom="4dp" android:layout_toRightOf="@id/line"> <TextView android:id="@+id/user" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="6dp" android:textColor="#666666" android:textSize="13sp" /> <ImageView android:id="@+id/comment_item_like" android:layout_width="18dp" android:layout_height="18dp" android:layout_alignParentRight="true" android:layout_marginRight="30dp" android:padding="3dp" android:src="@drawable/pxjh" android:visibility="visible" /> <TextView android:id="@+id/prizes" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:textColor="#666666" android:textSize="13sp" /> <TextView android:id="@+id/data" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="6dp" android:layout_marginTop="18dp" android:textColor="#1C1C1C" /> </RelativeLayout> </RelativeLayout> </LinearLayout>
自定义数据模型: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<CustomCommentModel.CustomComment> { public List<CustomComment> comments; @Override public List<CustomComment> getComments() { return comments; } public class CustomComment extends CommentEnable{ public List<CustomReply> replies; public String posterName; public String data; public CustomComment() { } public void setReplies(List<CustomReply> 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<CustomReply> 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的自定义数据类型和布局的使用实例(使用本地测试数据) * 使用自定义样式配置器,自定义数据模型,自定义布局 * <u>注意:使用自定义数据类型就必须自定义布局实现,不然会抛出数据模型的java.lang.ClassCastException异常</u><br></br> * @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<CustomUseInLocalActivity> mActivity; public ActivityHandler(CustomUseInLocalActivity activity) { mActivity = new WeakReference<CustomUseInLocalActivity>(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> 泛型C为自定义评论数据类 .customCommentItem(new CustomCommentItemCallback<CustomCommentModel.CustomComment>() { @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> 泛型R为自定义回复数据类 .customReplyItem(new CustomReplyItemCallback<CustomCommentModel.CustomComment.CustomReply>() { @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<CustomCommentModel.CustomComment.CustomReply> { @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<CustomCommentModel.CustomComment, CustomCommentModel.CustomComment.CustomReply> { @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(); } } }
以上就是控件的使用方法和具体使用例子,更多用法能够查看测试应用的源码中的例子。到这里,文章就差很少要结尾了。
哈哈,有人可能会问为何要作这个控件呀?
由于前一段时间作项目的时候须要开发文章的评论功能,而后须要用到相似的控件,原本想在网上找找有没有开源的,可是全网搜遍之后没有发现合适的。因此就本身手动写了一个。
可是我用在项目中的是这个开源的CommentView再进行深度优化和深度定制过的。也就是说目前这个开源出来的版本实际上是比较通用适配版本的,能够优化的地方还有不少不少不少。同时也存在很多BUG。还有那个测试用例WidgetTest也有不少能够优化的地方,只是如今没有太多时间去专门作优化,有空我会再去优化。你们有兴趣的能够一块儿到github优化这个控件,也能够提出对这个控件的一些问题、想法以及可优化的点。哈哈,欢迎你们一块儿来把这个开源控件库作的更好更强大。
后面有时间的话,这个开源控件库的安排:
一、优化
二、优化
三、扩大自定义开发的范围
四、结合市面上的APP评论模块开发更多通用的样式框架
五、添加更多功能(嵌套滑动等等更多实用功能)
六、更多的往架构为一个轮子发展,成为可以适配绝大多数需求的控件库
……
……
六、优化
有兴趣、有时间的能够一块儿加入这个项目的完善,很是欢迎你们呀!!
顺便提一下,目前用在我工做的项目中的版本显示10000+的数据性能都挺高的,在大数据量时内存开销也稳定在一个相对的值。毕竟是在这个开源版本基础上作了不少的深度优化。因此说,因此说,若是使用的场景是数据量比较大的状况下,优化是颇有必要的哦,否则数据量比较大很容易OOM。
我叫Jidcoo,一名理工男,一个专一于Java后端同时又热爱Android开发的花心大萝卜。我热爱阅读、热爱算法、热爱分享……喜欢交流、喜欢团队合做。
有任何有关这个项目或者有任何其余问题、想法,均可以POST我:
个人邮箱:jidcoo@163.com
个人GitHub: https://github.com/Jidcoo/