背景介绍:
在做一个项目的时候,有这么一个需求,需要请求服务器然后展现图片,
类似于一个美图展现的需求吧,具体项目细节就不进行细说了;
服务端采用的是:nodejs基于国内优秀的Thinkjs框架;
客户端:Android;
下面对Android客户端的问题进行归纳总结,后面再总结服务端的。
需求功能点如下:
1. 支持分页查询,上拉查找更多,有数据时显示转圈加载更多,无数据时进行提示;
2. 采用爆布流的方式进行显示,同时图片的高度随机;
整体思路
1. 采用oKHttp 第三方开源库进行接口数据请求;
2. 使用RecyclerView + StaggeredGridLayoutManager 进行爆布流的UI实现;
常见问题:
1.RecyclerView 如何实现下拉加载更多;
2.StaggeredGridLayoutManager 显示加载更多的时候,加载更多作为最后一个item没有单独占满屏幕宽度,只显示为一个item的宽度;
3.StaggeredGridLayoutManager 如何随机设置item的高度;
4.StaggeredGridLayoutManager 上拉加载数据刷新UI时,由于高度随机,造成页面item抖动问题;
5.RecyclerView莫名的Inconsistency detected崩溃;
开始填坑:
1. recyclerView 如何实现下拉加载更多:
思路:
相关代码:
监听recyclerView滑动事件,并且计算得出是否滑动到最后一个item
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
staggeredGridLayoutManager.invalidateSpanAssignments(); //防止第一行到顶部有空白区域
currentScrollState = newState;
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
if ((visibleItemCount > 0 && currentScrollState == RecyclerView.SCROLL_STATE_IDLE && (lastVisibleItemPosition) >= totalItemCount - 1)) {
Log.i(TAG, "onScrollStateChanged: ...");
requestMoreData();//请求更多数据
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManagerType == null) {
if (layoutManager instanceof LinearLayoutManager) {
layoutManagerType = LayoutManagerType.LinearLayout;
} else if (layoutManager instanceof GridLayoutManager) {
layoutManagerType = LayoutManagerType.GridLayout;
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
layoutManagerType = LayoutManagerType.StaggeredGridLayout;
} else {
throw new RuntimeException(
"Unsupported LayoutManager used. Valid ones are LinearLayoutManager, GridLayoutManager and StaggeredGridLayoutManager");
}
}
switch (layoutManagerType) {
case LinearLayout:
lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
break;
case GridLayout:
lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
break;
case StaggeredGridLayout:
StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
if (lastPositions == null) {
lastPositions = new int[staggeredGridLayoutManager.getSpanCount()];
}
staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions);
lastVisibleItemPosition = findMax(lastPositions);
break;
default:
break;
}
}
});
/**
* 取数组中最大值
*
* @param lastPositions
* @return
*/
private int findMax(int[] lastPositions) {
int max = lastPositions[0];
for (int value : lastPositions) {
if (value > max) {
max = value;
}
}
return max;
}
public static enum LayoutManagerType {
LinearLayout,
StaggeredGridLayout,
GridLayout
}
RecyclerView.Adapter的相关代码,主要定义两个ViewHolder类型,TYPE_BOTTOM 表示为底部的viewHolder,TYPE_NOMAL表示为正常的item的viewHolder,根据position的不同来显示不同的viewholder;
代码中还有对上面StaggeredGridLayoutManager的问题处理,具体的下面细说:
public class XXXRcyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_BOTTOM = 0;
private static final int TYPE_NOMAL = 1;
private LayoutInflater inflater;
private Context mContext;
private int preNumb = 0;
private boolean isLoading = false;
************此处省略部分代码
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_BOTTOM){
return new BottomViewHolder(inflater.inflate(R.layout.item_type_bottom,parent,false));
}
return new NomalViewHolder(inflater.inflate(R.layout.item_qr_code_say_rcy,parent,false));
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
if (holder instanceof NomalViewHolder){
NomalViewHolder nomalViewHolder = (NomalViewHolder) holder;
*********此处省略部分代码***********
ViewGroup.LayoutParams lp = nomalViewHolder.imgView.getLayoutParams();
int scale = new Random().nextInt(10);
if (scale < 5){
scale = 9;
}
lp.height= (int) (UIUtil.dp2px(mContext,200) *0.1 * scale);
lp.width= RecyclerView.LayoutParams.MATCH_PARENT;
nomalViewHolder.imgView.setLayoutParams(lp);
*********以上对每个Imageview设置随机高度*********
}else if (holder instanceof BottomViewHolder){
BottomViewHolder bottomViewHolder = (BottomViewHolder) holder;
if (isLoading){
bottomViewHolder.bottomContainer.setVisibility(View.VISIBLE);
bottomViewHolder.imgView.setVisibility(View.VISIBLE);
bottomViewHolder.textView.setVisibility(View.VISIBLE);
/***提示无更多数据***/
}else{
bottomViewHolder.bottomContainer.setVisibility(View.VISIBLE);
bottomViewHolder.imgView.setVisibility(View.GONE);
bottomViewHolder.textView.setVisibility(View.VISIBLE);
/***提示无更多数据***/
}
}
}
@Override
public int getItemCount() {
return xxxxList.size() + 1;
}
/**
* 重写viewType的返回值
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {
if (position < qrCodeSayBeanList.size()){
return TYPE_NOMAL;
}
return TYPE_BOTTOM;
}
/**
* 解决 GridLayoutManager 加载更多时,加载动画最后一个item没有占满屏幕宽度问题
* @param recyclerView
*/
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if (manager instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return isFooter(position) ? gridManager.getSpanCount() : 1;
}
});
}
}
/**
* 解决 StaggeredGridLayoutManager 加载更多时,加载动画导致最后一个item没有占满屏幕宽度问题
* @param holder
*/
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
super.onViewAttachedToWindow(holder);
if (isStaggeredGridLayout(holder)) {
handleLayoutIfStaggeredGridLayout(holder, holder.getLayoutPosition());
}
}
private boolean isStaggeredGridLayout(RecyclerView.ViewHolder holder) {
ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
if (layoutParams != null && layoutParams instanceof StaggeredGridLayoutManager.LayoutParams) {
return true;
}
return false;
}
protected void handleLayoutIfStaggeredGridLayout(RecyclerView.ViewHolder holder, int position) {
if ( isFooter(position)){
StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams();
p.setFullSpan(true);
}
}
/**
* 是否为 底部
* @param position
* @return
*/
private boolean isFooter(int position) {
if (position == qrCodeSayBeanList.size()){
return true;
}
return false;
}
/**
* 底部的viewHolder
*/
private static class BottomViewHolder extends RecyclerView.ViewHolder{
private View bottomContainer;
private ImageView imgView;
private TextView textView;
public BottomViewHolder(View itemView) {
super(itemView);
bottomContainer = itemView.findViewById(R.id.bottom_container);
imgView = itemView.findViewById(R.id.img_loading);
textView = itemView.findViewById(R.id.txt_tips);
}
}
/**
* 正常的viewHolder
*/
private static class NomalViewHolder extends RecyclerView.ViewHolder{
private ImageView imgView;
private TextView txtViewNumb;
private TextView txtLikedNumb;
public NomalViewHolder(View itemView) {
super(itemView);
imgView = itemView.findViewById(R.id.img_view);
txtViewNumb = itemView.findViewById(R.id.txt_view_numbs);
txtLikedNumb = itemView.findViewById(R.id.txt_tap_liked_numbs);
}
}
/**
* 开始加载动画,局部刷新
*/
public void startLoading(){
isLoading = true;
// notifyDataSetChanged();
notifyItemRangeChanged(preNumb,qrCodeSayBeanList.size());//防止全部item刷新,由于高度随机变化导致页面抖动问题,这里采用局部刷新
preNumb = xxxList.size();
}
/**
* 停止加载动画,局部刷新
*/
public void stopLoading(){
isLoading = false;
// notifyDataSetChanged();
notifyItemRangeChanged(preNumb,qrCodeSayBeanList.size());//防止全部item刷新,由于高度随机变化导致页面抖动问题,这里采用局部刷新
preNumb = xxxList.size();
}
/**
* 停止加载动画,全局刷新
*/
public void stopLoadingNotifyAll(){
isLoading = false;
notifyDataSetChanged();
}
}
2.StaggeredGridLayoutManager 显示加载更多的时候,加载更多作为最后一个item没有单独占满屏幕宽度,只显示为一个item的宽度;
主要是根据不同的layoutManager的特点进行重写下面这两个方法,并且调用相关layoutManager的Span的设置方法:
final GridLayoutManager gridManager = ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return isFooter(position) ? gridManager.getSpanCount() : 1;
}
});
StaggeredGridLayoutManager.LayoutParams.setFullSpan(true);
/**
* 解决 GridLayoutManager 加载更多时,加载动画最后一个item没有占满屏幕宽度问题
* @param recyclerView
*/
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if (manager instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return isFooter(position) ? gridManager.getSpanCount() : 1;
}
});
}
}
/**
* 解决 StaggeredGridLayoutManager 加载更多时,加载动画导致最后一个item没有占满屏幕宽度问题
* @param holder
*/
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
super.onViewAttachedToWindow(holder);
if (isStaggeredGridLayout(holder)) {
handleLayoutIfStaggeredGridLayout(holder, holder.getLayoutPosition());
}
}
private boolean isStaggeredGridLayout(RecyclerView.ViewHolder holder) {
ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
if (layoutParams != null && layoutParams instanceof StaggeredGridLayoutManager.LayoutParams) {
return true;
}
return false;
}
protected void handleLayoutIfStaggeredGridLayout(RecyclerView.ViewHolder holder, int position) {
if ( isFooter(position)){
StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams();
p.setFullSpan(true);
}
}
/**
* 是否为 底部
* @param position
* @return
*/
private boolean isFooter(int position) {
if (position == qrCodeSayBeanList.size()){
return true;
}
return false;
}
3.StaggeredGridLayoutManager 如何随机设置item的高度;
ViewGroup.LayoutParams lp = nomalViewHolder.imgView.getLayoutParams();
int scale = new Random().nextInt(10);
if (scale < 5){
scale = 9;
}
lp.height= (int) (UIUtil.dp2px(mContext,200) *0.1 * scale);
lp.width= RecyclerView.LayoutParams.MATCH_PARENT;
nomalViewHolder.imgView.setLayoutParams(lp);
4.StaggeredGridLayoutManager 上拉加载数据刷新UI时,由于高度随机,造成页面item抖动问题;
这里由于直接调用 notifyDataSetChanged();那么是全局刷新,而刷新的时候item的高度重新随机分配,导致数据刷新的时候会造成抖动。建议采用notifyItemRangeChanged进行局部刷新:
/**
* 开始加载动画,局部刷新
*/
public void startLoading(){
isLoading = true;
// notifyDataSetChanged();
notifyItemRangeChanged(preNumb,qrCodeSayBeanList.size());//防止全部item刷新,由于高度随机变化导致页面抖动问题,这里采用局部刷新
preNumb = qrCodeSayBeanList.size();
}
/**
* 停止加载动画,局部刷新
*/
public void stopLoading(){
isLoading = false;
// notifyDataSetChanged();
notifyItemRangeChanged(preNumb,xxxList.size());//防止全部item刷新,由于高度随机变化导致页面抖动问题,这里采用局部刷新
preNumb = xxxList.size();
}
/**
* 停止加载动画,全局刷新
*/
public void stopLoadingNotifyAll(){
isLoading = false;
notifyDataSetChanged();
}
5.RecyclerView莫名的Inconsistency detected崩溃;
自定义一个CustomStaggeredGridLayoutManager 在onLayoutChildren对异常进行捕获:
public class CustomStaggeredGridLayoutManager extends StaggeredGridLayoutManager {
private static final String TAG = "LOG_CustomStaggered";
public CustomStaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public CustomStaggeredGridLayoutManager(int spanCount, int orientation) {
super(spanCount, orientation);
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
}catch (Exception e){
Log.i(TAG, "onLayoutChildren: e " + e.getMessage());
}
}
}
参考https://www.jianshu.com/p/2eca433869e9