adapter) {
return new FlowLayoutManager();
}
};
}
```
**R.layout.scroll_item_layout代码:**
```
```
使用方面就注意这些了,下面就讲讲是怎么实现该效果的。。。
###讲解:
下面就来讲讲如何用`RecyclerView`如何实现上面的效果了:
先来张自己画的思路草图吧:
![草图.png](/contentImages/image/jianshu/2528336-6854ebc0a8d0b774.png)
**这里整体就是一个`RecyclerView`了,而且在初始的时候,需要定义我们自己的`Layoutmanager`,代码里面可见(`ChuShouManager`)该类,该`Layoutmanager`的功能就是让最后一个item在屏幕的上面显示,第一个item在屏幕中显示,第二个item到倒数第二个item在屏幕的下面显示。所以手机上面显示的永远是`RecyclerView`中第一个item了,只不过在手指滑动的时候,去改变数据源。**
```
/**
* Created by xiangcheng on 17/4/11.
* 初始化RecyclerView中所有item的位置
*/
public class ChuShouManager extends RecyclerView.LayoutManager {
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
*******省略代码******
//防止数量没达到1个以上的要求
if (getChildCount() >= 1) {
//第一个item放在屏幕中
if (i == 0) {
layoutDecoratedWithMargins(childAt, 0, 0,
getDecoratedMeasuredWidth(childAt),
getDecoratedMeasuredHeight(childAt));
}
}
//需要判断数量
if (getChildCount() >= 2) {
//从第二个item到倒数第二个放在屏幕下面
if (i >= 1 && i < getItemCount() - 1) {
layoutDecoratedWithMargins(childAt,
0, getHeight(),
getDecoratedMeasuredWidth(childAt),
getHeight() +getDecoratedMeasuredHeight(childAt));
}
}
//数量要求
if (getChildCount() >= 3) {
//最后一个item放在屏幕上面
if (i == getItemCount() - 1) {
layoutDecoratedWithMargins(childAt,
0, -getDecoratedMeasuredHeight(childAt),
getDecoratedMeasuredWidth(childAt),0);
}
}
}
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
//这里就直接定义recyclerView里面item直接占满整个屏幕了
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
}
}
```
好了,第一步终于完成啦,下面就是`RecyclerView`的`touch`相关代码控制了,关于`touch`的控制,我们需要接触到`android.support.v7.widget.helper.ItemTouchHelper.Callback`该类了:
![屏幕快照 2017-04-17 14.11.28.png](/contentImages/image/jianshu/2528336-4cd6787a1088c9c8.png)
从源码截图中看到该类是一个**静态的抽象类**,说明我们要使用的时候,需要去实现该类了。这里定义了一个实现类**ChuShouCallBack**,**CallBack**抽象类定义了只是定义了我们的**Drag**(拖拽)动作,实际上我们要用的是**SimpleCallback**子类,该类实现了我们的**Swipe**(滑动)动作。因此这里需要屏蔽Drag动作,实现Swipe动作。
**屏蔽Drag动作,实现Swipe动作:**
```
public class ChuShouCallBack extends ItemTouchHelper.SimpleCallback {
*****省略代码*****
//该构造器屏蔽了swipDirection
public ChuShouCallBack(RecyclerView.Adapter adapter, List mDatas) {
this(adapter, mDatas, 0);
}
//该构造器接收传进来的swipDirection
public ChuShouCallBack(RecyclerView.Adapter adapter, List mDatas, int swipDirection) {
this(0, swipDirection);
this.mAdapter = adapter;
this.mDatas = mDatas;
}
public ChuShouCallBack(int dragDirs, int swipeDirs) {
super(dragDirs, swipeDirs);
}
*****省略代码*****
}
```
**下面看看RecyclerView滑动Item的监听**
```
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, final RecyclerView.ViewHolder viewHolder, float dX, float dY, int act
// super.onChildDraw(c,recyclerView,viewHolder,dX,dY,actionState,isCurrentlyActive);这里就不要执行父类的该方法了,执行后就会让当前item随手势移动了,这样就不是我们想
Log.d(TAG,"dy")
if (height == 0) {
height = recyclerView.getHeight();
}
Log.d(TAG, "dy:" + dY + ",actionState:" + actionState + ",isCurrentlyActive:" + isCurrentlyActive);
Log.d(TAG, "lastDy:" + lastDy);
//add 2017/4/17,此时换了手指再去按住nextView,如果是下拉时:lastDy > 0 && dY <= 0,如果是上拉时:lastDy < 0 && dY >= 0
Log.d(TAG, "height * getSwipeThreshold(viewHolder):" + height * getSwipeThreshold(viewHolder));
if (lastDy > 0 && dY <= 0 || lastDy < 0 && dY >= 0) {
if (lastDy > 0 && dY <= height * getSwipeThreshold(viewHolder) || lastDy < 0 && dY >= 0) {
//这个是当松手时isCurrentlyActive=false
if (!isCurrentlyActive) {
if (valueAnimator == null) {
//从松手一瞬间,从lastDy的位置到0
valueAnimator = ValueAnimator.ofFloat(lastDy, 0);
//这里的下拉或上拉的最大距离是按照swipe的临界值来算的
float maxPullHeight = height * getSwipeThreshold(viewHolder);
//最长的时间是200毫秒
float duration = 200 * (Math.abs(lastDy) / maxPullHeight);
valueAnimator.setDuration((long) duration);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float animatedValue = (float) animation.getAnimatedValue();
float percent = Math.abs(animatedValue / height);
float scaleAlpha = (float) (1.0 - percent * 1.0);
viewHolder.itemView.setAlpha(scaleAlpha);
((ViewGroup) viewHolder.itemView).getChildAt(0).setScaleX(scaleAlpha);
((ViewGroup) viewHolder.itemView).getChildAt(0).setScaleY(scaleAlpha);
nextView.setTranslationY(animatedValue);
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
lastDy = 0;
valueAnimator = null;
}
});
valueAnimator.start();
}
}
} else {
//该种情况就是没有换手指的情况
float percent = Math.abs(dY / height);
float scaleAlpha = (float) (1.0 - percent * 1.0);
viewHolder.itemView.setAlpha(scaleAlpha);
((ViewGroup) viewHolder.itemView).getChildAt(0).setScaleX(scaleAlpha);
((ViewGroup) viewHolder.itemView).getChildAt(0).setScaleY(scaleAlpha);
//往下拉
if (dY > 0) {
//获取屏幕上方的item
nextView = recyclerView.getChildAt(recyclerView.getChildCount() - 1);
View childAt = ((ViewGroup) nextView).getChildAt(0);
if (childAt instanceof SlideRecyclerView) {
SlideRecyclerView sl = (SlideRecyclerView) childAt;
if (sl.getScrollY() == 0) {
sl.pullNextScroll();
}
}
nextView.setTranslationY(dY);
pullDown = true;
lastDy = dY;
} else if (dY < 0) {
//往上拉的时候,获取屏幕下面的item
nextView = recyclerView.getChildAt(1);
pullDown = false;
nextView.setTranslationY(dY);
lastDy = dY;
}
}
}
```
这里看似代码这么长,其实是在下拉的时候,获取到的`nextView`对应的是屏幕上方的item,也就是`RecyclerView`的最后一个item,因为最后一个item是放在了屏幕的上面;在上拉的时候,获取到的`nextView`对应的是屏幕下方的item,也就是`RecyclerView`的第二个item。
上面的代码只是处理我们的滑动,至于说松手的处理还没说呢。这里也正好说下`onSwipe`什么时候触发。这里需要介绍一个方法:
```
/**
* Returns the fraction that the user should move the View to be considered as swiped.
* The fraction is calculated with respect to RecyclerView's bounds.
*
* Default value is .5f, which means, to swipe a View, user must move the View at least
* half of RecyclerView's width or height, depending on the swipe direction.
*
* @param viewHolder The ViewHolder that is being dragged.
* @return A float value that denotes the fraction of the View size. Default value
* is .5f .
*/
public float getSwipeThreshold(ViewHolder viewHolder) {
return .5f;
}
```
**源码说是只要滑动位置超过了RecyclerView的width或height时就会触发onSwiped方法,我们这里不需要去动该值就可以了,默认就可以,这里也正好是RecyclerView高度一半的距离,在松手的时候就触发onSwipe方法**
**接下来看看`onSwiped`都做了些什么:**
```
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
Log.d(TAG, "onSwiped");
lastDy = 0;
refreshData(viewHolder);
if (onSwipedListener != null) {
onSwipedListener.onSwiped(pullDown);
}
}
```
**这里处理了数据跟接口的回调的监听,接下来看看`refreshData`方法做了些什么:**
```
/**
* 处理swipe时候view的还原以及数据源的刷新
*
* @param viewHolder
*/
private void refreshData(RecyclerView.ViewHolder viewHolder) {
//将当前的item进行还原
viewHolder.itemView.setAlpha(1);
((ViewGroup) viewHolder.itemView).getChildAt(0).setScaleX(1);
((ViewGroup) viewHolder.itemView).getChildAt(0).setScaleY(1);
if (pullDown) {
//将上面移动的进行还原
nextView.setTranslationY(-height);
//往下拉的时候,将集合整体往后挪一位
Collections.rotate(mDatas, 1);
} else {
//将下面移动的进行还原
nextView.setTranslationY(height);
//往上拉的时候,将集合整体往前挪一位
Collections.rotate(mDatas, -1);
}
//刷新item
mAdapter.notifyDataSetChanged();
}
```
**关于外层的`RecyclerView`滑动处理就先说这么多,下面来介绍如何处理内层带有滑动结构的`RecyclerView`,两个都有滑动结构,何时才让内层的`RecyclerView`,何时让外层的`RecyclerView`滑动呢:**
这个时候来看下里面的`SlideRecyclerView`内部滑动的处理:
```
public SlideRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
getViewTreeObserver().removeGlobalOnLayoutListener(this);
initView();
if (getIsCurrentItem()) {
addOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
scrollY += dy;
Log.d(TAG, "scrollY:" + scrollY);
if (scrollY == 0) {
//如果父recyclerView已经在顶部并且还往上滑的时候,让chuShouCallBack没有swipe动作
if (dy < 0) {
chuShouCallBack.setDefaultSwipeDirs(0);
}
}
if (isSlideToBottom()) {
//如果父recyclerView已经在底部并且还往下拉的时候,让chuShouCallBack没有swipe动作
if (dy > 0) {
chuShouCallBack.setDefaultSwipeDirs(0);
}
}
}
});
}
}
});
}
```
监听`SlideRecyclerView`滑动位置来动态设置外层的`RecyclerView`是否有滑动处理。总结下来是两种情况:一种是`SlideRecyclerView`滑动到了顶部,此时如果往上滑的时候,需要禁掉外层的`RecyclerView`的滑动,直接调用`chuShouCallBack.setDefaultSwipeDirs(0)`方法就可禁掉外层的滑动。另一种是`SlideRecyclerView`滑动到了底部,此时如果往下滑的时候,也需要禁掉外层的`RecyclerView`滑动。
说到这的时候很多人好奇为什么监听`SlideRecyclerView`滑动处理没有打开外层的`RecyclerView`代码呢。这里打开外层的`RecyclerView`滑动需要放在ontouch里面处理。因为在`SlideRecyclerView`滑动监听里面是无法监听到如果滑动到顶部时**继续往下滑**和滑动到底部时**继续往上滑**的操作,因此这里就通过`ontouch`的坐标该变量来是否打开外层的`RecyclerView`滑动:
```
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
if (getIsCurrentItem()) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "MotionEvent.ACTION_DOWN");
startY = e.getY();
chuShouCallBack.setDefaultSwipeDirs(0);
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "MotionEvent.ACTION_MOVE");
dataY = e.getY() - startY;
//只有滑动到顶部的时候才会通过判断两点之间的距离来切换item
if (scrollY == 0) {
if (dataY > 0) {
chuShouCallBack.setDefaultSwipeDirs(ItemTouchHelper.UP | ItemTouchHelper.DOWN);
Log.d(TAG, "到了顶部往下拉的时候");
}
}
if (isSlideToBottom()) {
if (dataY < 0) {
chuShouCallBack.setDefaultSwipeDirs(ItemTouchHelper.UP | ItemTouchHelper.DOWN);
Log.d(TAG, "到了底部往上拉的时候");
}
}
if (scrollY != 0 && !isSlideToBottom()) {
chuShouCallBack.setDefaultSwipeDirs(0);
Log.d(TAG, "在中间的位置");
}
break;
case MotionEvent.ACTION_UP:
break;
}
}
return super.onInterceptTouchEvent(e);
}
```
这里涉及到了三种情况:
(1)`SlideRecyclerView`滑动到顶部的时候,继续往下滑的时候,需要打开外层的`RecyclerView`滑动
(2)`SlideRecyclerView`滑动到底部的时候,继续往上滑的时候,需要打开外层的`RecyclerView`滑动
(3)`SlideRecyclerView`滑动到中间某一个位置的时候,不管往上滑还是往下滑需要禁掉外层的`RecyclerView`滑动
核心代码就这么多了,关于使用方面如果还有什么疑问,可以直接看demo,也可以直接跟我探讨,欢迎提出issue
###总结:
- **结构分析:**
整体外层是一个大的`RecyclerView`(这里定义成`ScrollRecyclerView`,为了对外提供自己的`ChuShouCallBack`),里面的item分两种情况,一种是带有滑动结构,一种是非滑动结构。滑动结构的话又定义了一个`RecyclerView`(这里定义成`SlideRecyclerView`,处理滑动的)。
- **分析item的排列:**
这里就是`ChuShouManager`的职责了,它负责把**最后一个item**放在屏幕上方,**第一个item**放在屏幕中,**从第二个item到倒数第二个item**放在屏幕下方。
- **处理touch的动作:**
`ChuShouCallBack`就是扮演该角色了,用来处理上拉和下拉改变item的透明度和平移量。最后在`onSwipe`时恢复item状态及改变数据源
- **处理item本身带有滑动(`SlideRecyclerView `)和外层`RecyclerView`滑动冲突:**
这里就是分析何时去禁掉外层`RecyclerView`滑动,何时打开滑动。原则是当item滑动到顶部时,若再继续往上滑禁掉外层`RecyclerView`滑动,若再继续往下滑打开外层`RecyclerView`滑动;当item滑动到中间某一个位置时,此时不管再继续往上滑还是往下滑都是禁掉外层`RecyclerView`滑动;当item滑动到底部时,若再继续往上滑打开外层`RecyclerView`滑动,若再继续往下滑禁掉外层`RecyclerView`滑动。
###后续添加:
滑动控件还会有`ListView`、`ScrollView`等
**欢迎客官到本店光临:**`184793647`(qq群)
###gradle依赖:
```
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
dependencies {
...
compile 'com.github.1002326270xc:ChuShouView-master:v1.3'
...
}
```
###项目文件目录截图:
> 项目结构图
![](/contentImages/image/20180413/LXrkic2CLM2shEHvly8.jpg)