scrollview嵌套上下拉控件嵌套recyclerview
Posted 翻滚的咸鱼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了scrollview嵌套上下拉控件嵌套recyclerview相关的知识,希望对你有一定的参考价值。
相信会碰到很多类似的需求,一个列表控件,然后控件上方的一个头部需要自定义,这样就不好有时候也不能加在列表控件的头部了,那必须得嵌套一层scrollview了,没毛病,那么一般的列表控件都是有上拉下拉的操作,而且一般也是在 github 上找寻一个收藏量高的
来做为一个全局通用的上下拉控件,这里问题就来了,一般的 scrollview 嵌套 recyclerview 或者 listview 都毕竟容易解决,可是在加上一层上下拉控件呢?上下拉控件肯定会有它自己的触摸处理机制,这样你改起来也很麻烦,这种滑动触摸的问题最是难搞,也有的是scrollview,或许网上有一些第三方库已经有这个效果了,不过看了一些都是自定义的一些控件来解决的,而这里的虽然也是自定义,但是却是用的一些流行库在不动它原本的基础上自定义
嵌套 viewpage 嵌套 recyclerview 的,不过这种效果网上还是有些方案的,搜索material design效果也有一些现成的,不过 scrollview 嵌套 SwipeRefreshLayout 嵌套 recyclerview 或者 scrollview 嵌套 PtrClassicFrameLayout 嵌套 recyclerview 很少,我搜了好久没有满足我需求的,而且很多是自定义了 recyclerview 和下拉控件才解决的,这样的话它不是用的 github 原控件,可以说是自己写的,可项目里很多都是有固定的一套第三方库,你不能改,除非你是项目负责人,一开始就这样规定了,不然就得想办法去解决
还有一种网上的方案是退而求其次,用 NestedScrollView,不过这也有一些问题,就是下滑的时候对于侵入式的下拉方式有效果,可是对于非侵入刷新就不好使了,而且我个人也更喜欢非侵入刷新,所以花了些空闲的时间捣鼓了一个demo,解决了一个心头之患,下次遇到就能轻松的对待了,不用在去纠结用这个库是否会有问题了
一开始做的走了个误区,没有思考清楚,大致思路就是首先把下拉的控件禁止 setEnabled(false) ,然后scrollview拦截事件,判断滑动,超出了头部的距离就让子控件滑动,这是一个大致的思路,然后就开干了,可想法是美好的,现实是残酷的,并没有取得成功,而且还把自己的思路搅浑了
先看失败的效果图
咋一看好像没啥问题,而且简单,这还说这么多做什么,其实只是效果没有显示出来(显示不出来),这就尴尬了⊙﹏⊙‖∣
如果直接把上下拉控件包住头部其实没有问题,但是这样你下拉的时候在顶端显示效果,这样头部和直接加入到列表控件里有什么区别,所以重新创建了个类,从头再来,好好在理理思路,先从简单的开始,SwipeRefreshLayout 是android自带的下拉刷新库,而且是侵入式的下拉刷新,这样处理起来更简单,柿子挑软的捏了,这样都搞不定那就猝死在电脑边吧,结果肯定是成功了,先看效果图
this is scrollView其实就是一个固定的头部,测试用的,可以换成自己的,而且还能根据滑动添加动画,基本的需求都满足了,直接看代码,代码粗糙了点
1 package com.example.demo.test2; 2 3 import android.os.Handler; 4 import android.support.v4.widget.SwipeRefreshLayout; 5 import android.support.v7.widget.LinearLayoutManager; 6 import android.support.v7.widget.RecyclerView; 7 import android.view.LayoutInflater; 8 import android.view.View; 9 import android.view.ViewGroup; 10 import android.view.ViewTreeObserver; 11 import android.widget.TextView; 12 import android.widget.Toast; 13 14 import com.example.demo.BaseActivity; 15 import com.example.demo.R; 16 17 import butterknife.BindView; 18 19 public class Test3Activity extends BaseActivity { 20 21 @BindView(R.id.scrollView) 22 MyNestedScroll scrollView; 23 @BindView(R.id.tv_head) 24 TextView tv_head; 25 @BindView(R.id.layout) 26 SwipeRefreshLayout layout; 27 @BindView(R.id.recyclerView) 28 RecyclerView recyclerView; 29 private boolean hasMeasured; 30 31 @Override 32 protected int getLayoutId() { 33 return R.layout.activity_test3; 34 } 35 36 @Override 37 protected void initView() { 38 recyclerView.setLayoutManager(new LinearLayoutManager(Test3Activity.this)); 39 recyclerView.setAdapter(new ContentAdapter()); 40 ViewTreeObserver vto = tv_head.getViewTreeObserver(); 41 vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 42 public boolean onPreDraw() { 43 if (hasMeasured == false) { 44 int height = tv_head.getMeasuredHeight(); 45 scrollView.setHeadHeight(height); 46 hasMeasured = true; 47 } 48 return true; 49 } 50 }); 51 scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() { 52 @Override 53 public void onScrollChanged() { 54 boolean b = scrollView.getScrollY()==0; 55 layout.setEnabled(b); 56 } 57 }); 58 //设置刷新时动画的颜色,可以设置4个 59 layout.setColorSchemeResources(android.R.color.holo_blue_light, android.R.color.holo_red_light, 60 android.R.color.holo_orange_light, android.R.color.holo_green_light); 61 layout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { 62 63 @Override 64 public void onRefresh() { 65 //正在刷新 66 // TODO Auto-generated method stub 67 new Handler().postDelayed(new Runnable() { 68 69 @Override 70 public void run() { 71 // TODO Auto-generated method stub 72 //刷新完成 73 layout.setRefreshing(false); 74 } 75 }, 500); 76 } 77 }); 78 79 } 80 81 class ContentAdapter extends RecyclerView.Adapter { 82 @Override 83 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 84 return new ContentViewHolder(LayoutInflater.from(Test3Activity.this).inflate(R.layout.item_test, parent, false)); 85 } 86 87 @Override 88 public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { 89 final ContentViewHolder viewHolder = (ContentViewHolder) holder; 90 viewHolder.itemView.setOnClickListener(new View.OnClickListener() { 91 @Override 92 public void onClick(View view) { 93 Toast.makeText(Test3Activity.this, ""+position, Toast.LENGTH_SHORT).show(); 94 } 95 }); 96 } 97 98 @Override 99 public int getItemCount() { 100 return 15; 101 } 102 } 103 104 class ContentViewHolder extends RecyclerView.ViewHolder { 105 106 public ContentViewHolder(View itemView) { 107 super(itemView); 108 } 109 } 110 }
1 package com.example.demo.test2; 2 3 import android.content.Context; 4 import android.util.AttributeSet; 5 import android.view.MotionEvent; 6 import android.widget.ScrollView; 7 8 /** 9 * Created by LiuZhen on 2017/6/8. 10 */ 11 12 public class MyNestedScroll extends ScrollView { 13 14 private int headHeight = 300;//默认高度,一般动态设置 15 private int downY; 16 17 public MyNestedScroll(Context context) { 18 super(context); 19 } 20 21 public MyNestedScroll(Context context, AttributeSet attrs) { 22 super(context, attrs); 23 } 24 25 public MyNestedScroll(Context context, AttributeSet attrs, int defStyleAttr) { 26 super(context, attrs, defStyleAttr); 27 } 28 29 @Override 30 public boolean onInterceptTouchEvent(MotionEvent e) { 31 int action = e.getAction(); 32 int dis = getScrollY(); 33 switch (action) { 34 case MotionEvent.ACTION_DOWN: 35 downY = (int) e.getRawY(); 36 break; 37 case MotionEvent.ACTION_MOVE: 38 int moveY = (int) e.getRawY(); 39 /**判断是向下滑动**/ 40 if(downY-moveY>0){ 41 if (dis < headHeight && dis >= 0){ 42 return true;//滑动距离小于头部并且没有超出头部拦截事件,让本身去滑动 43 }else{ 44 return false;//不拦截事件 45 } 46 }else{ 47 } 48 } 49 50 return super.onInterceptTouchEvent(e); 51 } 52 53 public void setHeadHeight(int headHeight) { 54 this.headHeight = headHeight; 55 } 56 57 }
这个很简单,主要的逻辑也就一个方法里
然后在github上又找了个下拉库,收藏毕竟多
结果不行了,上滑一小段距离,然后在下拉,结果拉不动了,直接就显示了上拉效果,这显然不正常,问题一个一个来解决,肯定是有其它问题的,不过先把眼前的解决了才能下一步,要解决这个问题就得禁止 PtrClassicFrameLayout 的下拉了,问题的根源很有可能是我们用的上下拉控件的滑动刷新效果起冲突了,最好的办法肯定是直接禁止了,所以就给scrollview来个监听 onScrollChanged ,获取scrollview的scrolly,如果scrolly是0,就不禁止,因为在顶部了,不是0就禁止下拉操作,结果好了,可惜新的问题来了,上拉没反应了,因为被禁止了,所以需要判断滑动到了底部让下拉效果出现
于是有了这么两个判断,然后肯定还是有问题的,上拉效果没事了,下拉效果出事了,滑动到顶端的时候下拉效果直接出现了,这样头部都显示不出来了,而此时事件处于子列表控件,所以scrollview不好去监听了,只得在 recyclerView 上面下手了,需要监听滑动到顶部,然后禁止上下拉效果,交给scrollview滑动,让头部显示出来
可是这样一来发现还是那样,而调试发现的确触发了事件,可是为什么上下拉还是没有禁止呢,这样滑动不出头部,然后调试发现被下面的 scrollview 的 onScrollChanged 事件里覆盖了,又设置回去了,所以还得控制一下这里,让scrollview的监听不要覆盖上面的设置,这里只是下拉的时候才会用到,所以就有了dy这个全局参数,这样一来就ok了
1 package com.example.demo.test2; 2 3 import android.os.Handler; 4 import android.support.v4.widget.NestedScrollView; 5 import android.support.v4.widget.SwipeRefreshLayout; 6 import android.support.v7.widget.LinearLayoutManager; 7 import android.support.v7.widget.RecyclerView; 8 import android.util.Log; 9 import android.view.LayoutInflater; 10 import android.view.MotionEvent; 11 import android.view.View; 12 import android.view.ViewGroup; 13 import android.view.ViewTreeObserver; 14 import android.widget.ScrollView; 15 import android.widget.TextView; 16 import android.widget.Toast; 17 18 import com.example.demo.BaseActivity; 19 import com.example.demo.R; 20 import com.example.demo.test2.MyRecyclerView; 21 22 import butterknife.BindView; 23 import in.srain.cube.views.ptr.PtrClassicFrameLayout; 24 import in.srain.cube.views.ptr.PtrDefaultHandler2; 25 import in.srain.cube.views.ptr.PtrFrameLayout; 26 27 public class Test2Activity extends BaseActivity { 28 29 @BindView(R.id.scrollView) 30 MyNestedScroll scrollView; 31 @BindView(R.id.tv_head) 32 TextView tv_head; 33 @BindView(R.id.layout) 34 PtrClassicFrameLayout layout; 35 @BindView(R.id.recyclerView) 36 RecyclerView recyclerView; 37 private boolean hasMeasured; 38 int height,dy; 39 40 @Override 41 protected int getLayoutId() { 42 return R.layout.activity_test2; 43 } 44 45 @Override 46 protected void initView() { 47 final LinearLayoutManager manager = new LinearLayoutManager(Test2Activity.this); 48 recyclerView.setLayoutManager(manager); 49 recyclerView.setAdapter(new ContentAdapter()); 50 ViewTreeObserver vto = tv_head.getViewTreeObserver(); 51 vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 52 public boolean onPreDraw() { 53 if (hasMeasured == false) { 54 height = tv_head.getMeasuredHeight(); 55 scrollView.setHeadHeight(height);//设置头部的高度 56 hasMeasured = true; 57 } 58 return true; 59 } 60 }); 61 62 recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 63 @Override 64 public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 65 66 // int visibleItemCount = manager.getChildCount(); 67 // int totalItemCount = manager.getItemCount(); 68 int pastVisiblesItems = manager.findFirstVisibleItemPosition(); 69 Test2Activity.this.dy = dy; 70 //小于0表示下拉滑动,下拉到顶端则禁止子控件刷新,让scrollview滑动,头部完全显示后才能下拉,这里只做下滑到顶部时禁止立刻刷新,让scrollview滑动 71 if ( pastVisiblesItems == 0 && dy < 0) { 72 Log.e("onScrolled","dy "+dy); 73 layout.setEnabled(false); 74 } 75 } 76 }); 77 scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() { 78 @Override 79 public void onScrollChanged() { 80 //滑动的距离超出头部,让列表控件自己滑动,dy大于0是上滑操作,这里只做滑动超出头部后交给子列表滑动 81 boolean b = scrollView.getScrollY()==0;//上拉一段距离后下拉,结果直接下拉效果出现了,滑动失效 82 //解决上拉滑动到底部后让上拉效果有用 83 if (scrollView != null && scrollView.getMeasuredHeight() <= scrollView.getScrollY()+height && dy >= 0) { 84 b = true; 85 // Log.e("Changed","true "); 86 } 87 layout.setEnabled(b); 88 Log.e("Changed",""+b+" - scrollView.getScrollY "+scrollView.getScrollY()); 89 } 90 }); 91 layout.setLastUpdateTimeRelateObject(this); 92 layout.setPtrHandler(new PtrDefaultHandler2() { 93 94 @Override 95 public void onLoadMoreBegin(PtrFrameLayout frame) { 96 updateData(); 97 } 98 99 @Override 100 public void onRefreshBegin(PtrFrameLayout frame) { 101 updateData(); 102 } 103 104 @Override 105 public boolean checkCanDoLoadMore(PtrFrameLayout frame, View content, View footer) { 106 return super.checkCanDoLoadMore(frame, recyclerView, footer); 107 } 108 109 @Override 110 public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) { 111 return super.checkCanDoRefresh(frame, recyclerView, header); 112 } 113 }); 114 115 layout.postDelayed(new Runnable() { 116 @Override 117 public void run() { 118 // mPtrFrame.autoRefresh(); 119 } 120 }, 100); 121 122 123 } 124 125 protected void updateData() { 126 127 layout.postDelayed(new Runnable() { 128 @Override 129 public void run() { 130 layout.refreshComplete(); 131 } 132 }, 1000); 133 } 134 135 class ContentAdapter extends RecyclerView.Adapter { 136 @Override 137 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 138 return new ContentViewHolder(LayoutInflater.from(Test2Activity.this).inflate(R.layout.item_test, parent, false)); 139 } 140 141 @Override 142 public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { 143 final ContentViewHolder viewHolder = (ContentViewHolder) holder; 144 viewHolder.itemView.setOnClickListener(new View.OnClickListener() { 145 @Override 146 public void onClick(View view) { 147 Toast.makeText(Test2Activity.this, ""+position, Toast.LENGTH_SHORT).show(); 148 } 149 }); 150 } 151 152 @Override 153 public int getItemCount() { 154 return 15; 155 } 156 } 157 158 class ContentViewHolder extends RecyclerView.ViewHolder { 159 160 public ContentViewHolder(View itemView) { 161 super(itemView); 162 } 163 } 164 }
侵入式的下拉刷新的确麻烦很多,但是好在自己也都解决了,而有了这两种方案不敢说每个第三方库都能通用,但是大部分都可以通用,只要懂得了其中的原理,自己稍加修改也行,毕竟第三方库太多了,每个库都有它自己的自定义,所以还是要根据独特的情况去修改
网上有些第三方的,但是也都是人家自定义重新做的,并不能完美的和第三方库结合,所以很多时候与其苦苦找寻不如自己慢慢摸索,这样无论是用别人的还是自己定义都更加的了解原理,而且这种效果是很频繁出现在项目中的,所以自己必须得搞懂才行,不然你下次种会遇到它的
前面命名有点乱,在这里从新把代码都贴一下,以免搞混
侵入式和非侵入式
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.TextView; import android.widget.Toast; import com.example.demo.BaseActivity; import com.example.demo.R; import butterknife.BindView; import in.srain.cube.views.ptr.PtrClassicFrameLayout; import in.srain.cube.views.ptr.PtrDefaultHandler2; import in.srain.cube.views.ptr.PtrFrameLayout; public class PtrClassicFrameScrollViewActivity extends BaseActivity { @BindView(R.id.scrollView) MyNestedScroll scrollView; @BindView(R.id.tv_head) TextView tv_head; @BindView(R.id.layout) PtrClassicFrameLayout layout; @BindView(R.id.recyclerView) RecyclerView recyclerView; private boolean hasMeasured; int height,dy; @Override protected int getLayoutId() { return R.layout.activity_ptr; } @Override protected void initView() { final LinearLayoutManager manager = new LinearLayoutManager(context); recyclerView.setLayoutManager(manager); recyclerView.setAdapter(new ContentAdapter()); ViewTreeObserver以上是关于scrollview嵌套上下拉控件嵌套recyclerview的主要内容,如果未能解决你的问题,请参考以下文章
SCrollView嵌套GridView,ScrollView如何实现上啦加载下拉刷新???
ScrollView中嵌套GridView,ListView只显示一行的解决办法