今天来使用BaseRecyclerViewAdapterHelper来完成瀑布流效果。
说明:
一,使用的Androidstudio版本为3.5(最新版)。
二,看过很多网络的瀑布流实现效果,或多或少都会出现各种问题,比如上下滑动的时候RecyclerView顶部留白,中间存在空隙。加载卡顿或者直接崩溃。本文彻底解决这些疑难杂症。
三,这是BaseRecyclerViewAdapterHelper的系列的第九篇文章,如有简单的不懂使用请看前面的文章。
原作的github地址为:https://github.com/CymChad/BaseRecyclerViewAdapterHelper
展示效果:
现在正式开始
1,先看gradle文件,加一些需要使用的相关依赖,***为核心依赖库
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
defaultConfig {
applicationId "com.mumu.jsrecyclerview8"
minSdkVersion 19
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
//1,支持1.8
compileOptions {
targetCompatibility 1.8
sourceCompatibility 1.8
}
}
//,2,增加itpack支持
allprojects {
repositories {
jcenter()
maven { url 'https://jitpack.io' }
maven { url "https://maven.google.com" }
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
//3,增加相关依赖
//butterKnife
implementation 'com.jakewharton:butterknife:10.1.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
//androidx ***
implementation 'androidx.recyclerview:recyclerview:1.0.0'
//RecyclerView的适配器 ***
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.50'
//通用广告栏ConvenientBanner
implementation 'com.bigkoo:convenientbanner:2.1.5'
//增加下拉刷新SmartRefreshLayout的依赖 ***
implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0-andx-14'
implementation 'com.scwang.smartrefresh:SmartRefreshHeader:1.1.0-andx-14'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
//FloatingActionButton ***
api 'com.google.android.material:material:1.1.0-beta01'
//标题栏
api 'com.github.goweii:ActionBarEx:3.2.0'
//增加一个图片加载库 ***
api 'com.github.bumptech.glide:glide:4.9.0'
//retrofit2网络框架
api 'io.reactivex.rxjava2:rxjava:2.2.13'
api 'io.reactivex.rxjava2:rxandroid:2.1.0'
api 'com.squareup.retrofit2:retrofit:2.6.0'
api 'com.squareup.retrofit2:converter-gson:2.5.0'
api 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
api 'com.squareup.okhttp3:logging-interceptor:3.10.0'
}
2,再看核心的mainactivity,核心在refreshView方法。注释写的很详细。
package com.mumu.jsrecyclerview8;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.bigkoo.convenientbanner.ConvenientBanner;
import com.bigkoo.convenientbanner.holder.CBViewHolderCreator;
import com.bigkoo.convenientbanner.holder.Holder;
import com.bigkoo.convenientbanner.listener.OnItemClickListener;
import com.bumptech.glide.Glide;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.mumu.jsrecyclerview8.api.ApiUrl;
import com.scwang.smartrefresh.layout.SmartRefreshLayout;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import per.goweii.actionbarex.common.ActionBarCommon;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class MainActivity extends AppCompatActivity {
@BindView(R.id.abc_main_return)
ActionBarCommon abcMainReturn;
@BindView(R.id.srl_main)
SmartRefreshLayout srlMain;
@BindView(R.id.fab_main)
FloatingActionButton fabMain;
private RecyclerView rvMain;
private int distance;
private boolean visible = true;
private MainAdapter mMainAdapter;
private View top;
private ArrayList arrayList = new ArrayList<>();
private ViewHolder viewHolder;
private boolean mCanLoop = true;
private List mList = new ArrayList<>();
private int mPage = 1;//页数
private boolean isSrl = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
rvMain = findViewById(R.id.rv_main);
initView();
}
private void initView() {
refreshView();
initBanner();
smartRefreshView();
getPicCmd();
}
/**
* 刷新消息列表
*/
private void refreshView() {
// 创建StaggeredGridLayoutManager实例
MMStaggeredGridLayoutManager layoutManager =
new MMStaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
layoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
rvMain.setLayoutManager(layoutManager);
//RecyclerView的滚动监听,是否展示FloatingActionButton
rvMain.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//向下滚动
if (distance < -ViewConfiguration.getTouchSlop() && !visible) {
//显示fab
showFABAnimation(fabMain);
distance = 0;
visible = true;
} else if (distance > ViewConfiguration.getTouchSlop() && visible) {
//隐藏
hideFABAnimation(fabMain);
distance = 0;
visible = false;
}
//向下滑并且可见 或者 向上滑并且不可见
if ((dy > 0 && visible) || (dy < 0 && !visible)) {
distance += dy;
}
//滑动到顶部的时候隐藏按钮
if (!recyclerView.canScrollVertically(-1)) {
//隐藏
hideFABAnimation(fabMain);
}
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
int[] first = new int[2];
layoutManager.findFirstCompletelyVisibleItemPositions(first);
if (newState == RecyclerView.SCROLL_STATE_IDLE && (first[0] == 1 || first[1] == 1)) {
layoutManager.invalidateSpanAssignments();
}
}
});
mMainAdapter = new MainAdapter();
rvMain.setAdapter(mMainAdapter);
top = getLayoutInflater().inflate(R.layout.item_main_header, rvMain, false);
mMainAdapter.addHeaderView(top);
//因为要加载recyclerview中的header的资源,所以吧绑定放在这
ButterKnife.bind(this);
mMainAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
Toast.makeText(MainActivity.this, "你点击了第" + position + "张美女图片", Toast.LENGTH_SHORT).show();
}
});
}
private void initBanner() {
arrayList.clear();
arrayList.add("http://img2.imgtn.bdimg.com/it/u=1447362014,2103397884&fm=200&gp=0.jpg");
arrayList.add("http://img1.imgtn.bdimg.com/it/u=111342610,3492888501&fm=26&gp=0.jpg");
arrayList.add("http://imgsrc.baidu.com/imgad/pic/item/77094b36acaf2eddc8c37dc7861001e9390193e9.jpg");
viewHolder = new ViewHolder(top);
if (arrayList.size() <= 1) {
mCanLoop = false;
} else {
mCanLoop = true;
}
viewHolder.cbMain.setPages(new CBViewHolderCreator() {
@Override
public Holder createHolder(View itemView) {
return new NetImageHolderView(itemView);
}
@Override
public int getLayoutId() {
return R.layout.item_main_banner;
}
}, arrayList)
.setPageIndicator(new int[]{R.mipmap.ic_page_indicator, R.mipmap.ic_page_indicator_focused})
.setPageIndicatorAlign(ConvenientBanner.PageIndicatorAlign.CENTER_HORIZONTAL)
.setPointViewVisible(mCanLoop)
.setCanLoop(mCanLoop)
.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(int position) {
Toast.makeText(MainActivity.this, "你点击了第" + position + "张banner图片", Toast.LENGTH_SHORT).show();
}
});
if (arrayList.size() > 0) {
viewHolder.cbMain.startTurning(3000);
}
initReListener(viewHolder.tvMain1);
initReListener(viewHolder.tvMain2);
initReListener(viewHolder.tvMain3);
initReListener(viewHolder.tvMain4);
initReListener(viewHolder.tvMain5);
initReListener(viewHolder.tvMain6);
initReListener(viewHolder.tvMain7);
initReListener(viewHolder.tvMain8);
}
private void initReListener(View view) {
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "暂未开放!",Toast.LENGTH_SHORT).show();
}
});
}
/**
* 轮播图对应的holder
*/
public class NetImageHolderView extends Holder {
private ImageView mImageView;
//构造器
public NetImageHolderView(View itemView) {
super(itemView);
}
@Override
protected void initView(View itemView) {
//找到对应展示图片的imageview
mImageView = itemView.findViewById(R.id.iv_banner);
//设置图片加载模式为铺满,具体请搜索 ImageView.ScaleType.FIT_XY
mImageView.setScaleType(ImageView.ScaleType.FIT_XY);
}
@Override
public void updateUI(String data) {
//使用glide加载更新图片
Glide.with(MainActivity.this).load(data).into(mImageView);
}
}
/**
* by moos on 2017.8.21
* func:显示fab动画
*/
public void showFABAnimation(View view) {
view.setVisibility(View.VISIBLE);
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha", 1f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleX", 1f);
PropertyValuesHolder pvhZ = PropertyValuesHolder.ofFloat("scaleY", 1f);
ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY, pvhZ).setDuration(400).start();
}
/**
* by moos on 2017.8.21
* func:隐藏fab的动画
*/
public void hideFABAnimation(View view) {
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha", 0f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleX", 0f);
PropertyValuesHolder pvhZ = PropertyValuesHolder.ofFloat("scaleY", 0f);
ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY, pvhZ).setDuration(400).start();
view.setVisibility(View.GONE);
}
@OnClick(R.id.fab_main)
public void onViewClicked() {
//缓慢滑动到顶部
rvMain.smoothScrollToPosition(0);
}
static
class ViewHolder {
@BindView(R.id.cb_main)
ConvenientBanner cbMain;
@BindView(R.id.tv_main1)
TextView tvMain1;
@BindView(R.id.tv_main2)
TextView tvMain2;
@BindView(R.id.tv_main3)
TextView tvMain3;
@BindView(R.id.tv_main4)
TextView tvMain4;
@BindView(R.id.tv_main5)
TextView tvMain5;
@BindView(R.id.tv_main6)
TextView tvMain6;
@BindView(R.id.tv_main7)
TextView tvMain7;
@BindView(R.id.tv_main8)
TextView tvMain8;
ViewHolder(View view) {
ButterKnife.bind(this, view);
}
}
/**
* MainActivity中增加下拉刷新和上拉加载的监听方法
*/
private void smartRefreshView() {
srlMain.setOnRefreshLoadMoreListener(new OnRefreshLoadMoreListener() {
@Override
public void onRefresh(@NonNull RefreshLayout refreshLayout) {
//下拉刷新,一般添加调用接口获取数据的方法
mPage = 1;
isSrl = true;
getPicCmd();
}
@Override
public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
//上拉加载,一般添加调用接口获取更多数据的方法
mPage++;
//接口后面的图片为空,这样做目的是让图片都能加载
if(mPage>4){
mPage=2;
}
isSrl = true;
getPicCmd();
}
});
}
/**
* 获取图片的请求
*/
private void getPicCmd(){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://gank.io/api/")
//设置数据解析器
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiUrl apiUrl=retrofit.create(ApiUrl.class);
Call> call = apiUrl.getPic(10,mPage);
call.enqueue(new Callback>() {
@Override
public void onResponse(Call> call, Response> response) {
if (mPage == 1) {
mList.clear();
}
if (response.body() != null&& response.body().getResults().size() > 0) {
mList.addAll(response.body().getResults());
if (isSrl && mPage != 1) {
int start = mList.size();
mMainAdapter.notifyItemRangeInserted(start, 10);
} else {
mMainAdapter.setNewData(mList);
}
isSrl = false;
srlMain.finishRefresh();
if (response.body() != null && response.body().getResults().size() >= 10) {
srlMain.finishLoadMore();
} else {
srlMain.finishLoadMoreWithNoMoreData();
}
}
}
@Override
public void onFailure(Call> call, Throwable t) {
Log.e("mmm","errow "+t.getMessage());
}
}
);
}
}
3,再看recyclerview对应的适配器,核心在glide加载图片的方法。加载完成图片后,利用map集合存储对应图片的高度。(宽度自适应屏幕),这样在上拉后在下拉也不会出现错乱和定顶部留白。
package com.mumu.jsrecyclerview8;
import android.graphics.Bitmap;
import android.text.TextUtils;
import android.util.Log;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import java.util.HashMap;
import java.util.List;
import static com.mumu.jsrecyclerview8.App.getContext;
/**
* @author : zlf
* date : 2019/5/26
* github : https://github.com/mamumu
* blog : https://www.jianshu.com/u/281e9668a5a6
* desc :
*/
public class MainAdapter extends BaseQuickAdapter {
private HashMap hashMap = new HashMap();
private int mWidth;
private int mHeight;
public MainAdapter(@Nullable List data) {
super(R.layout.item_main, data);
}
public MainAdapter() {
super(R.layout.item_main);
}
@Override
protected void convert(BaseViewHolder helper, MainEntity.ResultsBean data) {
//将每一个需要赋值的id和对应的数据绑定
ImageView imageView = helper.getView(R.id.item_iv_main);
helper.setText(R.id.item_tv_main_name, "type:"+data.getType()+"+id:"+data.get_id());//名字
if (TextUtils.isEmpty(data.getType()) || TextUtils.isEmpty(data.getUrl())) {
return;
}
if (hashMap.get(helper.getAdapterPosition()) != null) {
//屏幕的宽度(px值)
int screenWidth = getContext().getResources().getDisplayMetrics().widthPixels;
//图片的宽度
mWidth = screenWidth / 2;
//图片的高度
mHeight=hashMap.get(helper.getAdapterPosition());
//如果图片存储的高度不为空,则使用图片的存储高度作为imageView的高度
ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();
layoutParams.width=mWidth;
layoutParams.height=mHeight;
imageView.setLayoutParams(layoutParams);
Log.d("mmmm", "S_height" + helper.getAdapterPosition() + "=" + hashMap.get(helper.getAdapterPosition()));
}
RequestOptions options = new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.placeholder(R.mipmap.icon_no_shop)
.error(R.mipmap.icon_no_shop);
Glide.with(mContext)
.asBitmap()
.apply(options)
.addListener(new RequestListener() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Bitmap bitmap, Object model, Target target, DataSource dataSource, boolean isFirstResource) {
//存储图片的高度,以便在向上滚动的时候使用
int height = bitmap.getHeight();
if (hashMap.get(helper.getAdapterPosition()) == null) {
hashMap.put(helper.getAdapterPosition(), height);
}
// Log.d("mmm", "S_width" + helper.getAdapterPosition() + "=" + width); //400px
// Log.d("mmm", "S_height" + helper.getAdapterPosition() + "=" + height); //400px
return false;
}
})
.load(data.getUrl())
.into(imageView);
//对两个按钮进行监听
}
}
4,对应github地址
demo地址:https://github.com/mamumu/jsRecyclerView8
5,本系列第一篇文章地址,如果本文看不懂可以看第一篇,如有其它疑问请留言。
地址:https://www.jianshu.com/p/ce972355c71d
如果有发现错误欢迎指正我及时修改,如果有好的建议欢迎留言。如果觉得对你有帮助欢迎给小星星,谢谢。