难得有机会做一次控件的封装,于是将开发中用的最多的RecyclerView封装了一次。顺便写一个文记录一下封装的过程。
在一般情况下,我们要使用一个RecyclerView,会一定会同时用到RecyclerView,RecyclerView.Adaper,ViewHolder三个类。这三个类在显示一个列表的时候,各自有不同的分工。
这里就可以看出来,虽然三个类都会有用上,但是逻辑最多的还是Adapter类。
封装的目的是为了在调用的时候,可以通过比较少量的代码,就实现出原本的效果,为此进行封装。而刚才也说过,Adapter类是重点,所以先描述下对Adapter类如何进行封装。
在封装之前,先统计一下Adapter类需要用上哪些方法和哪些些对象。
在这之中,1除了自带的ViewGroup参数和viewType参数以外,还有一个可变动的参数就是item的layout布局;2是主要操作item的显示,不能省略,3一般返回list.size()。
在思考完了这些内容之后,就可以对Adapter类进行第一步的封装了。
/**
* @description:由于是封装,所以并不希望直接使用这个类,设置为抽象类。
*/
public abstract class BaseAdapter<T, B extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<BaseViewHolder> {
private static final String TAG = "BaseAdapter";
private LayoutInflater mLayoutInflater;
private View mView;
private List<T> mList; // 数据集合
public BaseAdapter(){
mList = new ArrayList<>();
}
public BaseAdapter(List<T> mList){
this.mList = mList;
}
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
mLayoutInflater = LayoutInflater.from(parent.getContext());
mView = mLayoutInflater.inflate(getLayoutResource(), parent, false); // 返回布局文件的id
BaseViewHolder baseViewHolder = new BaseViewHolder(mView);
return baseViewHolder;
}
@Override
public void onBindViewHolder(@NonNull BaseViewHolder vh, int position) {
setItemView(vh, mList.get(position), position); // 做成抽象方法,让子类自己设置子布局
}
@Override
public int getItemCount() {
return mList.size();
}
// 抽象方法,即继承类必须实现
protected abstract int getLayoutResource(); //返回布局的Rid
protected abstract void setItemView(BaseViewHolder vh, T t, int position); //设置子布局
}
由于封装还需要有一些比较方便的功能,所以也将这些功能套进去,封装就完成了。
public abstract class BaseAdapter<T, B extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<BaseViewHolder> {
private static final String TAG = "BaseAdapter";
private LayoutInflater mLayoutInflater;
private View mView;
private List<T> mList; // 数据集合
private OnItemClickListener onItemClickListener; //点击监听事件
private OnItemLongClickListener onItemLongClickListener; //长按监听事件
private int selectedItem; //选定的项
public BaseAdapter(){
mList = new ArrayList<>();
}
public BaseAdapter(List<T> mList){
this.mList = mList;
}
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
mLayoutInflater = LayoutInflater.from(parent.getContext());
mView = mLayoutInflater.inflate(getLayoutResource(), parent, false);
BaseViewHolder baseViewHolder = new BaseViewHolder(mView);
setClickListener(baseViewHolder);
return baseViewHolder;
}
@Override
public void onBindViewHolder(@NonNull BaseViewHolder vh, int position) {
setItemView(vh, mList.get(position), position);
}
/**
* 绑定监听事件
* @param vh
*/
protected void setClickListener(final BaseViewHolder vh){
if (vh == null) return;
View view = vh.itemView;
if (view == null) return;
if (onItemClickListener != null){
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onItemClickListener.onItemClick(v, vh.getLayoutPosition());
}
});
}
if (onItemLongClickListener != null){
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onItemLongClickListener.onItemLongClick(v, vh.getLayoutPosition());
}
});
}
}
@Override
public int getItemCount() {
return mList.size();
}
public void setSelectedItem(int position) {
this.selectedItem = position;
notifyDataSetChanged(); // 修改选定项之后,进行一次视图刷新
}
public boolean isSelected(int positon){
return positon == selectedItem;
}
public T getSelectedItem() {
return mList.get(selectedItem);
}
//把list的方法套个皮
public void addAll(List<T> list){
mList.addAll(list);
}
//把list的方法套个皮
public void add(T t){
mList.add(t);
}
//把list的方法套个皮
public boolean remove(T t){
return mList.remove(t);
}
//把list的方法套个皮
public boolean removeAll(List<T> list){
return mList.removeAll(list);
}
//把list的方法套个皮
public void clear(){
mList.clear();
}
//把list的方法套个皮
public List<T> getmList() {
return mList;
}
// 一些setter和getter方法
public BaseAdapter setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
return this;
}
public OnItemClickListener getOnItemClickListener() {
return onItemClickListener;
}
public BaseAdapter setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {
this.onItemLongClickListener = onItemLongClickListener;
return this;
}
public OnItemLongClickListener getOnItemLongClickListener() {
return onItemLongClickListener;
}
// 抽象方法,即继承类必须实现
protected abstract int getLayoutResource(); //返回布局的Rid
protected abstract void setItemView(BaseViewHolder vh, T t, int position); //设置子布局
// 接口,短点击和长点击按钮接口
public interface OnItemClickListener{
void onItemClick(View v, int position);
}
interface OnItemLongClickListener{
void onItemLongClick(View v, int position);
}
}
我们减少ViewHolder的任务,让它只做View的绑定,最终ViewHodler长这样。
public class BaseViewHolder extends RecyclerView.ViewHolder{
private View mView; //保存View
public BaseViewHolder(@NonNull View itemView) {
super(itemView);
mView = itemView;
}
/**
* 绑定资源文件的id,但是要做强制类型转换
* @param Rid
* @return
*/
public View setView(int Rid){
return mView.findViewById(Rid);
}
}
RecyclerView主要做adapter的绑定和item的排列方式,姑且也做一个简易的封装。
public class BaseRecyclerView extends RecyclerView {
private static final String TAG = "BaseRecyclerView";
private int columns; // 列数或者行数
private RecyclerMode recyclerMode; // 设置模式
public BaseRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public BaseRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public BaseRecyclerView setMode(RecyclerMode mode){
return setMode(mode, 1);
}
public BaseRecyclerView setMode(RecyclerMode mode, int columns){
recyclerMode = mode;
if (columns < 1) columns = 1;
this.columns =columns;
switch (mode){
case LINEAR_VERTICAL:
LinearLayoutManager manager = new LinearLayoutManager(getContext());
manager.setOrientation(LinearLayoutManager.VERTICAL);
setLayoutManager(manager);
break;
case LINEAR_HORIZONTAL:
LinearLayoutManager manager1 = new LinearLayoutManager(getContext());
manager1.setOrientation(LinearLayoutManager.HORIZONTAL);
setLayoutManager(manager1);
break;
case GRID_VERTICAL:
GridLayoutManager manager2 = new GridLayoutManager(getContext(), columns);
manager2.setOrientation(VERTICAL);
setLayoutManager(manager2);
break;
case GRID_HORIZONTAL:
GridLayoutManager manager3 = new GridLayoutManager(getContext(), columns);
manager3.setOrientation(HORIZONTAL);
setLayoutManager(manager3);
break;
}
return this;
}
}
public enum RecyclerMode{
LINEAR_VERTICAL, LINEAR_HORIZONTAL, GRID_VERTICAL, GRID_HORIZONTAL
}
但是我个人在这里碰到了一个新的需求:内部每个item的间距显示。
我个人认为item间距这种事情是不应该写在item的布局文件中的,所以我打算通过代码的方式来设置每个item的间距。
接下来的内容都以竖直排列Vertical来说明:
public class BaseRecyclerView extends RecyclerView {
.......省略之前出现过的代码
private int width;
private int height;
/**
* 设置RecyclerView的内部间距
* @param space 内部的间距(单位是dp)
*/
public BaseRecyclerView setInnerMargin(int space){
space = PixelUtil.dipToPx(space, getContext());
final int finalSapce = space;
post(new Runnable() {
@Override
public void run() {
getViewWidthAndHeight();
int itemWidth = 0;
int itemHeight = 0;
try {
itemWidth = getLayoutManager().getChildAt(0).getWidth();
itemHeight = getLayoutManager().getChildAt(0).getHeight();
}catch (NullPointerException e){
Log.e(TAG,"you need to set adapter and item before set innerMargin");
Log.e(TAG,"you need to set adapter and item before set innerMargin");
}
addItemDecoration(new InnerItemDecoration(finalSapce, height, width,
itemHeight, itemWidth, columns, recyclerMode));
}
});
return this;
}
private void getViewWidthAndHeight(){
width = getMeasuredWidth();
height = getMeasuredHeight();
}
}
class PixelUtil {
/**
* 像素转换,dp转px
* @param dp dp
* @param context 上下文
* @return px
*/
public static int dipToPx(int dp, Context context){
float scale = context.getResources().getDisplayMetrics().density;
return (int)(dp * scale + 0.5f);
}
}
class InnerItemDecoration extends RecyclerView.ItemDecoration{
private static final String TAG = "InnerItemDecoration";
private int space; // 单位是px不是dp
private int height;
private int width;
private int itemHeight;
private int itemWidth;
private int columns; // 列数
private RecyclerMode mode;
public InnerItemDecoration(int space, int height, int width, int itemHeight, int itemWidth, int columns, RecyclerMode recyclerMode){
this.space = space;
this.columns = columns;
this.mode = recyclerMode;
this.width = width;
this.height = height;
this.itemHeight = itemHeight;
this.itemWidth = itemWidth;
}
/*
这一段逻辑要着重处理
1. 设置内部每个item的margin
2. 在1的基础上还要增加对Grid模式的适配
对于Grid模式而言
GRID_VERTICAL的话,竖直自定义,水平贴边均分
1. 具体做法,获取RecyclerView的宽度,让左右两边的贴边,再设置中间元素的left
*/
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
int totalCount = parent.getAdapter().getItemCount();
setOffset(outRect, position, totalCount);
}
private void setOffset(Rect outRect, int position, int totalCount) {
switch (mode){
case GRID_HORIZONTAL:
case LINEAR_HORIZONTAL:
setOffsetHorizontal(outRect, position, totalCount);
break;
case GRID_VERTICAL:
case LINEAR_VERTICAL:
setOffsetVertical(outRect, position, totalCount);
break;
}
}
// Vertical,不包括边缘
private void setOffsetVertical(Rect outRect, int position, int totalCount) {
outRect.top = space/2;
outRect.bottom = space/2;
double w = ((double)width - itemWidth * columns) / (columns * (columns-1));
int p = position % columns;
outRect.left = (int)(w*p);
// 最上面的一排(columns个item)
if (position < columns)
outRect.top = 0;
// 最下面一排
int tem = (int) (Math.ceil((double) totalCount/columns) * columns);
if (position >= tem-columns)
outRect.bottom = 0;
}
// Horizontal
private void setOffsetHorizontal(Rect outRect, int position, int totalCount) {
outRect.left = space /2;
outRect.right = space /2;
double w = ((double)height - itemHeight * columns) / (columns * (columns-1));
int p = position % columns;
outRect.top = (int)(w*p);
// 最左边的一排(columns个item)
if (position < columns)
outRect.left = 0;
// 最右边一排
int tem = (int) (Math.ceil((double) totalCount/columns) * columns);
if (position >= tem-columns)
outRect.right = 0;
}
}
封装完之后就是如何使用了,用起来还是很方便的,Adapter类只要实现两个方法。
// String是数据实体
public class MyAdapter extends BaseAdapter<String, BaseViewHolder> {
@Override
protected int getLayoutResource() {
return R.layout.item_string;
}
@Override
protected void setItemView(BaseViewHolder baseViewHolder, String s, int position) {
((TextView)baseViewHolder.setView(R.id.tv)).setText(s); // 实现具体的界面逻辑
}
}
public class MainActivity extends AppCompatActivity {
BaseRecyclerView recyclerView;
MyAdapter adapter = new MyAdapter(); // 创建实例的时候直接用无参构造函数就行
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerView);
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
adapter.addAll(list);
adapter.setOnItemClickListener(new BaseAdapter.OnItemClickListener() {
@Override
public void onItemClick(View v, int position) {
Toast.makeText(getApplicationContext(), adapter.getmList().get(position),Toast.LENGTH_SHORT).show();
}
});
recyclerView.setMode(RecyclerMode.GRID_VERTICAL, 3);
recyclerView.setAdapter(adapter);
recyclerView.setInnerMargin(10); //调用这行之前要保证已经setAdapter,且adapter内有item
}
}