0
点赞
收藏
分享

微信扫一扫

Android Study Material Design 二 之:这可能是RecyclerView最全解析 中级进阶(二)


LZ-Says:自从国庆回来,状态就一直不好,乱七八糟的事儿,真是不知道如何开口,相应的学习计划也下降的几乎为0了。还是要硬着头皮继续上,越不动,越懒,当某天习惯了可就难改了,加油~

前言

前几天写了一篇RecyclerView初步使用,有兴趣的可以看下,地址如下:

​​Android Study Material Design之:这可能是RecyclerView最全解析(一)​​

而今天在此基础上继续深入,让大家更好的掌握RecyclerView~

本文目标

学习完本篇之后,你会掌握如下技能:

  • 为RecyclerView添加分割线;
  • 为RecyclerView装饰更美的头部以及底部;
  • 让RecyclerView更人性化,添加交互动画。

效果简阅

光说不练假把式,没图,多不爽?


Android Study Material Design 二 之:这可能是RecyclerView最全解析 中级进阶(二)_material-design

进入Study模式

- - - - - - 前方高能,注意哦- - - - - -

一、添加分割线

首先我们来回顾下ListView是如何添加分割线,如下:

<ListView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:listSelector="@color/background" />

而在RecyclerView中,细心的你会发现,卧槽,怎么找不到这个属性呢?

这里就不得不再次说下RecyclerView这个东西:高内聚-低耦合,正是由于RecyclerView具有此特性,才方便我们可能自由的定制我们的分割线,想怎么玩就怎么玩,But,我们需要依赖一个特别的东西,如下:

ItemDecoration: 作用就是在RecyclerView上绘制(定制)分割线,而其下还有俩个关键方法需要我们来关注,如下。

  • getItemOffsets: 获得item偏移量;
  • onDraw: RecycleView回调此函数,需自己实现绘制分割线

基于上面,还要和RecyclerView中的addItemDecoration()配合使用,大话不多说,我们首先了解下编写思路。

重写方法之后,你需要做如下几步:

  • 1. 分割线样式选择

既然是绘制分割线,那总的有分割线才是,我们先使用系统提供分割线来撸一波

  • 2. 确定当前显示类型

分割线显示,就拿最简单的LinearLayout举例,默认水平,还可设置垂直,那我们的分割线同理,不光有默认绘制方向,对外也能提供入口

  • 3. 获取item偏移量

获取item偏移量,也就是获取矩形的区域,这点不难理解,我们绘制分割线,是通过onDraw()方法去绘制,那么绘制的点又是怎么确定的呢?附上简图一张。

  • 4. 绘制分割线

如第3点所述,根据item显示方向不同,我们分割线的绘制也不相同

基本思路如上,具体代码中会体现:

package com.materialdesignstudy.ltem;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutParams;
import android.view.View;

/**
* 分割线 Created by HLQ on 2017/9/26
*/
public class DividerItemDecoration extends RecyclerView.ItemDecoration {

private int mOrientation = LinearLayoutManager.VERTICAL;

private Drawable mDivider;

private int[] attrs = new int[]{android.R.attr.listDivider};

public DividerItemDecoration(Context context, int orientation) {
// 引用系统属性样式
TypedArray typedArray = context.obtainStyledAttributes(attrs);
mDivider = typedArray.getDrawable(0);
typedArray.recycle();
setOrientation(orientation);
}

/**
* 设置参数枚举类型 水平 or 垂直
* @param orientation
*/
public void setOrientation(int orientation) {
// 避免传入非法类型
if (orientation != LinearLayoutManager.HORIZONTAL && orientation != LinearLayoutManager.VERTICAL) {
throw new IllegalArgumentException("非法参数枚举类型");
}
mOrientation = orientation;
}

/**
* 1. 获得条目偏移量
*
* @param outRect
* @param view
* @param parent
* @param state
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
// 首先获取条目之间的间隙宽度 也就是Rect矩形区域
if (mOrientation == LinearLayoutManager.VERTICAL) { // 垂直
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else { // 水平
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}

/**
* 2. RecycleView回调此函数 开发者需自己实现绘制分割线
*
* @param c
* @param parent
* @param state
*/
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == LinearLayoutManager.VERTICAL) { // 垂直
drawVertical(c, parent);
} else { // 水平
drawHorizontal(c, parent);
}
super.onDraw(c, parent, state);
}

/**
* 绘制水平分割线
*
* @param c
* @param parent
*/
private void drawVertical(Canvas c, RecyclerView parent) {
// 考虑父容器padding值
int left = parent.getPaddingLeft();
// 绘制右侧时需要减去右侧padding值
int right = parent.getWidth() - parent.getPaddingRight();
// 获取父容器下子控件个数 也就是item个数
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (LayoutParams) child.getLayoutParams();
// 高度等于子控件底部加margin加y轴值
int top = child.getBottom() + params.bottomMargin + Math.round(ViewCompat.getTranslationY(child));
// 底部等于高度加当前实际高度
int bottom = top + mDivider.getIntrinsicHeight();
// 设置绘制位置
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}

private void drawHorizontal(Canvas c, RecyclerView parent) {
int top = parent.getPaddingTop();
int bottom = parent.getHeight() - parent.getPaddingBottom();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (LayoutParams) child.getLayoutParams();
int left = child.getRight() + params.rightMargin + Math.round(ViewCompat.getTranslationX(child));
int right = left + mDivider.getIntrinsicHeight();
// 设置绘制位置
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}

}

运行后查看效果如下:


Android Study Material Design 二 之:这可能是RecyclerView最全解析 中级进阶(二)_分割线_03

          


Android Study Material Design 二 之:这可能是RecyclerView最全解析 中级进阶(二)_分割线_04

LinearLayout分割线绘制完成,那么GirdView的呢?

首先我们分析下俩者区别,很简单,一个单一,一个多嘛。
LinearLayout只有俩种状态,我们也只需要绘制横向或者纵向即可,而GirdView,则需要绘制横向以及纵向俩种,其实也就是改了部分而已,关键代码如下:

@Override
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
// 偏移量 代表左上右下分别对应上一个的偏移值
int right = mDivider.getIntrinsicWidth();
int bottom = mDivider.getIntrinsicHeight();
if (isLastRow(parent)) { // 代表当前是最后一行 不绘制底部
bottom = 0;
}
if (isLastCol(itemPosition, parent)) { // 代表当前是最后一列 不绘制右侧
right = 0;
}
outRect.set(0, 0, right, bottom);
}

/**
* 是否是最后一行
*
* @return
*/
private boolean isLastRow(RecyclerView parent) {
int spanCount = getSpanCount(parent);
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
int childCount = parent.getAdapter().getItemCount();
int lastRowCount = childCount % spanCount;
if (lastRowCount == 0 || lastRowCount < spanCount) { // 最后一行情况:1)被整除 2)小于列数
return true;
}
}
return false;
}

/**
* 是否是最后一列
*
* @return
*/
private boolean isLastCol(int itemPosition, RecyclerView parent) {
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
// 拿到当前有多少列
int spanCount = getSpanCount(parent);
if ((itemPosition + 1) % spanCount == 0) {
return true;
}
}
return false;
}

/**
* 获取列数
* @param parent
* @return
*/
private int getSpanCount(RecyclerView parent) {
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
// 拿到当前有多少列
int spanCount = gridLayoutManager.getSpanCount();
return spanCount;
}
return 0;
}

效果如下:


Android Study Material Design 二 之:这可能是RecyclerView最全解析 中级进阶(二)_recyclerview_05

嗯哼,是不是感觉一套下来感觉思路也开拓了许多呢?

二、来一场模仿秀:为RecyclerView添加头部和底部

在此,首先为大家附上效果图,如下:


Android Study Material Design 二 之:这可能是RecyclerView最全解析 中级进阶(二)_recyclerview_06

同理,RecyclerView的添加头部以及底部,我们同样可参考ListView源码进行操作,那么接下来,我们看看ListView中源码是如何进行操作的,而我们又该如何进行改造,从而实现我们的效果呢?

打开源码,Alt+7查看当前包含方法,找到ListView的添加头部以及底部代码部分,如下图:


Android Study Material Design 二 之:这可能是RecyclerView最全解析 中级进阶(二)_Itemtouch_07

下面,我们进行简易分析。

首先找到addHeaderView方法,如下:

/**
* Add a fixed view to appear at the top of the list.在list上添加一个view
* @param v The view to add.
*/
public void addHeaderView(View v) {
addHeaderView(v, null, true);
}

没啥特别的,传入我们要添加的View,其他俩个参数啥意思不知道,只能继续往下看,代码和注释并存。

/**
* Add a fixed view to appear at the top of the list.
* @param v The view to add. view被添加
* @param data Data to associate with this view view数据源
* @param isSelectable whether the item is selectable 当前item是否被选中
*/
public void addHeaderView(View v, Object data, boolean isSelectable) {
// 校验当前view父容器是否非空 并且 view的父容器不是当前容器
if (v.getParent() != null && v.getParent() != this) { // 代表当前已经为父容器添加了子视图,也就是我们的头布局 必须remove掉方才可能继续添加 这点比较局限性
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "The specified child already has a parent. "
+ "You must call removeView() on the child's parent first.");
}
}
// 以下为数据常量初始化
final FixedViewInfo info = new FixedViewInfo();
info.view = v;
info.data = data;
info.isSelectable = isSelectable;
// 添加容器
mHeaderViewInfos.add(info);
mAreAllItemsSelectable &= isSelectable;
// 校验当前adapter是否有效
// Wrap the adapter if it wasn't already wrapped.
if (mAdapter != null) {
if (!(mAdapter instanceof HeaderViewListAdapter)) { // 如果当前adapter不属于HeaderViewListAdapter 这里又做了哪儿些操作呢?
wrapHeaderListAdapterInternal();
}

// In the case of re-adding a header view, or adding one later on,
// we need to notify the observer.
if (mDataSetObserver != null) {
mDataSetObserver.onChanged();
}
}
}

怀着疑问,我们继续往下看~

protected void wrapHeaderListAdapterInternal() { // 这里对adapter再次进行一次封装 之前的adapter的类型为:ListAdapter 
mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, mAdapter);
}

protected HeaderViewListAdapter wrapHeaderListAdapterInternal(
ArrayList<ListView.FixedViewInfo> headerViewInfos,
ArrayList<ListView.FixedViewInfo> footerViewInfos,
ListAdapter adapter) {
return new HeaderViewListAdapter(headerViewInfos, footerViewInfos, adapter); // 它的处理便是真正封装内容 将adapter转变成HeaderViewListAdapter
}

public HeaderViewListAdapter(ArrayList<ListView.FixedViewInfo> headerViewInfos,
ArrayList<ListView.FixedViewInfo> footerViewInfos,
ListAdapter adapter) {
mAdapter = adapter;
mIsFilterable = adapter instanceof Filterable;

// 如果headerViewInfos等于null 代表属于item项实际body部分 也就是我们正常显示的部分
if (headerViewInfos == null) {
mHeaderViewInfos = EMPTY_INFO_LIST;
} else { // 代表当前是头布局 headerViewInfos
mHeaderViewInfos = headerViewInfos;
}

// 如果footerViewInfos等于null 代表属于item项实际body部分 也就是我们正常显示的部分
if (footerViewInfos == null) {
mFooterViewInfos = EMPTY_INFO_LIST;
} else { // 代表当前是底布局 footerViewInfos
mFooterViewInfos = footerViewInfos;
}

mAreAllFixedViewsSelectable =
areAllListInfosSelectable(mHeaderViewInfos)
&& areAllListInfosSelectable(mFooterViewInfos);
}

看的是不是有点晕乎,为什么中间又跑出来一个adapter呢?这个问题,我们画个简图简单说明下:


Android Study Material Design 二 之:这可能是RecyclerView最全解析 中级进阶(二)_分割线_08

  • 如上图所示,这是ListView最终展示的效果,那么为什么中间还会有个adapter呢?

最初,父容器mAdapter为ListAdapter,当添加了头部时,此时的mAdapter变转化为HeaderViewAdapter,而添加了尾部时,又转化为FooterViewAdapter,而真正数据展示,也就是body部分却再次回归ListAdapter。其中的过程很像是中间代理,有木有一种分工明确的感觉,下面再举个例子。

好比工厂生产手机,他们对外提供的只是一部完整的手机,而他们不会生产其中的零件,看到这里,大家有没有是懂非懂的感觉?
有还是没有,我们都该撸码了骚年。

那么下面,我们依据ListView上面的简单分析源码进行我们的模仿秀。

既然是模仿,首先我们需要搭建一个中间层,也就是ListView的HeaderViewAdapter,如下:

package com.materialdesignstudy.adapter;

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

import java.util.ArrayList;

/**
* Created by HLQ on 2017/10/12
* 模仿ListView添加头部时 中间的adapter
*/
public class HeaderViewRecyclerAdapter extends RecyclerView.Adapter {

private RecyclerView.Adapter mAdapter;

private ArrayList<View> mHeaderViewInfos;
private ArrayList<View> mFooterViewInfos;

public HeaderViewRecyclerAdapter(ArrayList<View> headerViewInfos, ArrayList<View> footerViewInfos, RecyclerView.Adapter adapter) {
// 这里面的逻辑和ListView中很相似,不过相对来说我们这里更为简单一些
// 这里 我们只需要考虑 三种情况即可 头布局 正常body 尾布局
mAdapter = adapter;
if (headerViewInfos == null) {
mHeaderViewInfos = new ArrayList<>();
} else {
mHeaderViewInfos = headerViewInfos;
}
if (footerViewInfos == null) {
mFooterViewInfos = new ArrayList<>();
} else {
mFooterViewInfos = footerViewInfos;
}
}

/**
* 判断当前条目类型 通过类型指定相关视图
*
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {
int numHeaders = getHeadersCount();
// 头部
if (position < numHeaders) {
return RecyclerView.INVALID_TYPE;
}
// 正常内容载体
int adapterCount = 0;
int adjPosition = position - numHeaders;
if (mAdapter != null) {
adapterCount = mAdapter.getItemCount();
if (adjPosition < adapterCount) {
return mAdapter.getItemViewType(adjPosition);
}
}
// 底部
return RecyclerView.INVALID_TYPE - 1;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == RecyclerView.INVALID_TYPE) {// 头部
return new HeaderViewHolder(mHeaderViewInfos.get(0));
} else if (viewType == RecyclerView.INVALID_TYPE - 1) {
return new HeaderViewHolder(mFooterViewInfos.get(0));
}
return mAdapter.onCreateViewHolder(parent, viewType);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
int numHeaders = getHeadersCount();
// 头部
if (position < numHeaders) {
return;
}
int adapterCount = 0;
int adjPosition = position - numHeaders;
if (mAdapter != null) {
adapterCount = mAdapter.getItemCount();
if (adjPosition < adapterCount) {
mAdapter.onBindViewHolder(holder, adjPosition);
return;
}
}
}

@Override
public int getItemCount() {
if (mAdapter != null) {
return getFootersCount() + getHeadersCount() + mAdapter.getItemCount();
} else {
return getFootersCount() + getHeadersCount();
}
}

public int getFootersCount() {
return mFooterViewInfos.size();
}

public int getHeadersCount() {
return mHeaderViewInfos.size();
}

private static class HeaderViewHolder extends RecyclerView.ViewHolder {

public HeaderViewHolder(View itemView) {
super(itemView);
}
}

}

其次,需要我们自定义RecyclerView,也可以理解为对之前的RecyclerView进行改造,使其具有添加头部以及尾部的功能,如下:

package com.materialdesignstudy.weight;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;

import com.materialdesignstudy.adapter.HeaderViewRecyclerAdapter;

import java.util.ArrayList;

/**
* 为RecycleView添加头部Created by HLQ on 2017/10/12
*/

public class MyRecyclerView extends RecyclerView {

// 存放头部布局容器
private ArrayList<View> mHeaderViewInfos = new ArrayList<>();
// 存放底部布局容器
private ArrayList<View> mFooterViewInfos = new ArrayList<>();

private Adapter mAdapter;

public MyRecyclerView(Context context) {
super(context);
}

public MyRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}

public MyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

public void addHeaderView(View view) {
mHeaderViewInfos.add(view);
if (mAdapter != null) {
if (!(mAdapter instanceof HeaderViewRecyclerAdapter)) {
mAdapter = new HeaderViewRecyclerAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
}
}
}

public void addFooterView(View view) {
mFooterViewInfos.add(view);
if (mAdapter != null) {
if (!(mAdapter instanceof HeaderViewRecyclerAdapter)) {
mAdapter = new HeaderViewRecyclerAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
}
}
}

@Override
public void setAdapter(Adapter adapter) {
if (mHeaderViewInfos.size() > 0 || mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewRecyclerAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
}
super.setAdapter(mAdapter);
}
}

主布局中引用我们改造后的RecyclerView,如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.materialdesignstudy.activity.WrapRecycleViewActivity">

<com.materialdesignstudy.weight.MyRecyclerView
android:id="@+id/id_my_recycleview"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</LinearLayout>

编写对应的Adapter使其填充数据。

package com.materialdesignstudy.adapter;

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

import com.materialdesignstudy.R;

import java.util.List;

/**
* Created by HLQ on 2017/10/17
*/

public class MyWrapRecycleVlewAdapter extends RecyclerView.Adapter<MyWrapRecycleVlewAdapter.ViewHolder> {

private List<String> mStrList;

public MyWrapRecycleVlewAdapter(List<String> strList) {
this.mStrList=strList;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater=LayoutInflater.from(parent.getContext());
View view=layoutInflater.inflate(R.layout.item_recy,parent,false);
ViewHolder holder=new ViewHolder(view);
return holder;
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.tvText.setText(mStrList.get(position));
}

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

public class ViewHolder extends RecyclerView.ViewHolder{

public TextView tvText;

public ViewHolder(View itemView) {
super(itemView);
tvText= (TextView) itemView.findViewById(R.id.item_tv);
}
}

}

主Activity中实例化模拟数据,如下:

package com.materialdesignstudy.activity;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;

import com.materialdesignstudy.R;
import com.materialdesignstudy.adapter.MyWrapRecycleVlewAdapter;
import com.materialdesignstudy.weight.MyRecyclerView;

import java.util.ArrayList;
import java.util.List;

/**
*
*/
public class WrapRecycleViewActivity extends AppCompatActivity {

private MyRecyclerView recyclerView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wrap_recycle);
recyclerView = (MyRecyclerView) findViewById(R.id.id_my_recycleview);

TextView tvHeader = new TextView(this);
LinearLayout.LayoutParams headerParam = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 120);
tvHeader.setLayoutParams(headerParam);
tvHeader.setText("我是HeaderView");
tvHeader.setBackgroundColor(Color.YELLOW);
recyclerView.addHeaderView(tvHeader);

TextView tvFooter = new TextView(this);
LinearLayout.LayoutParams footerParam = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 80);
tvFooter.setLayoutParams(footerParam);
tvFooter.setText("我是FooterView");
tvFooter.setBackgroundColor(Color.BLUE);
recyclerView.addFooterView(tvFooter);

List<String> strList=new ArrayList<>();
for (int i = 0; i <10 ; i++) {
strList.add("item:"+i);
}
MyWrapRecycleVlewAdapter myAdapter=new MyWrapRecycleVlewAdapter(strList);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(myAdapter);
}
}

到现在为止,我们已经完成了RecyclerView添加头部以及尾部,大家可能有点蒙,不过别着急,回过头好好看下我们刚刚分析过的ListView源码,虽然简单,但是毕竟主要的都在里面,而在实际的代码中,大家能看出高仿的痕迹~

三、为RecyclerView添加交互动画

如文章开头说所示,本小节目标就是给RecyclerView添加交互动画,那么什么是交互动画呢?所谓的交互都有什么呢?

  • 拖拽 根据方向可分为上下移动item并实时更新item位置,左右侧滑删除;
  • 动画 以及显示效果 总不能光秃秃的傻不啦叽的吧

这里不得不介绍官方为我们提供的一个Helper帮助类: ItemTouchHelper 。而我们实现也主要依旧此类。

它的使用也是很nice,只需要进行俩步,1:实例化帮助类; 2:绑定RecyclerView。 就好像下面示例一样nice。

// RecycleView item触摸帮助类 例如 滑动 拖动
helper =new ItemTouchHelper(new BaseItemTouchHelperCallback(mAdapter));
// 绑定RecycleView
helper.attachToRecyclerView(mRecycleView);

而针对ItemTouchHelper,我们在实际使用中应该手动创建HelperCallback并继承ItemTouchHelper.Callback,以便于我们在回调中进行具体细节操作,这里简单列举下关键要重写的方法及其作用:

ItemTouchHelper:官方提供item touch helper

  • getMovementFlags(): 判断用户当前操作,例如:上下拖动 左右侧滑;
  • onMove(): 用户移动时回调;
  • onSwiped(): 侧滑时回调;
  • isLongPressDragEnabled(): 是否允许长按拖拽效果;
  • isItemViewSwipeEnabled(): 是否允许开启侧滑效果;
  • onSelectedChanged(): 选择状态改变;
  • clearView(): 重置View,也代表可以在这里恢复View状态;
  • onChildDraw(): 重绘item.

定义Item Touch Move Listener,用于回调监听拖动以及侧滑事件,如下:

package com.materialdesignstudy.itemtouch;

/**
* Created by HLQ on 2017/10/18
*/
public interface ItemTouchMoveListener {

/**
* 拖拽时调用并进行相应操作 例如刷新界面UI
*
* @param fromPosition 从某个位置开始拖动
* @param toPosition 到某个位置结束拖动
* @return
*/
boolean onItemMove(int fromPosition, int toPosition);

/**
* 移动Item
*
* @param position 移除位置
* @return
*/
boolean onItemRemove(int position);

}

Activity中模拟测试数据,并绑定helper帮助类:

...

private RecyclerView mRecycleView;
private ItemAdapter mAdapter;
private ItemTouchHelper helper;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_item_touch);

mRecycleView = (RecyclerView) findViewById(R.id.id_item_touch);

mRecycleView.setLayoutManager(new LinearLayoutManager(this));
mAdapter = new ItemAdapter(getData(),this);
mRecycleView.setAdapter(mAdapter);

// RecycleView item触摸帮助类 例如 滑动 拖动
helper =new ItemTouchHelper(new BaseItemTouchHelperCallback(mAdapter));
// 绑定RecycleView
helper.attachToRecyclerView(mRecycleView);

}

private List<String> getData() {
List<String> strList = new ArrayList<>();
for (int i = 0; i < 30; i++) {
strList.add("今天天气好晴朗,处处好风光,报数:" + i);
}
return strList;
}

...

Adapter中实现接口,并对接口回调进行处理,另外,还需要对数据进行绑定,但是需要注明的有俩点:

  • 拖动时,需要将item替换移动过的每个item,刷新界面UI的同时,也需要更新每个item的position;
  • 侧滑时,只需要将List中item项remove并更新当前remove掉的UI

package com.materialdesignstudy.itemtouch;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.materialdesignstudy.R;

import java.util.Collections;
import java.util.List;

/**
* Created by HLQ on 2017/10/17
*/
public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ViewHolder> implements ItemTouchMoveListener {

private StartDragListener mStartDragListener;
private List<String> mStrList;

public ItemAdapter(List<String> strList, StartDragListener dragListener) {
this.mStrList = strList;
mStartDragListener = dragListener;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.item_touch, parent, false);
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}

@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
holder.tvTitle.setText(mStrList.get(position));
}

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

@Override
public boolean onItemMove(int fromPosition, int toPosition) {
Collections.swap(mStrList, fromPosition, toPosition); // 数据交换
notifyItemMoved(fromPosition, toPosition); // 刷新界面UI
return true;
}

@Override
public boolean onItemRemove(int position) {
// 移除item
mStrList.remove(position);
// 更新移除掉item位置
notifyItemRemoved(position);
return true;
}

class ViewHolder extends RecyclerView.ViewHolder {

private ImageView ivIcon;
private TextView tvTitle;

public ViewHolder(View itemView) {
super(itemView);
tvTitle = (TextView) itemView.findViewById(R.id.id_app_item_touch_title);
ivIcon = (ImageView) itemView.findViewById(R.id.id_app_item_touch_icon);
}
}

}

现在,我们来处理下关于我们定义的Callback业务流程:

package com.materialdesignstudy.itemtouch;

import android.graphics.Canvas;
import android.graphics.Color;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;

/**
* Created by HLQ on 2017/10/17
*/
public class BaseItemTouchHelperCallback extends ItemTouchHelper.Callback {

private ItemTouchMoveListener mMoveListener;

public BaseItemTouchHelperCallback(ItemTouchMoveListener moveListener) {
this.mMoveListener = moveListener;
}

// 判断当前操作
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
// 判断用户当前的操作:向上、向下拖动 左右侧滑
// 而所对应的状态标识分别为:ItemTouchHelper.UP ItemTouchHelper.DOWN ItemTouchHelper.LEFT ItemTouchHelper.RIGHT
// int upFlag=ItemTouchHelper.UP; // 1 0*0001
// int downFlag=ItemTouchHelper.DOWN; // 2 0*0010
// 监听拖动方向为 上下
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
// 监听侧滑方向
// int swipeFlags = 0; // 不监听
int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; // 监听左右侧滑动
return makeMovementFlags(dragFlags, swipeFlags);
}

// 移动时回调
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
if (viewHolder.getItemViewType() != target.getItemViewType()) {
return false;
}
boolean result = mMoveListener.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return result;
}

// 侧滑时回调
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
mMoveListener.onItemRemove(viewHolder.getAdapterPosition());
}

// 是否允许长按拖拽效果
@Override
public boolean isLongPressDragEnabled() {
return true;
}

@Override
public boolean isItemViewSwipeEnabled() {
return true;
}

@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
// 判断选中状态
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { // 不是闲置状态
viewHolder.itemView.setBackgroundColor(Color.BLUE);
}
super.onSelectedChanged(viewHolder, actionState);
}

@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
viewHolder.itemView.setBackgroundColor(Color.WHITE);
// 关于复用导致空白 解决方案一
// viewHolder.itemView.setAlpha(1);
// viewHolder.itemView.setScaleX(1);
// viewHolder.itemView.setScaleY(1);
super.clearView(recyclerView, viewHolder);
}

// 重绘item
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
float alpha = 1 - Math.abs(dX) / viewHolder.itemView.getWidth();
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { // 侧滑时增加特效
// dx:水平方向移动的偏移量(负:往左 正:往右) 范围:0~View.getWidth() 0~1
// 透明度
viewHolder.itemView.setAlpha(alpha);
// 缩放
viewHolder.itemView.setScaleX(alpha);
viewHolder.itemView.setScaleY(alpha);
}
if (alpha == 0) { // item划出时 恢复之前状态 解决由于复用导致划出空白bug 方案二
viewHolder.itemView.setAlpha(1);
viewHolder.itemView.setScaleX(1);
viewHolder.itemView.setScaleY(1);
}
// 判断滑动偏移量是否超出屏幕一半时 就设置其当前停留在一般的位置
// if(Math.abs(dX)<=viewHolder.itemView.getWidth()/2){
// viewHolder.itemView.setTranslationX(-0.5f*viewHolder.itemView.getWidth());
// }else{
// viewHolder.itemView.setTranslationX(dX);
// }

super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}

}

那么假设现在有个需要,当你点击item中的头像,也能进行上下拖动item呢?那我们又该如何处理呢?

同样很easy,定义拖拽接口,回调继续将事件传递下去即可,如下:

package com.materialdesignstudy.itemtouch;

/**
* Created by HLQ on 2017/10/18
*/
public interface StartDragListener {

/**
* 该接口用于需要主动回调拖拽效果
* @param viewHolder
*/
void onStartDrag(ItemAdapter.ViewHolder viewHolder);

}

adapter绑定数据时,绑定touch事件(这里需要解释Activity传递的监听):

holder.ivIcon.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mStartDragListener.onStartDrag(holder);
}
return false;
}
});

最后,实现StartDragListener接口,重写拖拽接口,将ViewHolder传递进去即可。

@Override
public void onStartDrag(ItemAdapter.ViewHolder viewHolder) {
helper.startDrag(viewHolder);
}

OK啦~

GitHub查看地址

​​https://github.com/HLQ-Struggle/MaterialDesignStudy​​

结束

坚持到现在,有点心累了,各种问题接踵而来,解决完之后,自己只剩下疲倦了。

真想好好休息下哈~

想想各位小伙伴依然在坚挺,在努力,甩甩头,继续~


举报

相关推荐

0 条评论