文章标题

该篇参考资料

recycleview讲解1

recycleview讲解2

一、做一个recyclerview的万能适配器

贴源码

++viewholder++

package com.example.hy.test.recyclerview;

import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.View;
import android.widget.TextView;


public class BaseViewholder extends RecyclerView.ViewHolder {
    private SparseArray mViewSparseArray;
    private View itemView;

    public BaseViewholder(View itemView) {
        super(itemView);
        this.itemView = itemView;
        mViewSparseArray = new SparseArray<>();
    }

    private  T getView(int id) {
        View view = mViewSparseArray.get(id);
        if (view == null) {
            view = itemView.findViewById(id);
            mViewSparseArray.put(id, view);
        }
        return (T) view;
    }

    public void setText(int id, String data) {
        TextView tv = getView(id);
        tv.setText(data);
    }

}

++adapter++

package com.example.hy.test.recyclerview;


import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;

import java.util.List;

public abstract class QuickRecyclerAdapter extends RecyclerView.Adapter {
    private List mList;

    public QuickRecyclerAdapter(List list) {
        mList = list;
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }

    @Override
    public BaseViewholder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new BaseViewholder(setLayoutView(parent, viewType));
    }

    @Override
    public void onBindViewHolder(BaseViewholder holder, int position) {
        setData(holder, mList.get(position), position);
    }

    public abstract View setLayoutView(ViewGroup parent, int viewType);

    public abstract void setData(BaseViewholder holder, T t, int position);
}

++使用实例++

List testStrings = new ArrayList<>();
        QuickRecyclerAdapter quickRecyclerAdapter = new QuickRecyclerAdapter(testStrings) {
    @Override
    public View setLayoutView(ViewGroup parent, int viewType) {
        return LayoutInflater.from(RecyclerViewDemo.this).inflate(R.layout.test_tv, parent, false);
    }

    @Override
    public void setData(BaseViewholder holder, String str, int position) {
        holder.setText(R.id.tv_test, str);
    }
};

二、ItemDecoration

用来设置item之间的分割线

DividerItemDecoration

一、说明。

由google官方提供,在support.v7包下。但是得注意版本,测试在recyclerview22.0.0包下没有该类。design25.0.0包下存在。(之后design包整合了recyclerview,recyclerview单独的包也有发布,可以视情况使用)。

二、使用

recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));

三、源码解析

构造函数中获得系统属性:android:listDivider


private static final int[] ATTRS = new int[]{ android.R.attr.listDivider };
private Drawable mDivider;

public DividerItemDecoration(Context context, int orientation) {
    final TypedArray a = context.obtainStyledAttributes(ATTRS);
    mDivider = a.getDrawable(0);//获得需要画在画布上的drawable对象
    a.recycle();
    setOrientation(orientation);
}

该属性可以再styles.xml中设置

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="android:listDivider">@drawable/item_divider
style>

在ondraw方法中,将drawable画出来
主要是根据recyclerview的布局方向,来画横线还是竖线。

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        ...
        if (mOrientation == VERTICAL) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }
    }

布局是竖的,但是画的其实是横向分割线

private void drawVertical(Canvas canvas, RecyclerView parent) {
        canvas.save();
        final int left;
        final int right;
        if (parent.getClipToPadding()) {
            left = parent.getPaddingLeft();
            right = parent.getWidth() - parent.getPaddingRight();
            canvas.clipRect(left, parent.getPaddingTop(), right,
                    parent.getHeight() - parent.getPaddingBottom());
        } else {
            left = 0;
            right = parent.getWidth();
        }
        //以上是圈定布局的边间,把padding去掉。画横向分割线,需要去掉左右padding。

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            parent.getDecoratedBoundsWithMargins(child, mBounds);
            final int bottom = mBounds.bottom + Math.round(ViewCompat.getTranslationY(child));
            final int top = bottom - mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            //以上设置drawable的布局边界(左上右下)
            mDivider.draw(canvas);
            //以上将mdivider画在画布上
        }
        canvas.restore();//重置画布
    }

布局是横的时候,画竖线

private void drawHorizontal(Canvas canvas, RecyclerView parent) {
        canvas.save();
        final int top;
        final int bottom;
        if (parent.getClipToPadding()) {
            top = parent.getPaddingTop();
            bottom = parent.getHeight() - parent.getPaddingBottom();
            canvas.clipRect(parent.getPaddingLeft(), top,
                    parent.getWidth() - parent.getPaddingRight(), bottom);
        } else {
            top = 0;
            bottom = parent.getHeight();
        }
        //以上是去掉上下padding

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds);
            final int right = mBounds.right + Math.round(ViewCompat.getTranslationX(child));
            final int left = right - mDivider.getIntrinsicWidth();
            mDivider.setBounds(left, top, right, bottom);
            //设置divider的左上右下边界
            mDivider.draw(canvas);
        }
        canvas.restore();
    }

getItemOffsets方法

@Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
            RecyclerView.State state) {
        if (mOrientation == VERTICAL) {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        }
    }

outRect.set(左,上,右,下);该方法其实是在设置recyclerview每个item的内padding。分别是左padding,上padding,右padding,下padding

看getItemOffsets方法在何处被调用

Rect getItemDecorInsetsForChild(View child) {
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    if (!lp.mInsetsDirty) {
        return lp.mDecorInsets;
    }

    final Rect insets = lp.mDecorInsets;
    insets.set(0, 0, 0, 0);
    final int decorCount = mItemDecorations.size();
    for (int i = 0; i < decorCount; i++) {
        mTempRect.set(0, 0, 0, 0);
        mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
        insets.left += mTempRect.left;
        insets.top += mTempRect.top;
        insets.right += mTempRect.right;
        insets.bottom += mTempRect.bottom;
    }
    lp.mInsetsDirty = false;
    return insets;
}

最终在getItemOffsets中设置的左上右下值,被累加到insets中了,insets其实是记录了每个item的总尺寸

最终会在mesure方法中,被用来测量item的尺寸。

public void measureChild(View child, int widthUsed, int heightUsed) {
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();

    final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);

    widthUsed += insets.left + insets.right;
    heightUsed += insets.top + insets.bottom;

    final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
            getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
            canScrollHorizontally());
    final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
            getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
            canScrollVertically());
    if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
        child.measure(widthSpec, heightSpec);
    }
}
onDrawOver

decoration 的 onDraw,child view 的 onDraw,decoration 的 onDrawOver,这三者依次发生

decoration 的onDraw绘制在最底下,因此其绘制范围任意,但是不在getItemOffsets中的话,会导致overdraw

decoration 的onDrawOver 绘制在最上层

可以利用onDrwaOver 给recyclerview绘制一个蒙版,或者给部分item绘制蒙版

三、layoutmanager

GridLayoutManager

SpanSizeLookup

通常用在grid布局的recyclerview需要添加占一行的header或者footer上

GridLayoutManager gridLayoutManager=new GridLayoutManager(this,2);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
    @Override
    public int getSpanSize(int position) {
        return 0;//Returns the number of span occupied by the item at position.
    }
});
StaggeredGridLayoutManager

该布局设置占一行的item

ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();

if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams){

    StaggeredGridLayoutManager.LayoutParams p =(StaggeredGridLayoutManager.LayoutParams) lp;

    p.setFullSpan(true);//为StaggeredGridLayoutManager.LayoutParams的特有方法,父类中无。
}

三、缓存机制

  • recyclerview 缓存的对象时viewholder,而非view
  • recyclerview 内部是两级内存缓存
  • 滑出界面的viewholder暂时存放在cache中,从cache中移除的viewholder会存放在RecycledViewPool中
  • cache默认存放2个viewholder,RecycledViewPool默认存放5个viewholder
  • 缓存中的viewholder会根据viewtype分类,不同类型的viewholder互不影响
  • RecycledViewPool可以被多个recyclerview共用
  • CacheExtension 该缓存由开发者自己定义维护,如果不实现,默认不用;如果实现了,则该缓存的调用在cache 之后,recycledViewPool之前
//设置cache缓存的个数
recyclerView.setItemViewCacheSize(3);
//设置recycledViewPool缓存的个数
recyclerView.setRecycledViewPool(new RecyclerView.RecycledViewPool() {
    @Override
    public void setMaxRecycledViews(int viewType, int max) {
        super.setMaxRecycledViews(viewType, max);
    }
});
//设置CacheExtension
recyclerView.setViewCacheExtension(new RecyclerView.ViewCacheExtension() {
    @Override
    public View getViewForPositionAndType(RecyclerView.Recycler recycler, int position, int type) {
        return null;
    }
});

四、recyclerview动画

  • recyclerview通过setItemAnimator(ItemAnimator animator)方法来设置添加、删除、移动、改变的动画
  • 默认情况下,recyclerview有自己的一套动画实现,即DefaultItemAnimator继承SimpleItemAnimator,SimpleItemAnimator实现了ItemAnimator
  • 要自定义recyclerview各种操作动画,通常我们的做法是继承SimpleItemAnimator
  • 附上比较好的开源recyclerview动画实现 recyclerview-animators (自己去实现recyclerview的动画貌似比较麻烦,好在我们可以站在前辈的肩膀上)

五、ItemTouchHelper

  • 该类是用来帮助recyclerview实现拖拽和滑动消失的(两个功能)
  • 因此getMovementFlags()方法需要设置拖拽的flag和滑动的flag
  • android3.1版本以上通过属性动画实现
  • 需要给ItemTouchHelper设置一个callback
  • 官方提供了一个简单的callback继承类SimpleCallback(简单拖拽的需求可以用该类完成即可,复杂的需要直接继承callback自定义)

以下是简单实现:

RecyclerView recyclerView = new RecyclerView(this);
final List data = new ArrayList<>();
final QuickRecyclerAdapter adapter = new QuickRecyclerAdapter(data) {
    @Override
    public View setLayoutView(ViewGroup parent, int viewType) {
        return null;
    }

    @Override
    public void setData(BaseViewholder holder, String s, int position) {

    }
};
recyclerView.setAdapter(adapter);
ItemTouchHelper.Callback callback = new ItemTouchHelper.Callback() {
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
            return makeMovementFlags(dragFlags, swipeFlags);
        }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        int from = viewHolder.getAdapterPosition();
        int to = target.getAdapterPosition();
        Collections.swap(data, from, to);
        adapter.notifyItemMoved(from, to);
        return true;
    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        int pos = viewHolder.getAdapterPosition();
        data.remove(pos);
        adapter.notifyItemRemoved(pos);
    }

    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        super.onSelectedChanged(viewHolder, actionState);
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            //设置拖拽时候的背景色
            viewHolder.itemView.setBackgroundColor(getResources().getColor(R.color.colorAccent));
        }
    }

    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        //还原背景色
        viewHolder.itemView.setBackgroundColor(getResources().getColor(R.color.colorPrimary));
    }
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
//绑定recyclerview
itemTouchHelper.attachToRecyclerView(recyclerView);

以上拖拽的触发方式是长按,如果要改成触摸的方式,则需要如下:

//定义接口
interface OnStartDragListener{
    void startDrag(RecyclerView.ViewHolder holder);
}
//activity实现该接口
public MainActivity extends Activity implements OnStartDragListener{
    ...
    public void startDrag(RecyclerView.ViewHolder holder) {
        mHelper.startDrag(holder);
    }
}
//如果是对viewholder中的某个控件触摸滑动,如textview
public void onBindViewHolder(ViewHolder holder,int position){
    holder.text.setOntouchListener(new View.OnTouchListener(){
        @override
        public boolean onTouch(View v,MotionEvent event){
            if(event.getAction==MotionEvent.ActionDown){
                //mListener为实现了OnStartDragListener的activity
                mListener.startDrag(holder);
            }
        }
    });
}

你可能感兴趣的:(Android,JAVA)