随着入行时间变长,越来越懒得使用开源裤子,对第三方的认知也越来越清晰,有团队支撑的裤子还好,个人开发的如果遇到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);
//因为我将RecyclerView的一些设置封装在了Adapter中
//所以这里不再是RecyclerView.setAdapter()了,而是由Adapter绑定RecyclerView,请注意
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 {
private static final String TAG = "BaseBannerAdapter";
private RecyclerView recyclerView;
private int position;
private List data;
private int duration = 5000;//切换时间,默认5秒
private OnItemClickListener onItemClickListener;
private Handler handler;
private Runnable runnable;
private boolean isPause;//暂停
private OnPageChangeListener onPageChangeListener;
public interface OnPageChangeListener {
void onPage(int sum, int page);
}
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 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);
if (onPageChangeListener != null) {
int page = position % data.size() + 1;
onPageChangeListener.onPage(data.size(), page);
}
return position;
}
};
helper.attachToRecyclerView(recyclerView);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
if (newState == 1) {
if (handler != null)
handler.removeCallbacks(runnable);
handler = null;
} else {
if (handler == null)
startTimer();
}
}
});
recyclerView.getViewTreeObserver().addOnWindowAttachListener(new ViewTreeObserver.OnWindowAttachListener() {
@Override
public void onWindowAttached() {
}
@Override
public void onWindowDetached() {
if (handler != null) {
if (runnable != null) {
handler.removeCallbacks(runnable);
}
handler = null;
}
}
});
}
/**
* 绑定数据
*/
public void setData(List data) {
this.data = data;
Log.d("Banner", "dataSize: " + data.size());
notifyDataSetChanged();
toCenterPage();
if (handler == null) {
startTimer();
}
if (onPageChangeListener != null) {
onPageChangeListener.onPage(data.size(), 1);
}
}
/**
* 跳转到中心页,并从第一页开始
*/
private void toCenterPage() {
if (getTrueItemCount() < 2) {
return;
}
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 = () -> {
Log.d("banner", "切换: ");
if (recyclerView != null) {
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);
}
if (onPageChangeListener != null) {
int page = position % data.size() + 1;
onPageChangeListener.onPage(data.size(), page);
}
}
/**
* 设置点击事件
*/
protected void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public void setOnPageChangeListener(OnPageChangeListener onPageChangeListener) {
this.onPageChangeListener = onPageChangeListener;
}
/**
* 恢复轮播
*/
public void onResume() {
if (isPause) {
startTimer();
}
isPause = false;
}
public void onPause() {
isPause = true;
if (handler != null)
handler.removeCallbacks(runnable);
handler = null;
}
}
BaseBannerAdapter的代码就这么多,以实现自动轮播,和手势触摸时停止自动轮播,离开时自动恢复轮播功能,下面是点击事件和轮播监听器用于实现指示器。
bannerAdapter.setOnPageChangeListener((sum, page) -> {
gdBannerIndicator.setText(page + "/" + sum);
});