自定义控件recycleview高级UI

利用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();
                }
            }
        });
上一篇下一篇

猜你喜欢

热点阅读