RecyclerView嵌套聯(lián)動(dòng)

/**

* RecyclerView -ViewPage-RecyclerView? 內(nèi)層嵌套的RV,? 解決上下滑動(dòng)沖突

*/

public class ParentRecyclerViewextends RecyclerView {

int totalDy =0;

/**

* 用于判斷RecyclerView是否在fling

*/

? ? boolean isStartFling =false;

private int mMaxDistance;

private FlingHelper mFlingHelper;

/**

* 記錄上次Event事件的y坐標(biāo)

*/

? ? private float lastY;

public int getVelocityY() {

return velocityY;

}

/**

* 記錄當(dāng)前滑動(dòng)的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過程中已經(jīng)到底部,需要讓子RecyclerView滑動(dòng)神域的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情況下险胰,記錄當(dāng)前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的時(shí)候重置加速度

? ? ? ? ? ? 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);

}

/****

* 滑動(dòng)距離及坐標(biāo) 歸還父控件焦點(diǎn)

****/

? ? 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軸移動(dòng)

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

? ? ? ? }else {

//Y軸移動(dòng)

? ? ? ? ? ? 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)的值表示是否能向上滾動(dòng)遇西,false表示已經(jīng)滾動(dòng)到底部

? ? ? ? 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:當(dāng)ChildRecyclerView下滑時(shí)(手指未放開),ChildRecyclerView滑動(dòng)到頂部(非fling)般甲,此時(shí)ParentRecyclerView不會(huì)繼續(xù)下滑。

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

? ? @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.當(dāng)前Parent RecyclerView沒有滑動(dòng)底剩盒,且dy> 0 是下滑

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

//2.當(dāng)前Child RecyclerView滑到頂部了呆瞻,且dy < 0,即上滑

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

//以上兩種情況都需要讓Parent RecyclerView去scroll滞造,和下面onNestedPreFling機(jī)制類似

? ? ? ? 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:當(dāng)ChildRecyclerView下滑時(shí)(手指未放開),ChildRecyclerView滑動(dòng)到頂部(非fling)栋烤,此時(shí)ParentRecyclerView不會(huì)繼續(xù)下滑。

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

}



/**

* RecyclerView -ViewPage-RecyclerView? 內(nèi)層嵌套的RV,? 解決上下滑動(dòng)沖突

*/

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) {

//當(dāng)前ChildRecyclerView已經(jīng)滑動(dòng)到頂部挺狰,且豎直方向加速度不為0,如果有多余的需要交由父RecyclerView繼續(xù)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;//按下時(shí) 的X坐標(biāo)

? ? private float downY;//按下時(shí) 的Y坐標(biāo)

? ? @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);

//將按下時(shí)的坐標(biāo)存儲(chǔ)

? ? ? ? ? ? ? ? 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) {

//左右滑動(dòng)交給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軸移動(dòng)

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

? ? ? ? }else {

//Y軸移動(dòng)

? ? ? ? ? ? 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表示加速度達(dá)不到fling的要求丰泊,將mVelocityY重置

? ? ? ? ? ? mVelocityY =0;

}else {

//正在進(jìn)行fling

? ? ? ? ? ? isStartFling =true;

mVelocityY = velocityY;

}

return fling;

}

public boolean isScrollTop() {

//RecyclerView.canScrollVertically(-1)的值表示是否能向下滾動(dòng)薯定,false表示已經(jīng)滾動(dòng)到頂部

? ? ? ? return !canScrollVertically(-1);

}

public boolean isScrollEnd() {

//RecyclerView.canScrollVertically(1)的值表示是否能向上滾動(dòng),false表示已經(jīng)滾動(dòng)到底部

? ? ? ? return !canScrollVertically(1);

}

private ParentRecyclerView findParentRecyclerView() {

ViewParent parentView = getParent();

while (!(parentViewinstanceof ParentRecyclerView)) {

parentView = parentView.getParent();

}

return (ParentRecyclerView) parentView;

}

}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瞳购,一起剝皮案震驚了整個(gè)濱河市话侄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌学赛,老刑警劉巖年堆,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異盏浇,居然都是意外死亡变丧,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門绢掰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痒蓬,“玉大人,你說我怎么就攤上這事滴劲」ド梗” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵班挖,是天一觀的道長鲁捏。 經(jīng)常有香客問我,道長聪姿,這世上最難降的妖魔是什么碴萧? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮末购,結(jié)果婚禮上破喻,老公的妹妹穿的比我還像新娘。我一直安慰自己盟榴,他們只是感情好曹质,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般羽德。 火紅的嫁衣襯著肌膚如雪几莽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天宅静,我揣著相機(jī)與錄音章蚣,去河邊找鬼。 笑死姨夹,一個(gè)胖子當(dāng)著我的面吹牛纤垂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播磷账,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼峭沦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了逃糟?” 一聲冷哼從身側(cè)響起吼鱼,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绰咽,沒想到半個(gè)月后菇肃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡取募,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年巷送,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矛辕。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡笑跛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出聊品,到底是詐尸還是另有隱情飞蹂,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布翻屈,位于F島的核電站陈哑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏伸眶。R本人自食惡果不足惜惊窖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望厘贼。 院中可真熱鬧界酒,春花似錦、人聲如沸嘴秸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至凭疮,卻和暖如春饭耳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背执解。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工寞肖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衰腌。 一個(gè)月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓逝淹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親桶唐。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353