利用RecyclerView实现Banner轮播图
2019-05-22 本文已影响4人
萧清轩
随着入行时间变长,越来越懒得使用开源裤子,随着时间加长,对第三方的认知也越来越清晰,有团队支撑的裤子还好,个人开发的如果遇到BUG,有些问题是很难自己修复的;
这不,在使用人气最高的Banner框架时,就遇到了一个无法处理的BUG,我觉得是小问题,就是显示越界,但是我只能大眼瞪小眼的没法搞,相比于修复这个BUG,索性自己实现一个轮播图;
利用RecyclerView作为主体,通过自定义Adapter完成,对相关逻辑进行封装;布局中直接使用RecyclerView,并且绑定自定义Adapter,只需要重写两个方法既可轻松实现
自定义Adapter继承BaseBannerAdapter,并重写createLayout()、bindViewData()
/**
* Created by dzh on 05.21.021.
* 轮播图适配器
*/
public class BannerAdapter extends BaseBannerAdapter {
private Context context;
public BannerAdapter(Context context) {
this.context = context;
//添加点击事件(这个语法是 java 1.8,如果你的项目不是用java1.8写的,自行new一个监听器出来即可)
setOnItemClickListener((obj, position) ->{
BannerBean bannerBean = (BannerBean) obj;
ToastUtils.showShort("点击第:" + position + " " + bannerBean.getPosition());
System.out.println("s" + position);
});
}
/**
* 绑定Item视图,View可以任意自定义
* @return
*/
@Override
protected int createLayout() {
return R.layout.item_banner;
}
/**
* 绑定数据,将第二个参数强转换为自己的javabean
* @param holder
* @param data
* @param position
*/
@Override
protected void bindViewData(ViewHolder holder, Object data, int position) {
BannerBean bannerBean = (BannerBean) data;
//通过 holder.findView()可以获取你自定义的对应控件
ImageView bannerImg = holder.findView(R.id.bannerImg);
bannerImg.setImageResource(bannerBean.getPosition());
}
}
好了,上面就是最精简的轮播图适配器,其余工作全都交给父类去完成;
在Activity/Fragment中绑定RecyclerView即可
//模拟数据
int[] imgs = {R.mipmap.banner1,R.mipmap.banner2,R.mipmap.banner3};
bannerAdapter = new BannerAdapter(this);
bannerAdapter.bindingRecyclerView(mainBanner);
bannerBeans = new ArrayList<>();
for (int i = 0; i < 3; i++) {
bannerBeans.add(new BannerBean(imgs[i]));
}
//把数据交给Adapter
bannerAdapter.addData(bannerBeans);
这样是不是想怎么定义就怎么定义了,至于添加指示器,自己定义一个监听器即可,懒得写了,自行扩展吧;
下面是完整的BaseBannerAdapter
/**
* Created by dzh on 05.21.021.
* 无限轮播适配器
*/
public abstract class BaseBannerAdapter extends RecyclerView.Adapter<BaseBannerAdapter.ViewHolder> {
private RecyclerView recyclerView;
private int position;
private List<?> data;
private int duration = 5000;//切换时间,默认5秒
private OnItemClickListener onItemClickListener;
private Handler handler;
private Runnable runnable;
public interface OnItemClickListener {
void onItemClick(Object obj, int position);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(createLayout(), parent, false);
ViewHolder holder = new ViewHolder(view);
if (onItemClickListener != null) {
int position = computePage(holder.getLayoutPosition());
holder.itemView.setOnClickListener(v -> onItemClickListener.onItemClick(data != null ? data.get(position) : null, position));
}
return holder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
if (data == null) return;
bindViewData(holder, data.get(computePage(position)), computePage(position));
}
/**
* 核心方法,返回int最大值
*/
@Override
public int getItemCount() {
return getTrueItemCount();
}
private int getTrueItemCount() {
if (data == null || data.size() == 0) {
return 0;
} else if (data.size() == 1) {
return 1;
}
return Integer.MAX_VALUE;
}
/**
* ViewHolder
*/
public class ViewHolder extends RecyclerView.ViewHolder {
private View view;
ViewHolder(@NonNull View itemView) {
super(itemView);
this.view = itemView;
}
public <V extends View> V findView(@IdRes int id) {
return view.findViewById(id);
}
}
/**
* 计算相应页码
*
* @param page 实际页码
* @return 相应页码下标
*/
private int computePage(int page) {
int size = data.size();
return page % size;
}
/**
* 抽象方法,用于子类创建View
*/
protected abstract int createLayout();
/**
* 抽象方法,用于子类绑定数据
*/
protected abstract void bindViewData(ViewHolder holder, Object data, int position);
/**
* 绑定RecyclerView
*/
public void bindingRecyclerView(RecyclerView recyclerView) {
this.recyclerView = recyclerView;
recyclerView.setAdapter(this);
recyclerView.setLayoutManager(new LinearLayoutManager(recyclerView.getContext(), LinearLayoutManager.HORIZONTAL, false) {
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
LinearSmoothScroller smoothScroller =
new LinearSmoothScroller(recyclerView.getContext()) {
//调整RecyclerView切换时的滚动速度
@Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return 150f / displayMetrics.densityDpi;
}
};
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
});
PagerSnapHelper helper = new PagerSnapHelper() {
@Override
public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) {
position = super.findTargetSnapPosition(layoutManager, velocityX, velocityY);
return position;
}
};
helper.attachToRecyclerView(recyclerView);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
if (newState == 1) {
handler.removeCallbacks(runnable);
handler = null;
} else {
if (handler == null)
startTimer();
}
}
});
}
/**
* 绑定数据
*/
public void setData(List<?> data) {
this.data = data;
notifyDataSetChanged();
toCenterPage();
startTimer();
}
/**
* 跳转到中心页,并从第一页开始
*/
private void toCenterPage() {
int centerPage = Integer.MAX_VALUE / 2;
if (centerPage % data.size() > 0) {
centerPage = centerPage - centerPage % data.size();
}
if (recyclerView != null) {
recyclerView.scrollToPosition(position = centerPage);
}
}
/**
* 开始轮播 每5秒执行一次
*/
private void startTimer() {
if (getTrueItemCount() < 2) return;
if (handler == null)
handler = new Handler();
if (runnable == null)
runnable = () -> {
pageDown();
handler.postDelayed(runnable, duration);
};
handler.postDelayed(runnable, duration);
}
public void setDuration(int duration) {
this.duration = duration;
}
/**
* 当前页码 +1,切换到下一页
*/
private void pageDown() {
if (recyclerView != null) {
recyclerView.smoothScrollToPosition(position = position + 1);
}
}
/**
* 设置点击事件
*/
protected void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
}
还有一个问题没有处理掉,就是RecyclerView被触摸时,停止计时器,本来监听RecyclerView触摸事件能够实现,但是由于给Item添加了点击事件,RecyclerView的触摸事件被拦截了,无法正常暂停,给出的思路是,通过RecyclerView滚动监听器来监听手指触摸和离开,希望对大家有所帮助,最后,项目中尽量少用个人开源库
通过实践证实,可以监听RecyclerView滚动事件来控制自动切换的启动和暂停,添加代码如下
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
if (newState == 1) {
handler.removeCallbacks(runnable);
handler = null;
} else {
if (handler == null)
startTimer();
}
}
});