RecyclerView嵌套联动

/**

* RecyclerView -ViewPage-RecyclerView  内层嵌套的RV,  解决上下滑动冲突

*/

public class ParentRecyclerViewextends RecyclerView {

int totalDy =0;

/**

* 用于判断RecyclerView是否在fling

*/

    boolean isStartFling =false;

private int mMaxDistance;

private FlingHelper mFlingHelper;

/**

* 记录上次Event事件的y坐标

*/

    private float lastY;

public int getVelocityY() {

return velocityY;

}

/**

* 记录当前滑动的y轴加速度

*/

    private int velocityY;

private AtomicBoolean canScrollVertically;

private ChildRecyclerView mChildRecyclerView;

private int mMeasuredHeight;

private ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener;

private VirtualLayoutManager.LayoutParams mLayoutParams;

public ParentRecyclerView(Context context) {

super(context);

init();

}

public ParentRecyclerView(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

init();

}

public ParentRecyclerView(Context context, @Nullable AttributeSet attrs,int defStyle) {

super(context, attrs, defStyle);

init();

}

private void init() {

mFlingHelper =new FlingHelper(getContext());

mMaxDistance = mFlingHelper.getVelocityByDistance(UIUtils.getScreenHeight() *4.0);

canScrollVertically =new AtomicBoolean(true);

addOnScrollListener(new OnScrollListener() {

@Override

public void onScrollStateChanged(RecyclerView recyclerView,int newState) {

super.onScrollStateChanged(recyclerView, newState);

//如果父RecyclerView fling过程中已经到底部,需要让子RecyclerView滑动神域的fling

                if (parentRecyclerOnScrollListener !=null){

parentRecyclerOnScrollListener.onScrollStateChanged(recyclerView,newState);

}

if (newState == RecyclerView.SCROLL_STATE_IDLE) {

dispatchChildFling();

}

}

@Override

public void onScrolled(RecyclerView recyclerView,int dx,int dy) {

super.onScrolled(recyclerView, dx, dy);

if (isStartFling) {

totalDy =0;

isStartFling =false;

}

//在RecyclerView fling情况下,记录当前RecyclerView在y轴的偏移

                totalDy += dy;

if (parentRecyclerOnScrollListener !=null){

parentRecyclerOnScrollListener.onScrolled(recyclerView,dx,dy);

}

}

});

onGlobalLayoutListener =new ViewTreeObserver.OnGlobalLayoutListener() {

@Override

public void onGlobalLayout() {

int measuredHeight = getMeasuredHeight();

if (measuredHeight != mMeasuredHeight) {

mMeasuredHeight = measuredHeight;

if (mLayoutParams !=null) {

mLayoutParams.height = (int) (mMeasuredHeight - getResources().getDimension(R.dimen.x40));

}

}

}

};

getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);

}

public interface ParentRecyclerOnScrollListener{

void onScrollStateChanged(RecyclerView recyclerView,int newState);

void onScrolled(RecyclerView recyclerView,int dx,int dy);

}

private ParentRecyclerOnScrollListener parentRecyclerOnScrollListener;

public void setParentRecyclerOnScrollListener(ParentRecyclerOnScrollListener parentRecyclerOnScrollListener) {

this.parentRecyclerOnScrollListener = parentRecyclerOnScrollListener;

}

@Override

protected void onDetachedFromWindow() {

super.onDetachedFromWindow();

getViewTreeObserver().removeOnGlobalLayoutListener(onGlobalLayoutListener);

}

private void dispatchChildFling() {

if (isScrollEnd() && velocityY !=0) {

double splineFlingDistance = mFlingHelper.getSplineFlingDistance(velocityY);

if (splineFlingDistance > totalDy) {

childFling(mFlingHelper.getVelocityByDistance(splineFlingDistance - totalDy));

}

}

totalDy =0;

velocityY =0;

}

private void childFling(int velY) {

if (mChildRecyclerView !=null) {

mChildRecyclerView.fling(0,velY);

}

}

public void setChildRecyclerView(ChildRecyclerView childRecyclerView) {

this.mChildRecyclerView = childRecyclerView;

}

public VirtualLayoutManager initLayoutManager() {

return new VirtualLayoutManager(getContext()) {

@Override

public int scrollVerticallyBy(int dy, Recycler recycler, State state) {

try {

return super.scrollVerticallyBy(dy, recycler, state);

}catch (Exception e) {

e.printStackTrace();

}

return 0;

}

@Override

public void onLayoutChildren(Recycler recycler, State state) {

try {

super.onLayoutChildren(recycler, state);

}catch (Exception e) {

e.printStackTrace();

}

}

@Override

public boolean canScrollVertically() {

return canScrollVertically.get() || mChildRecyclerView ==null || mChildRecyclerView.isScrollTop();

}

@Override

public void addDisappearingView(View child) {

try {

super.addDisappearingView(child);}catch ( Exception e) {

e.printStackTrace();

}

}

@Override

public RecyclerView.LayoutParams generateDefaultLayoutParams() {

mLayoutParams =new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,

(int) (mMeasuredHeight - getResources().getDimension(R.dimen.x40)));

return mLayoutParams;

}

@Override

public boolean supportsPredictiveItemAnimations() {

return false;

}

};

}

@Override

public boolean dispatchTouchEvent(MotionEvent ev) {

if(ev !=null && ev.getAction() == MotionEvent.ACTION_DOWN) {

//ACTION_DOWN的时候重置加速度

            velocityY =0;

stopScroll();

LogUtil.i("ParentRecyclerView","isScrollTop:"+isScrollTop());

if (!isScrollTop()) {

getParent().requestDisallowInterceptTouchEvent(true);

}

}

if(!(ev ==null || ev.getAction() == MotionEvent.ACTION_MOVE)) {

//在ACTION_MOVE的情况下,将lastY置为0

            lastY =0f;

canScrollVertically.set(!isScrollEnd());

//LogUtil.i("ParentRecyclerView","isScrollEnd:"+isScrollEnd());

        }

return super.dispatchTouchEvent(ev);

}

/****

* 滑动距离及坐标 归还父控件焦点

****/

    private float xLast, yLast;

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

switch (ev.getAction()) {

case MotionEvent.ACTION_DOWN:

xLast = ev.getX();

yLast = ev.getY();

break;

case MotionEvent.ACTION_MOVE:

final float curX = ev.getX();

final float curY = ev.getY();

final int xDistance = (int) (curX - xLast);

final int yDistance = (int) (curY - yLast);

int orientation = getOrientation(xDistance, yDistance);

if (orientation=='r'||orientation =='l'){

return false;

}

if (orientation =='u' && isScrollEnd()) {

return false;

}

break;

}

return super.onInterceptTouchEvent(ev);

}

private int getOrientation(float dx,float dy) {

if (Math.abs(dx) > Math.abs(dy)) {

//X轴移动

            return dx >0 ?'r' :'l';//右,左

        }else {

//Y轴移动

            return dy >0 ?'d' :'u';//下//上

        }

}

@Override

public boolean fling(int velocityX,int velocityY) {

boolean fling =super.fling(velocityX, velocityY);

if (!fling || velocityY <=0) {

this.velocityY =0;

}else {

isStartFling =true;

this.velocityY = velocityY;

}

return fling;

}

private boolean isScrollEnd() {

//RecyclerView.canScrollVertically(1)的值表示是否能向上滚动,false表示已经滚动到底部

        return !canScrollVertically(1);

}

public void scrollToTop() {

if (!canScrollVertically.get()) {

canScrollVertically.set(true);

}

if (mChildRecyclerView !=null && !mChildRecyclerView.isScrollTop()) {

mChildRecyclerView.scrollToPosition(0);

postDelayed(() -> {

super.scrollToPosition(0);

},20);

}else {

super.scrollToPosition(0);

}

}

public boolean isScrollTop() {

if (mChildRecyclerView !=null && mChildRecyclerView.isScrollTop() && canScrollVertically.get() && !canScrollVertically(-1)) {

return true;

}

return canScrollVertically.get() &&!canScrollVertically(-1);

}

//----------------------------------------------------------------------------------------------

// NestedScroll. fix:当ChildRecyclerView下滑时(手指未放开),ChildRecyclerView滑动到顶部(非fling),此时ParentRecyclerView不会继续下滑。

//----------------------------------------------------------------------------------------------

    @Override

public boolean onStartNestedScroll(View child, View target,int nestedScrollAxes) {

return targetinstanceof ChildRecyclerView;

}

@Override

public void onNestedPreScroll(View target,int dx,int dy,int[] consumed) {

//1.当前Parent RecyclerView没有滑动底,且dy> 0 是下滑

        boolean isParentCanScroll = dy >0 && !isScrollEnd();

//2.当前Child RecyclerView滑到顶部了,且dy < 0,即上滑

        boolean isChildCanNotScroll = !(dy >=0 || mChildRecyclerView ==null || !mChildRecyclerView.isScrollTop());

//以上两种情况都需要让Parent RecyclerView去scroll,和下面onNestedPreFling机制类似

        if(isParentCanScroll || isChildCanNotScroll) {

scrollBy(0,dy);

consumed[1] = dy;

}

}

@Override

public boolean onNestedFling(View target,float velocityX,float velocityY,boolean consumed) {

return true;

}

@Override

public boolean onNestedPreFling(View target,float velocityX,float velocityY) {

boolean isParentCanFling = velocityY >0f && !isScrollEnd();

boolean isChildCanNotFling = !(velocityY >=0 || mChildRecyclerView ==null || !mChildRecyclerView.isScrollTop());

if(!isParentCanFling && !isChildCanNotFling) {

return false;

}

fling(0, (int) velocityY);

return true;

}

//    override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float): Boolean {

//        val childRecyclerView = findNestedScrollingChildRecyclerView()

//        if(isScrollEnd().not() || childRecyclerView == null || childRecyclerView.isScrollTop()) {

//            return super.dispatchNestedPreFling(velocityX, velocityY)

//        }

//        childFling(velocityY.toInt())

//        return true

//    }

//----------------------------------------------------------------------------------------------

// NestedScroll. fix:当ChildRecyclerView下滑时(手指未放开),ChildRecyclerView滑动到顶部(非fling),此时ParentRecyclerView不会继续下滑。

//----------------------------------------------------------------------------------------------

}



/**

* RecyclerView -ViewPage-RecyclerView  内层嵌套的RV,  解决上下滑动冲突

*/

public class ChildRecyclerViewextends RecyclerView {

private FlingHelper mFlingHelper;

private int mVelocityY =0;

private boolean isStartFling =false;

private int  totalDy;

private ParentRecyclerView mParentRecyclerView;

public ChildRecyclerView(Context context) {

super(context);

init();

}

public ChildRecyclerView(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

init();

}

public ChildRecyclerView(Context context, @Nullable AttributeSet attrs,int defStyle) {

super(context, attrs, defStyle);

init();

}

private void init() {

mFlingHelper =new FlingHelper(getContext());

setOverScrollMode(RecyclerView.OVER_SCROLL_NEVER);

initScrollListener();

}

private void initScrollListener() {

addOnScrollListener(new OnScrollListener() {

@Override

public void onScrolled(RecyclerView recyclerView,int dx,int dy) {

super.onScrolled(recyclerView, dx, dy);

if(isStartFling) {

totalDy =0;

isStartFling =false;

}

totalDy += dy;

}

@Override

public void onScrollStateChanged(RecyclerView recyclerView,int newState) {

if(newState == RecyclerView.SCROLL_STATE_IDLE) {

dispatchParentFling();

}

super.onScrollStateChanged(recyclerView, newState);

}

});

}

private void dispatchParentFling() {

if (mParentRecyclerView ==null) {

mParentRecyclerView =  findParentRecyclerView();

}

if (mParentRecyclerView !=null) {

LogUtil.i("childRecyclerView","isScrollTop:"+isScrollTop()+",mVelocityY:"+mVelocityY);

if(isScrollTop() && mVelocityY !=0) {

//当前ChildRecyclerView已经滑动到顶部,且竖直方向加速度不为0,如果有多余的需要交由父RecyclerView继续fling

                double flingDistance = mFlingHelper.getSplineFlingDistance(mVelocityY);

if(flingDistance > (Math.abs(totalDy))) {

fling(0,-mFlingHelper.getVelocityByDistance(flingDistance + totalDy));

}

//fix 在run方法里面,注意 this@ChildRecyclerView的使用,否则使用的是ParentRecyclerView的变量

                totalDy =0;

mVelocityY =0;

}

}

}

private float downX;//按下时 的X坐标

    private float downY;//按下时 的Y坐标

    @Override

public boolean dispatchTouchEvent(MotionEvent ev) {

float x = ev.getX();

float y = ev.getY();

switch (ev.getAction()) {

case MotionEvent.ACTION_DOWN:

mVelocityY =0;

findParentRecyclerView().setChildRecyclerView(this);

//将按下时的坐标存储

                downX = x;

downY = y;

//                True if the child does not want the parent to

//                intercept touch events.

                getParent().requestDisallowInterceptTouchEvent(true);

break;

case MotionEvent.ACTION_MOVE:

//获取到距离差

                float dx = x - downX;

float dy = y - downY;

//通过距离差判断方向

                int orientation = getOrientation(dx, dy);

switch (orientation) {

//左右滑动交给ViewPager处理

                    case 'r':

case 'l':

getParent().requestDisallowInterceptTouchEvent(false);

break;

case 'u':

if (isScrollEnd())

getParent().requestDisallowInterceptTouchEvent(false);

break;

}

break;

}

return super.dispatchTouchEvent(ev);

}

private int getOrientation(float dx,float dy) {

if (Math.abs(dx) > Math.abs(dy)) {

//X轴移动

            return dx >0 ?'r' :'l';//右,左

        }else {

//Y轴移动

            return dy >0 ?'d' :'u';//下//上

        }

}

@Override

public boolean fling(int velocityX,int velocityY) {

if(!isAttachedToWindow())return false;

boolean fling =super.fling(velocityX, velocityY);

if(!fling || velocityY >=0) {

//fling为false表示加速度达不到fling的要求,将mVelocityY重置

            mVelocityY =0;

}else {

//正在进行fling

            isStartFling =true;

mVelocityY = velocityY;

}

return fling;

}

public boolean isScrollTop() {

//RecyclerView.canScrollVertically(-1)的值表示是否能向下滚动,false表示已经滚动到顶部

        return !canScrollVertically(-1);

}

public boolean isScrollEnd() {

//RecyclerView.canScrollVertically(1)的值表示是否能向上滚动,false表示已经滚动到底部

        return !canScrollVertically(1);

}

private ParentRecyclerView findParentRecyclerView() {

ViewParent parentView = getParent();

while (!(parentViewinstanceof ParentRecyclerView)) {

parentView = parentView.getParent();

}

return (ParentRecyclerView) parentView;

}

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352