一 嵌套滑动
1 需求:
解决RecycleView嵌套其他滑动控件时滑动不流畅的问题
2 解决
- 自定义RecyclerView重写onInterceptTouchEvent
- setScrollingTouchSlop方法获取最小滑动识别距离
- onInterceptTouchEvent方法中记录down事件起始位置,move事件计算x和y方向距离,根据滑动状态和距离来判断是否拦截事件
- 当快速滑动自定义RecyclerView的一个滑动项目,然后突然改版手势方向,发现自定义RecyclerView不响应:
解决方法:requestDisallowInterceptTouchEvent作用是不允许父类打断这个onTouch 事件,设置一个空的函数,override 父类的方法,就可以达到相反的效果
3 关键代码
public class BetterRecyclerView extends RecyclerView {
private int touchSlop;
private Context mContext;
private int INVALID_POINTER = -1;
private int scrollPointerId = INVALID_POINTER;
private int initialTouchX;
private int initialTouchY;
private final static String TAG = "BetterRecyclerView";
public BetterRecyclerView(Context context) {
// super(context);
this(context, null);
}
public BetterRecyclerView(Context context, @Nullable AttributeSet attrs) {
// super(context, attrs);
this(context, attrs, 0);
}
public BetterRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
ViewConfiguration vc = ViewConfiguration.get(context);
touchSlop = vc.getScaledEdgeSlop();
mContext = context;
}
@Override
public void setScrollingTouchSlop(int slopConstant) {
super.setScrollingTouchSlop(slopConstant);
ViewConfiguration vc = ViewConfiguration.get(mContext);
switch (slopConstant) {
case TOUCH_SLOP_DEFAULT:
touchSlop = vc.getScaledTouchSlop();
break;
case TOUCH_SLOP_PAGING:
touchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(vc);
break;
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
if (e == null) {
return false;
}
int action = MotionEventCompat.getActionMasked(e);
int actionIndex = MotionEventCompat.getActionIndex(e);
switch (action) {
case MotionEvent.ACTION_DOWN :
scrollPointerId = MotionEventCompat.getPointerId(e, 0);
initialTouchX = Math.round(e.getX() + 0.5f);
initialTouchY = Math.round(e.getY() + 0.5f);
return super.onInterceptTouchEvent(e);
case MotionEvent.ACTION_POINTER_DOWN:
scrollPointerId = MotionEventCompat.getPointerId(e, actionIndex);
initialTouchX = Math.round(MotionEventCompat.getX(e, actionIndex) + 0.5f);
initialTouchY = Math.round(MotionEventCompat.getY(e, actionIndex) + 0.5f);
return super.onInterceptTouchEvent(e);
case MotionEvent.ACTION_MOVE:
int index = MotionEventCompat.findPointerIndex(e, scrollPointerId);
if (index < 0) {
return false;
}
int x = Math.round(MotionEventCompat.getX(e, index) + 0.5f);
int y = Math.round(MotionEventCompat.getY(e, index) + 0.5f);
if (getScrollState() != SCROLL_STATE_DRAGGING ) {
int dx = x - initialTouchX;
int dy = y - initialTouchY;
boolean startScroll = false;
//将斜率添加进来,这样可以减少 startScroll 为true 的机会。这个机会就会给需要这个返回值
if (getLayoutManager().canScrollHorizontally() && Math.abs(dx) > touchSlop &&
(getLayoutManager().canScrollVertically() || Math.abs(dx) > Math.abs(dy))) {
startScroll = true;
}
if(getLayoutManager().canScrollVertically() && Math.abs(dy) > touchSlop &&
(getLayoutManager().canScrollHorizontally() || Math.abs(dy) > Math.abs(dx))) {
startScroll = true;
}
Log.d(TAG, "startScroll: " + startScroll);
return startScroll && super.onInterceptTouchEvent(e);
}
return super.onInterceptTouchEvent(e);
default:
return super.onInterceptTouchEvent(e);
}
}
}
4 Demo
二 RecyclerView 三级缓存
1 第一级缓存
- mChangedScrap RecyclerView中需要改变的Viewholder
- mAttachedScrap 还没有和RecyclerView 分离的ViewHolder
- mCachedViews RecyclerView 的 ViewHolder 的缓存
2 第二级缓存
mViewCacheExtension 提供给开放者自己创建的缓存
3 第三级
mRecyclerPool 缓存池
RecycledViewPool:
一种用于多个RecyclerView之间共享View 的缓存。
应用场景:viewPager + adapter + tab
4 Demo
三 RecyclerView添加header和footer
- Recyclerview :每一个部分都是职责分明的。
- 无论你怎么用这个item,你都必须遵守我的基本原则:view 必须是ViewHolder 子类
- ViewHolder 是所有Item 的父类,所有的Item 都具备了这个基本的功能。
- HeaderVIew & FooterView只是特殊位置上的一个Item 类。使用了装饰设计模式封装Adapter
- 注意:宫格情况下,header和footer要在整个的列表头部或者底部
HeaderAndFooterWrapper
public class HeaderAndFooterWrapper <T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int BASE_ITEM_TYPE_HEADER = 100000;
private static final int BASE_ITEM_TYPE_FOOTER = 200000;
private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();
private SparseArrayCompat<View> mFootViews = new SparseArrayCompat<>();
private RecyclerView.Adapter mInnerAdapter;
public HeaderAndFooterWrapper(RecyclerView.Adapter adapter)
{
mInnerAdapter = adapter;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
if (mHeaderViews.get(viewType) != null)
{
ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mHeaderViews.get(viewType));
return holder;
} else if (mFootViews.get(viewType) != null)
{
ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mFootViews.get(viewType));
return holder;
}
return mInnerAdapter.onCreateViewHolder(parent, viewType);
}
@Override
public int getItemViewType(int position)
{
if (isHeaderViewPos(position))
{
return mHeaderViews.keyAt(position);
} else if (isFooterViewPos(position))
{
return mFootViews.keyAt(position - getHeadersCount() - getRealItemCount());
}
return mInnerAdapter.getItemViewType(position - getHeadersCount());
}
private int getRealItemCount()
{
return mInnerAdapter.getItemCount();
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
if (isHeaderViewPos(position))
{
return;
}
if (isFooterViewPos(position))
{
return;
}
mInnerAdapter.onBindViewHolder(holder, position - getHeadersCount());
}
@Override
public int getItemCount()
{
return getHeadersCount() + getFootersCount() + getRealItemCount();
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView)
{
WrapperUtils.onAttachedToRecyclerView(mInnerAdapter, recyclerView, new WrapperUtils.SpanSizeCallback()
{
@Override
public int getSpanSize(GridLayoutManager layoutManager, GridLayoutManager.SpanSizeLookup oldLookup, int position)
{
int viewType = getItemViewType(position);
if (mHeaderViews.get(viewType) != null)
{
return layoutManager.getSpanCount();
} else if (mFootViews.get(viewType) != null)
{
return layoutManager.getSpanCount();
}
if (oldLookup != null)
return oldLookup.getSpanSize(position);
return 1;
}
});
}
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder)
{
mInnerAdapter.onViewAttachedToWindow(holder);
int position = holder.getLayoutPosition();
if (isHeaderViewPos(position) || isFooterViewPos(position))
{
WrapperUtils.setFullSpan(holder);
}
}
private boolean isHeaderViewPos(int position)
{
return position < getHeadersCount();
}
private boolean isFooterViewPos(int position)
{
return position >= getHeadersCount() + getRealItemCount();
}
public void addHeaderView(View view)
{
mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view);
}
public void addFootView(View view)
{
mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view);
}
public int getHeadersCount()
{
return mHeaderViews.size();
}
public int getFootersCount()
{
return mFootViews.size();
}
}
WrapperUtils
public class WrapperUtils {
public interface SpanSizeCallback
{
int getSpanSize(GridLayoutManager layoutManager , GridLayoutManager.SpanSizeLookup oldLookup, int position);
}
public static void onAttachedToRecyclerView(RecyclerView.Adapter innerAdapter, RecyclerView recyclerView, final SpanSizeCallback callback)
{
innerAdapter.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof GridLayoutManager)
{
final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup()
{
@Override
public int getSpanSize(int position)
{
return callback.getSpanSize(gridLayoutManager, spanSizeLookup, position);
}
});
gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount());
}
}
public static void setFullSpan(RecyclerView.ViewHolder holder)
{
ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if (lp != null
&& lp instanceof StaggeredGridLayoutManager.LayoutParams)
{
StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
p.setFullSpan(true);
}
}
}