业余时间:RecyclerView的封装
来源:互联网 发布:检测电脑硬件故障软件 编辑:程序博客网 时间:2024/06/05 15:17
前言
上一篇已经实现了头部和尾部的加载标识,接下来只需要将它们与RecyclerView组合封装就OK了,不得不说自己要去封装一个好用的刷新加载控件还是得费好多心思去实现和优化的。
Begin
刷新头部比较麻烦点,当然也是重点,需要处理touch和控件高度,这里我的思路是:
1. 当头部处于刷新状态时不在接收任何触摸事件
2. 当刷新头部处于当前列表在屏幕上的第一个Item并且有下拉手势时开始响应下拉事件,当然这也是必须的
3. 刷新头部内部只处理触摸事件,是否处于头部在RecyclerView中判断
4. 通过手指在屏幕Y方向的距离计算刷新头部的高度,通过刷新头部的高度计算时钟的角度
刷新头部的触摸事件处理如下:
protected void touch(MotionEvent event, int appbarState) { //如果当前正在刷新,不接收任何的触摸事件 if (currentState == STATE_REFRESH) return; if (event.getAction() == MotionEvent.ACTION_MOVE) { if (isFirstMove) { lastY = event.getRawY(); isFirstMove = false; } float delaY = (event.getRawY() - lastY) / 3; if (delaY > 0 || getCurrentHeight() > 0) { currentHeight = (int) (delaY + getCurrentHeight()); clockView.setClockAngle(currentHeight);//设置时钟的角度 currentState = STATE_PREPARE;//当前的刷新状态为准备状态 if (delaY > 0 && appbarState == XRecyclerView.APP_BAR_NORMAL) {//下拉 changeHeight(currentHeight); } else if (delaY < 0) {//上滑 layout(0, currentHeight, width, currentHeight); changeHeight(currentHeight); } changeRefreshState(true); } } else if (event.getAction() == MotionEvent.ACTION_UP) { isFirstMove = true; changeRefreshState(false); } lastY = event.getRawY(); } private void changeHeight(int height) { LayoutParams params = (LayoutParams) refreshView.getLayoutParams(); params.height = height > 0 ? height : 0; params.width = width; refreshView.setLayoutParams(params); }
这里的touch事件是从RecyclerView中传递过来的,同时传递过来的还有AppbarLayout的状态,这个稍后再说。这里通过float delaY = (event.getRawY() - lastY) / 3计算在Y方向的移动距离,除以3以减小移动距离。刷新状态共有三种,分别为正常状态,准备状态,也就是刷新头部响应触摸事件且手指还在屏幕上的状态,还有正在刷新的状态。当手指离开屏幕时高度的渐变,通过属性动画来实现,并根据起始与结束值在动画结束时更改刷新头部处于的对应状态
private void changeHeightAnim(final int start, final int end) { if (animator == null || !animator.isRunning()) { animator = ValueAnimator.ofInt(start, end); animator.setDuration(300).setInterpolator(new DecelerateInterpolator(1.2f)); animator.start(); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (int) animation.getAnimatedValue(); changeHeight(value); if (start == currentHeight && end == initHeight) { clockView.setClockAngle(value); } } }); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); if (end == (int) refreshHeight) { currentState = STATE_REFRESH; } else if (end == initHeight) { currentState = STATE_NORMAL; clockView.stopClockAnim(); } } }); } }
为了效果好看点,这里设置了一个减速的插值器DecelerateInterpolator,并设置因子为1.2,这个通过http://inloop.github.io/interpolator/这个网站可以很方便的查看不同的插值器对应的曲线,强烈推荐。
接下来就是RecyclerView的封装,这里取一个高逼格的名字就叫XRecyclerView,当然此XRecyclerView非彼XRecyclerView,网上的XRecyclerView(https://github.com/jianghejie/XRecyclerView)是github上开源的封装RecyclerView实现的刷新加载控件,功能齐全,很不错!
这里刷新头部和加载尾部在RecyclerView的添加有两种方式,一种是采用装饰者模式,在RecyclerView内部构建一个适配器并添加头部尾部,另外一种嘛就是封装一个可添加头部和尾部的适配器,在RecyclerView内部强转,然后添加头部和尾部,但耦合性高。归根到底,实际上就是一种方式:即通过适配器添加头部尾部。这里使用的是第二种,因为第一种添加方式在实际使用过程中发现发现很不容扩展,与封装的适配器存在下标冲突,所以当使用装饰着模式添加头部尾部时,我只能老老实实的写RecyclerView的原生的适配器,而不能用自己封装的,这个是我忍受不了了的,毕竟会多出很多代码。两者共用的方式我目前没有好的解决方法,只能用这种笨方法了。
初始化刷新头部和加载尾部:
private void initRefresh() { isRefreshEnable = true; if (refreshHeader == null) { refreshHeader = new RefreshHeader(getContext(), refreshHeight); } refreshHeader.setRefreshListener(new RefreshHeader.RefreshListener() { @Override public void refresh() { loadingListener.refresh(); } }); } private void initLoading() { isLoadMoreEnable = true; if (loadingFooter == null) { loadingFooter = new LoadingFooter(getContext(), loadingHeight); } loadingFooter.setVisibility(GONE); LayoutManager manager = getLayoutManager(); if (manager instanceof LinearLayoutManager) { //只支持LinearLayoutManager和GridLayoutManager布局,不支持StaggeredGridLayoutManager final LinearLayoutManager layoutManager = (LinearLayoutManager) manager; addOnScrollListener(new OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); judgeLastItem(layoutManager.findLastVisibleItemPosition(), dy); } }); } }
通过重写setAdapter方法,添加头部和尾部,并注册数据变化的观察者,通过AdapterDataObserver可以监听RecyclerView的数据变化,从而设置对应的空值界面的显示与隐藏:
private AdapterDataObserver mObserver = new AdapterDataObserver() { @Override public void onChanged() { super.onChanged(); checkEmpty(); } @Override public void onItemRangeInserted(int positionStart, int itemCount) { super.onItemRangeInserted(positionStart, itemCount); checkEmpty(); } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { super.onItemRangeRemoved(positionStart, itemCount); checkEmpty(); } };@Override public void setAdapter(Adapter adapter) { if (this.getAdapter() != null) { this.getAdapter().unregisterAdapterDataObserver(mObserver); } super.setAdapter(adapter); if (refreshHeader == null || loadingFooter == null) { refreshHeader = new RefreshHeader(getContext(), refreshHeight); loadingFooter = new LoadingFooter(getContext(), loadingHeight); } ((BaseRVAdapter) getAdapter()).addHeaderView(refreshHeader); ((BaseRVAdapter) getAdapter()).addFooterView(loadingFooter); adapter.registerAdapterDataObserver(mObserver); }
继续:
接下来处理touch事件,但是需要注意的是,当AppbarLayout作为RecyclerView父View与CoordinatorLayout协同处理滑动时会有手势冲突,可以通过监听AppbarLayout的滑动来处理,思路很简单:找到AppbarLayout,设置偏移监听器:
@Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (appBarLayout == null) { ViewParent parent = getParent(); while (parent != null) { if (parent instanceof CoordinatorLayout) break; parent = parent.getParent(); } if (parent != null) { CoordinatorLayout layout = (CoordinatorLayout) parent; appBarLayout = null; for (int i = 0; i < layout.getChildCount(); i++) { View child = layout.getChildAt(i); if (child instanceof AppBarLayout) { appBarLayout = (AppBarLayout) child; break; } } if (appBarLayout != null) { appBarLayout.addOnOffsetChangedListener(this); } } } }@Override public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { appBarState = verticalOffset == 0 ? APP_BAR_NORMAL : APP_BAR_UP; }最后一步:处理RecyclerView的onTouchEvent事件:
@Override public boolean onTouchEvent(MotionEvent e) { if (isRefreshEnable && isTop()) {//处理刷新头部 refreshHeader.touch(e, appBarState); } return super.onTouchEvent(e); }
还有一些无关紧要的方法,就不贴代码了,整体来说难度不大,但还是要花费不少心思去琢磨细节和优化代码,刷新头部的触摸事件前前后后写了好几个版本,有的版本在使用的时候才发现,从最开始的思路就是错的,有的版本觉得刷新头部和RecyclerView的耦合性太高了,几乎把刷新头部的刷新处理全写在了RecyclerView里,不利于刷新头部的更换和扩展,当前版本算是比较满意的一个版本,当然可能也有很多潜在问题,后续会一直改进,说不定哪一天就用在真实项目上了,哈哈!
End
在封装的实现过程中遇到了很多不明白的地方,在很多方法中寻求最佳的解决方案,以更少的代码实现更好的功能,这是我的追求!虽然达不到我师父那种每天除了吃饭睡觉都是在写代码的状态,但是也不能差太多!
最后附上使用装饰者模式实现添加头和尾部的代码:
private class WrapperAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int HEADER_TYPE = 100; private static final int FOOTER_TYPE = 101; private Adapter adapter; WrapperAdapter(Adapter adapter) { this.adapter = adapter; } private boolean isHeader(int position) { return position < getHeaderCount(); } private boolean isFooter(int position) { return position >= (getDataCount() + getHeaderCount()); } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == HEADER_TYPE) { return com.hzw.freetime.adapter.ViewHolder.getViewHolder(refreshHeader); } else if (viewType == FOOTER_TYPE) { return com.hzw.freetime.adapter.ViewHolder.getViewHolder(loadingFooter); } return adapter.onCreateViewHolder(parent, viewType); } @SuppressWarnings("unchecked") @Override public void onBindViewHolder(ViewHolder holder, int position) { if (isHeader(position) || isFooter(position)) return; adapter.onBindViewHolder(holder, position - getHeaderCount()); } @Override public int getItemViewType(int position) { if (isHeader(position)) { return HEADER_TYPE; } else if (isFooter(position)) { return FOOTER_TYPE; } return adapter.getItemViewType(position - getHeaderCount()); } @Override public int getItemCount() { return getDataCount() + getHeaderCount() + getFooterCount(); } private int getHeaderCount() { return refreshHeader == null ? 0 : 1; } private int getFooterCount() { return loadingFooter == null ? 0 : 1; } private int getDataCount() { return adapter.getItemCount(); } }@Override public void setAdapter(Adapter adapter) { if (this.getAdapter() != null) { this.getAdapter().unregisterAdapterDataObserver(mObserver); }if (refreshHeader == null || loadingFooter == null) { refreshHeader = new RefreshHeader(getContext(), refreshHeight); loadingFooter = new LoadingFooter(getContext(), loadingHeight); } WrapperAdapter adapter1 = new WrapperAdapter(adapter); super.setAdapter(adapter1); adapter.registerAdapterDataObserver(mObserver); }OVER
- 业余时间:RecyclerView的封装
- Android Recyclerview的封装
- RecyclerView 的简单封装
- recyclerView的简单封装
- RecyclerView.Adapter的封装
- recyclerView的 BaseAdapter的封装
- Recyclerview Adapter 的简单封装
- RecyclerView.Adapter 的简单封装
- 简单封装RecyclerView的Adapter
- ListView 及Recyclerview的封装
- RecyclerView 使用的简单封装
- Android 封装RecyclerView的Adapter
- RecyclerView的使用和封装
- RecyclerView.Adapter的基本封装
- recyclerview封装好的链接
- 对recyclerview的简单封装
- 关于RecyclerView的Adapter封装
- 针对recyclerView的adapter封装
- Balsamiq Mockups 3.5.7 for Windows / Mac 简体中文汉化 最佳原型设计工具之一
- 功能名称:在线聊天功能大升级-支持直播、腾讯云IM及诸多体验
- 动态修改UINavigationBar的背景色
- Easy-UI案例使用总结
- liferay实现model监听
- 业余时间:RecyclerView的封装
- 支付宝RAS密钥生成器SHAwithRSA1024_V1.0.bat运行失败、一闪关闭
- Vert.x入门实例
- Chapter.16 Templates And Generic Programming
- DOCTYPE元素详解
- 微信小程序--常用快捷键
- Android中View点击事件传递(二)
- HDU 1176 免费馅饼【dp】
- 关于如何检测自己的电脑是否成为肉鸡!!