Android中RecyclerView配合BaseRecyclerViewAdapterHelper实现瀑布流(九)

今天来使用BaseRecyclerViewAdapterHelper来完成瀑布流效果。

说明:

一,使用的Androidstudio版本为3.5(最新版)。

二,看过很多网络的瀑布流实现效果,或多或少都会出现各种问题,比如上下滑动的时候RecyclerView顶部留白,中间存在空隙。加载卡顿或者直接崩溃。本文彻底解决这些疑难杂症。

三,这是BaseRecyclerViewAdapterHelper的系列的第九篇文章,如有简单的不懂使用请看前面的文章。

原作的github地址为:https://github.com/CymChad/BaseRecyclerViewAdapterHelper

展示效果:

效果图

Screenrecorder.gif

现在正式开始

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<String> arrayList = new ArrayList<>();
    private ViewHolder viewHolder;
    private boolean mCanLoop = true;
    private List<MainEntity.ResultsBean> 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<String> {
        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<BaseResponse<MainEntity.ResultsBean>> call = apiUrl.getPic(10,mPage);
        call.enqueue(new Callback<BaseResponse<MainEntity.ResultsBean>>() {
                         @Override
                         public void onResponse(Call<BaseResponse<MainEntity.ResultsBean>> call, Response<BaseResponse<MainEntity.ResultsBean>> 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<BaseResponse<MainEntity.ResultsBean>> 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<MainEntity.ResultsBean, BaseViewHolder> {

    private HashMap<Integer, Integer> hashMap = new HashMap<Integer, Integer>();
    private int mWidth;
    private int mHeight;

    public MainAdapter(@Nullable List<MainEntity.ResultsBean> 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<Bitmap>() {
                    @Override
                    public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Bitmap> target, boolean isFirstResource) {
                        return false;
                    }

                    @Override
                    public boolean onResourceReady(Bitmap bitmap, Object model, Target<Bitmap> 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

如果有发现错误欢迎指正我及时修改,如果有好的建议欢迎留言。如果觉得对你有帮助欢迎给小星星,谢谢。

推荐阅读更多精彩内容