这是一个使用RecyclerView实现瀑布流,并带上下拉刷新和上拉加载功能的Demo。做Demo之前看了很多网友们实现瀑布流踩的坑,所以这个Demo把那些常见的坑都填上了,目前没发现有什么问题。
关于下拉刷新和上拉加载,说实话我每次做这个功能都很头疼,因为一直没有找到一个好的方式或者说好的库,能让我只拿着一个框架就去适配所有需要下拉刷新和上拉加载功能的ViewGroup,并且可以自己实现Header和Footer。今天自己鼓捣了很久瀑布流的Header和Footer,然而光是Footer我就花费了不少功夫,最后在寻找好的Header实现方式的过程中发现了一个能满足我全部需求的库——# SmartRefreshLayout,讲真,我以前就见过这个库,但是那时候觉得这个库花里胡哨的就没仔细看下去,今天定睛一看,,我的妈呀,神器呀,我以前咋就把它错过了呢!从此我要抱着这个库不放手了。
废话了这么多,下边来看看我的实现吧。
先贴完整代码
首先是Activity文件
package com.adminstrator.guaguakaapplication;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import com.adminstrator.guaguakaapplication.widget.StaggeredDividerItemDecoration;
import com.scwang.smartrefresh.layout.SmartRefreshLayout;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnLoadMoreListener;
import com.scwang.smartrefresh.layout.listener.OnRefreshListener;
import java.util.ArrayList;
public class WaterFallActivity extends AppCompatActivity {
private ArrayList<Integer> imageIds = new ArrayList<>();
private int[] ids = {R.drawable.p1,R.drawable.p2,R.drawable.p3,R.drawable.p4,R.drawable.p5,R.drawable.p6,R.drawable.p7,
R.drawable.p8,R.drawable.p9,R.drawable.p10,R.drawable.p11,R.drawable.p12,R.drawable.p13,R.drawable.p14,};
private RecyclerView rv_waterfall;
private DemoAdapter adapter;
private SmartRefreshLayout refreshlayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_water_fall);
refreshlayout = findViewById(R.id.refreshlayout);
rv_waterfall = findViewById(R.id.rv_waterfall);
rv_waterfall.setHasFixedSize(true);
rv_waterfall.setItemAnimator(null);
//垂直方向的2列
final StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
//防止Item切换
layoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
rv_waterfall.setLayoutManager(layoutManager);
final int spanCount = 2;
rv_waterfall.addItemDecoration(new StaggeredDividerItemDecoration(this,10,spanCount));
//解决底部滚动到顶部时,顶部item上方偶尔会出现一大片间隔的问题
rv_waterfall.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
int[] first = new int[spanCount];
layoutManager.findFirstCompletelyVisibleItemPositions(first);
if (newState == RecyclerView.SCROLL_STATE_IDLE && (first[0] == 1 || first[1] == 1)) {
layoutManager.invalidateSpanAssignments();
}
}
});
//设置Adapter
for(int i = 0 ; i < ids.length;i++){
imageIds.add(ids[i]);
}
adapter = new DemoAdapter();
rv_waterfall.setAdapter(adapter);
adapter.replaceAll(imageIds);
//设置下拉刷新和上拉加载监听
refreshlayout.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh(@NonNull final RefreshLayout refreshLayout) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
adapter.replaceAll(getData());
refreshLayout.finishRefresh();
}
},2000);
}
});
refreshlayout.setOnLoadMoreListener(new OnLoadMoreListener() {
@Override
public void onLoadMore(@NonNull final RefreshLayout refreshLayout) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
adapter.addData(adapter.getItemCount(),getData());
refreshLayout.finishLoadMore();
}
},2000);
}
});
}
private ArrayList<Integer> getData() {
ArrayList<Integer> list = new ArrayList<>();
for(int i = 0 ; i < 6;i++){
list.add(ids[i]);
}
return list;
}
}
Activity的布局文件,使用了SmartRefreshLayout。Header和Footer我都是用了一个360度旋转的Loading图片。这种Header和Footer的添加方式可以让你随意定制自己想要的任何样式,及其方便。SmartRefreshLayout有更丰富的功能,建议没了解过的童鞋去看看。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.adminstrator.guaguakaapplication.WaterFallActivity">
<com.scwang.smartrefresh.layout.SmartRefreshLayout
android:id="@+id/refreshlayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--下拉刷新用的Header-->
<RelativeLayout
android:id="@+id/rl_header_refresh"
android:layout_width="match_parent"
android:layout_height="60dp"
>
<ProgressBar
android:id="@+id/progress_loading_dialog"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@null"
android:indeterminateDrawable="@drawable/loading_anim"
android:indeterminateBehavior="repeat"
android:layout_centerInParent="true"
/>
</RelativeLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_waterfall"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="10dp" />
<!--上拉加载用的Footer-->
<RelativeLayout
android:id="@+id/rl_footer_refresh"
android:layout_width="match_parent"
android:layout_height="60dp"
>
<ProgressBar
android:id="@+id/progress_loading_dialog2"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@null"
android:indeterminateDrawable="@drawable/loading_anim"
android:indeterminateBehavior="repeat"
android:layout_centerInParent="true"
/>
</RelativeLayout>
</com.scwang.smartrefresh.layout.SmartRefreshLayout>
</android.support.constraint.ConstraintLayout>
loading_anim也贴出来
<?xml version="1.0" encoding="utf-8"?>
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/loading"
android:fromDegrees="0.0"
android:pivotX="50.0%"
android:pivotY="50.0%"
android:toDegrees="360.0"
/>
接下来是DemoAdapter,实现瀑布流高低错落效果的关键就在里边,需要在onBindViewHolder的时候去为item设定高度。
package com.adminstrator.guaguakaapplication;
import android.app.Activity;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.nostra13.universalimageloader.core.ImageLoader;
import java.util.ArrayList;
import java.util.Random;
/**
* Created by Administrator on 2019/7/31.
*/
public class DemoAdapter extends RecyclerView.Adapter<DemoAdapter.BaseViewHolder> {
private ArrayList<Integer> dataList = new ArrayList<>();
public void replaceAll(ArrayList<Integer> list) {
dataList.clear();
if (list != null && list.size() > 0) {
dataList.addAll(list);
}
notifyDataSetChanged();
}
/**
* 插入数据使用notifyItemInserted,如果要使用插入动画,必须使用notifyItemInserted
* 才会有效果。即便不需要使用插入动画,也建议使用notifyItemInserted方式添加数据,
* 不然容易出现闪动和间距错乱的问题
* */
public void addData(int position,ArrayList<Integer> list) {
dataList.addAll(position,list);
notifyItemInserted(position);
}
//移除数据使用notifyItemRemoved
public void removeData(int position) {
dataList.remove(position);
notifyItemRemoved(position);
}
@Override
public DemoAdapter.BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new OneViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_rv_water_fall, parent, false));
}
@Override
public void onBindViewHolder(DemoAdapter.BaseViewHolder holder, int position) {
holder.setData(dataList.get(position),position);
}
@Override
public int getItemCount() {
return dataList != null ? dataList.size() : 0;
}
public class BaseViewHolder extends RecyclerView.ViewHolder {
public BaseViewHolder(View itemView) {
super(itemView);
}
void setData(Object data,int position) {
}
}
private class OneViewHolder extends BaseViewHolder {
private ImageView ivImage;
public OneViewHolder(View view) {
super(view);
ivImage = (ImageView) view.findViewById(R.id.iv_item_water_fall);
}
@Override
void setData(Object data,int position) {
if (data != null) {
int id = (int) data;
ivImage.setImageResource(id);
//需要Item高度不同才能出现瀑布流的效果,此处简单粗暴地设置一下高度
if (position % 2 == 0) {
ivImage.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 250));
} else {
ivImage.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 350));
}
}
}
}
}
以上已经实现了瀑布流效果,为了美观一般情况下还需要在Item间设置间隔。也就上边Activity里用到的StaggeredDividerItemDecoration。
package com.adminstrator.guaguakaapplication.widget;
import android.content.Context;
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.TypedValue;
import android.view.View;
public class StaggeredDividerItemDecoration extends RecyclerView.ItemDecoration {
private Context context;
private float interval;
private int spanCount;
/**
* @param interval item的间距
* @param spanCount 列数
* */
public StaggeredDividerItemDecoration(Context context, float interval, int spanCount) {
this.context = context;
this.interval = interval;
this.spanCount = spanCount;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
// 获取item在span中的下标
int spanIndex = params.getSpanIndex();
int interval = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
this.interval, context.getResources().getDisplayMetrics());
// 中间间隔
/**
* 这个判断适用于瀑布流只有两列的情况,如果有多列,那么再增加spanIndex % spanCount == 的判断并做处理就好了
* 此处的left和right都为interval / 2的原因是为了让左边item和右边item同宽
* */
if (spanIndex % spanCount == 0) {
outRect.right = interval / 2;
} else {
outRect.left = interval / 2;
}
// 下方间隔
outRect.bottom = interval;
}
}
我效果图里的圆角图片使用了SWImageView,如果有需要,在Gradle中引入
implementation 'com.angel:SWImageView:1.0.0'
我使用的SmartRefreshLayout版本
implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0-alpha-32'
以上
谢谢大家的督促,我把项目放Github上了,地址:https://github.com/ChunmingWu/StudyProject/tree/scratchDemo