Android RecyclerView —— 基本使用

Android RecyclerView —— 基本使用

Android RecyclerView —— 适配器封装探索

Android RecyclerView —— 自定义分割线

RecyclerView 我相信大家都不陌生,是Google在Android 5.0 的时候推出的一个可以在有限的窗口中展示大量数据集的控件(类似的控件有ListView、GridView),放在了 com.android.support:recyclerview-v7:xx.x.x 包下(xx表示版本),那么既然已经有了ListView、GridView,为什么还要使用RecyclerView呢?主要是因为RecyclerView高度解耦,非常灵活,使用简单的代码就能达到一些绚丽的效果。不过 RecyclerView 有一个地方比较坑,那就是 item 的点击和长按事件系统都没有实现,需要自己实现。

使用基本流程

recyclerView = findView(R.id.id_recyclerview);
// 设置布局管理器
recyclerView.setLayoutManager(layout);
// 设置Adapter
recyclerView.setAdapter(adapter)
// 设置Item增加、移除动画(根据需求确认是否需要)
recyclerView.setItemAnimator(new DefaultItemAnimator());
// 添加分割线(根据需求确认是否需要)
recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.HORIZONTAL_LIST));

看到上面的代码,我们发现至少需要设置一个 LayoutManagerAdapter ,而以前的 ListViewGridView 最基本的使用,只需要设置 Adapter 就行了,那么感觉使用 RecyclerViewListView 更加麻烦,代码量更多,这是因为 RecyclerView 这个控件的主要功能就是复用与回收,其他的效果和功能都可以开发者根据需求自定义,这也就是我们为什么可以只要改变 RecyclerView.LayoutManager 就可以实现 ListView、GridView 以及瀑布流的效果了。

实现 ListView 效果(LinearLayoutManager)

想要实现 ListView 效果,只需设置 RecyclerView.LayoutManagerLinearLayoutManager 就行了,然后设置 Adapter
LinearLayoutManager 常用的 构造方法有2个:

// 只需要一个上下文参数,默认表示一个垂直方向的列表
public LinearLayoutManager(Context context)

// 参数1:上下文;
// 参数2:列表方向,值为 水平:HORIZONTAL(0) 垂直:VERTICAL(1);
// 参数3:false表示RecycleView中item从上到下依次添加;true表示RecycleView中item从下到上依次添加,一般为 false
public LinearLayoutManager(Context context, int orientation, boolean reverseLayout)

实现 GridView 效果(GridLayoutManager)

想要实现 GridView 效果,只需设置 RecyclerView.LayoutManagerGridLayoutManager 就行了,然后设置 Adapter
GridLayoutManager 常用的 构造方法有2个:

// 参数1:上下文参数
// 参数2:列数,默认表示一个垂直方向的列表
public GridLayoutManager(Context context, int spanCount) {
   
}

// 参数1:上下文;
// 参数2:列数
// 参数3:列表方向,值为 水平:HORIZONTAL(0) 垂直:VERTICAL(1);
// 参数4:false表示RecycleView中item从上到下依次添加;true表示RecycleView中item从下到上依次添加,一般为 false
public GridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
    
}

对于 GridLayoutManager 类,有一个方法是比较使用的,那就是 GridLayoutManagersetSpanSizeLookup(GridLayoutManager.SpanSizeLookup spanSizeLookup) ,该方法可以对每一个 item 所占列数进行设置。
比如,设置如下:

// 设置当前位置占用的列数
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
    // 该方法返回 position 位置的 item 占用 GridLayout 的多少列
    @Override
    public int getSpanSize(int position) {
        if (position <= 3) {
            return 6;
        } else if (position <= 9) {
            return 3;
        } else if (position <= 15) {
            return 2;
        } else if (position <= 27) {
            return 1;
        } else if (position <= 35) {
            return 3;
        } else if (position <= 39) {
            return 6;
        } else {
            return 1;
        }
    }
});

效果如下:
Android RecyclerView —— 基本使用_第1张图片

实现 瀑布流效果(StaggeredGridLayoutManager)

想要实现瀑布流效果,只需设置 RecyclerView.LayoutManagerStaggeredGridLayoutManager 就行了,然后设置 Adapter
StaggeredGridLayoutManager 常用的 构造方法为:

// 参数1:列数
// 参数2:列表方向,值为 水平:HORIZONTAL(0) 垂直:VERTICAL(1);
public StaggeredGridLayoutManager(int spanCount, int orientation) {

}

RecyclerView.AdapternotifyXxx 系列方法使用说明:

该系列方法都表示类表数据发生变化时,调用用来更新列表数据。但是可以刷新全部或者刷新、插入、移除指定位置的数据。也就是全部刷新或者局部刷新。
刷新改变:
notifyDataSetChanged():刷新全部列表
notifyItemChanged(int position)notifyItemChanged(int position, @Nullable Object payload):刷新指定位置数据
notifyItemRangeChanged(int positionStart, int itemCount)notifyItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload):刷新指定范围的数据

插入数据(调用下面方法有动画效果,动画效果可以自定义 recyclerView.setItemAnimator() 方法可以设置动画效果):
notifyItemInserted(int position):插入了单条数据到指定位置
notifyItemRangeInserted(int positionStart, int itemCount):从指定位置开始,插入了指定数量的item数据

** 移除数(调用下面方法有动画效果,动画效果可以自定义 recyclerView.setItemAnimator() 方法可以设置动画效果):**
notifyItemRemoved(int position):移除了单条数据到指定位置
notifyItemRangeRemoved(int positionStart, int itemCount):从指定位置开始,移除了指定数量的item数据

数据位置移动(交换 item 位置时调用)
notifyItemMoved(int fromPosition, int toPosition):用来交换2个item的位置

可能出现的问题及解决办法

  1. 使用 Adapter 的 notifyItemRemoved(position)notifyItemInserted(position) 方法产生的问题:
    问题:使用上面两个方法之后不会使 position 及其之后位置的 itemView 重新 onBindViewHolder,会导致下标错乱,如果一直调用notifyItemRemoved(position)来移除的话,那么就会发现真正移除的并不是想要移出的,而且还非常有可能出现 IndexOutOfBoundsException 异常。
    解决办法:在调用上面两个方法(其中一个)之后继续调用 Adapter 的 notifyItenRangeChanged(int positionStart, int itemCount) 方法,使下面的 itemView 重新 onBind,就可以了。

  2. 使用瀑布流显示图片可能出现的问题以及解决办法:
    问题1:item 的位置不断发生变化。
    解决办法:调用 StaggeredGridLayoutManagersetGapStrategy(int gapStrategy) 方法:

     staggeredGridLayoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
    

问题2:当解决 “问题1” 时,会产生另外一个问题,顶部会留下空白
解决办法:给 RecyclerView 控件增加监听,并且在监听中调用调用 StaggeredGridLayoutManagerinvalidateSpanAssignments() 方法:

	recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
	    @Override
	    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
	        super.onScrollStateChanged(recyclerView, newState);
	        layoutManager.invalidateSpanAssignments();
	    }
	});

问题3:因为 item 布局的复用,但是每张图片的高度又不同,所以导致 item 图片闪烁问题
解决办法:使用一个集合保存每一个 item 的高度,然后在显示的时候对每一个 item 的高度重新设置

使用 ItemTouchHelper 类实现 RecyclerView 的拖拽和侧滑删除功能:

​
// 使用 ItemTouchHelper 类实现 RecyclerView 的拖拽和侧滑删除功能
private ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
	/**
	 * 用于设置拖拽和滑动的方向
	 */
	@Override
	public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
		int dragFlags = 0, swipeFlags = 0;
		RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
		if (layoutManager instanceof StaggeredGridLayoutManager || layoutManager instanceof GridLayoutManager) {
			// 网格式布局有4个方向拖拽,但是不能测试
			dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
		} else if (layoutManager instanceof LinearLayoutManager) {
			// 线性式布局有2个方向
			dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; // 设置拖拽方向为 上下

			swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END; // 设置侧滑方向 左右
		}
		// 调用 makeMovementFlags() 方法 或者 makeFlag() 方法使值生效
		return makeMovementFlags(dragFlags, swipeFlags);// swipeFlags 为0的话item不能滑动
	}

	/**
	 * 长按 item 拖拽时会回调这个方法
	 */
	@Override
	public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
		// 获取拖拽位置
		int srcPosition = viewHolder.getAdapterPosition();
		int targetPosition = target.getAdapterPosition();
		// 交换两个位置的数据
		List datas = adapter.getDatas();
		changePosition(srcPosition, targetPosition, datas);
		// 更新Adapter
		adapter.notifyItemMoved(srcPosition, targetPosition);
		return true;
	}

	/**
	 * List集合交换两个位置的数据
	 */
	private void changePosition(int srcPosition, int targetPosition, List datas) {
		String srcData = datas.get(srcPosition);
		String targetData = datas.get(targetPosition);
		datas.add(srcPosition, targetData);
		datas.add(targetPosition + 1, srcData);
		datas.remove(srcPosition + 1);
		datas.remove(targetPosition + 1);
	}

	/**
	 * 处理滑动删除
	 * @param viewHolder
	 * @param direction
	 */
	@Override
	public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
		// 获取位置
		int adapterPosition = viewHolder.getAdapterPosition();
		List datas = adapter.getDatas();
		// 移除数据
		datas.remove(adapterPosition);
		// 更新Adapter数据,一定要调用 notifyItemRangeChanged() 方法,否则角标会错乱
		adapter.notifyItemRemoved(adapterPosition);
		adapter.notifyItemRangeChanged(adapterPosition, datas.size() - adapterPosition);

		RLog.i("删除 item .......");
	}

	/**
	 * 开始拖拽时回调
	 * @param viewHolder
	 * @param actionState
	 */
	@Override
	public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
		super.onSelectedChanged(viewHolder, actionState);
		// 判断状态
		if (ItemTouchHelper.ACTION_STATE_DRAG == actionState || ItemTouchHelper.ACTION_STATE_SWIPE == actionState) {
			viewHolder.itemView.setBackgroundColor(getResources().getColor(R.color.start_drag_color));
			RLog.i("开始拖拽或者开始侧滑.......");
		}
	}

	/**
	 * 拖拽完成时回调,可以恢复颜色
	 * @param recyclerView
	 * @param viewHolder
	 */
	@Override
	public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
		super.clearView(recyclerView, viewHolder);
		viewHolder.itemView.setBackgroundColor(getResources().getColor(R.color.item_bg));
		RLog.i("拖拽完成.......");
	}

	/**
	 * 拖拽视图位置发生变化时回调,动态改变颜色
	 */
	@Override
	public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
		super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
		// 改变颜色(这里只有侧滑时改变颜色)
		viewHolder.itemView.setAlpha(1 - (Math.abs(dX) / screenWidth));
		RLog.i("位置发生变化.......");
	}

	@Override
	public boolean isLongPressDragEnabled() {
		// 返回true则为所有item都设置可以拖拽
		return true;
	}
});

// 记住:需要将 RecyclerView 和 ItemTouchHelper 绑定到一起
itemTouchHelper.attachToRecyclerView(recyclerView);

相关代码可在 github 上查看下载

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