GIF.gif
技能点:
1.Android事件分发机制等
需求点:
1.列表嵌套,内层的列表可以左右切换
2.ViewPager可以点击和滑动切换
最近在淘宝京东看到类似的效果,有时间就写了一下,效果实现了,但是感觉解决问题的思路和代码有很多瑕疵,写出来抛砖引玉,希望大佬们不吝赐教,写的不好不喜勿喷!
下面进入正题,先看下布局结构:
screen.png
就是标题所说的布局结构 RecyclerView+ViewPager+RecyclerView`
很多同学看到这里肯定想到要处理滑动冲突,没错,我们简单分析一下好撸代码(虽然是撸好的代码)
大概就是这样,思路很清晰,这里先提几个接下来遇到的问题:
操作步骤:滑动到Tab吸顶->滑动内层RecyclerView至中间->切换一个Tab(内层RecyclerView的状态已经滑动到顶部,就是初始状态)->这时候将Tab滑动到非吸顶->切换到最初内层RecyClerView滑动到中间的Tab,这时候展示的就是Tab未吸顶,内层RecyclerView不在顶部的尴尬局面.说了这么多应该需要一张gif解释一下上图:
GIF1.gif
对于上图所提到的情况,这个时候用户手指纵向滑动红色区域,滑动事件交给谁都不合适
.那先说下淘宝和京东采取的方式:
个人感觉第一种处理方式比较好一点,demo的代码如下(需要请自行修改,PagerFragment.java)
if(! ((MainActivity)getActivity()).isStick){
((MainActivity)getActivity()).adjustScroll(true);
return false;
}
下面说下实现方式,以及问题的解决(布局等细节就不贴出来了,详情见demo):
private float downX ; //按下时 的X坐标
private float downY ; //按下时 的Y坐标
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
float x= e.getX();
float y = e.getY();
switch (e.getAction()){
case MotionEvent.ACTION_DOWN:
//将按下时的坐标存储
downX = x;
downY = y;
break;
case MotionEvent.ACTION_MOVE:
//获取到距离差
float dx= x-downX;
float dy = y-downY;
//通过距离差判断方向
int orientation = getOrientation(dx, dy);
switch (orientation) {
//左右滑动交给ViewPager处理
case 'r':
setNeedIntercept(false);
break;
//左右滑动交给ViewPager处理
case 'l':
setNeedIntercept(false);
break;
}
return isNeedIntercept;
}
return super.onInterceptTouchEvent(e);
}
public void setNeedIntercept(boolean needIntercept) {
isNeedIntercept = needIntercept;
}
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?'b':'t';//下//上
}
}
isNeedIntercept为是否拦截滑动事件,自己处理.并提供了一个setNeedIntercept方法供外部调用.代码可以看出,横向的滑动直接放行,让ViewPager处理,向上滑动时候如果tab吸顶了且已经滑动到底部,交给内部的RecyclerView处理,否则自己处理.
我们对内层的RecyclerView进行处理,重写其onTouchEvent方法
@Override
public boolean onTouchEvent(MotionEvent e) {
float x= e.getX();
float y = e.getY();
switch (e.getAction()){
case MotionEvent.ACTION_DOWN:
//将按下时的坐标存储
downX = x;
downY = y;
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
//获取到距离差
float dx= x-downX;
float dy = y-downY;
//通过距离差判断方向
int orientation = getOrientation(dx, dy);
int[] location={0,0};
getLocationOnScreen(location);
switch (orientation) {
case 'b':
//内层RecyclerView下拉到最顶部时候不再处理事件
if(!canScrollVertically(-1)){
getParent().requestDisallowInterceptTouchEvent(false);
if(needIntercepectListener!=null){
needIntercepectListener.needIntercepect(false);
}
}else{
getParent().requestDisallowInterceptTouchEvent(true);
if(needIntercepectListener!=null){
needIntercepectListener.needIntercepect(true);
}
}
break;
case 't':
if(location[1]<=maxY){
getParent().requestDisallowInterceptTouchEvent(true);
if(needIntercepectListener!=null){
needIntercepectListener.needIntercepect(true);
}
}else{
getParent().requestDisallowInterceptTouchEvent(false);
if(needIntercepectListener!=null){
needIntercepectListener.needIntercepect(false);
return true;
}
}
break;
case 'r':
getParent().requestDisallowInterceptTouchEvent(false);
break;
//左右滑动交给ViewPager处理
case 'l':
getParent().requestDisallowInterceptTouchEvent(false);
break;
}
break;
}
return super.onTouchEvent(e);
}
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?'b':'t';//下//上
}
}
public void setMaxY(int height) {
this.maxY=height;
}
public interface NeedIntercepectListener{
void needIntercepect(boolean needIntercepect);
}
public void setNeedIntercepectListener(NeedIntercepectListener needIntercepectListener) {
this.needIntercepectListener = needIntercepectListener;
}
其中的回调是为了告诉外层的RecyclerView需不需要拦截事件.
滑动冲突到这里基本上处理完了,下面说下吸顶的问题,其实只是思路的问题,这里采取的方式是将TabLayout和ViewPager当做一个外层RecyclerView的最后一个item,并且高度为屏幕高度-状态栏高度,这样当外层RecyclerView滑动到底部,Tab看上去是吸顶的.
简单说下:这个demo之前是按真正的吸顶做的,所以文章改动过,哪里说得不清楚的请直接看demo,主要是处理滑动事件冲突,难度不大,纯属抛砖引玉.
最后暴露一个问题,在外层RecyclerView滑动到底部时,需要将触摸事件交给内层的RecyclerView处理时,按照Demo里的处理方式,手指抬起之后重新滑动,内层RecyclerView才能拿到事件,原因是Demo判断外层RecyclerView是否滑动到底部的代码写在onInterceptTouchEvent里面,这个方法并不会实时调用,试过将判断写在onTouchEvent里面,实时判断再调用onInterceptTouchEvent,但是好像因为内层的RecyclerView并没有消费掉事件,所以这么做并没有效果,并没有实时的将触摸事件交给内层RecyclerView处理,这里尝试了很多方式,都不太理想,希望有思路的大佬给指点一下,效果如下:
GIF2.gif
项目地址:Github
作者:大名鼎鼎刘小厨
链接:https://www.jianshu.com/p/a5100ac471ae
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。