本篇我们来解决上一篇的遗留问题:自定义LinearLayout与RecyclerView的滑动冲突。
没看过上一篇文章的,可以先去看一下:https://blog.csdn.net/zz51233273/article/details/108320445
先看效果图:
一、解决思路
当手指一开始滑动时,触摸事件会被传递给RecyclerView去处理。并且在滑动列表数据的时候,我们确实是想让RecyclerView去处理触摸事件。经过长时间尝试,我发现用onInterceptTouchEvent方法让LinearLayout拦截事件是很难实现图中的效果。所以我换了一种解决思路:重写RecyclerView的onTouchEvent方法
我们不再去处理触摸的拦截事件,全部交由RecyclerView处理,并且当滑动到RecyclerView顶部并继续手指往下滑时,就手动调用LinearLayout的scrollTo或scrollBy来让它进行相应的滑动,而不是在LinearLayout的触摸事件里做逻辑处理。
当我们滑到抽屉页面时,自然而然触摸事件就自动交给了自定义LinearLayout处理。
二、代码解释
下面我给出代码,并解释每一步的操作
创建MyRecyclerView类并继承RecyclerView:
public class MyRecyclerView extends RecyclerView {
public MyRecyclerView(@NonNull Context context) {
super(context);
}
public MyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean onTouchEvent(MotionEvent e) {
return super.onTouchEvent(e);
}
}
因为在滑动的时候,RecyclerView需要提醒外界是否手指向下滑动或者滑动完毕,所以需要开放接口:
public interface OnScroll{
//向下滑动时回调
float scrollPullDown(int dy);
//手指离开屏幕时回调
void eventUp(int dy);
}
private OnScroll onScroll;
public void setOnScroll(OnScroll onScroll){
this.onScroll=onScroll;
}
当手指向上滑动时,肯定是处在滑动列表数据的状态,我们不用去做处理。当下滑的时候,可能是在滑动列表,也有可能是在滑动LinearLayout,这里就需要去判断一下。下面我们重写RecyclerView的onTouchEvent方法:
private float lastPosY,moveY; //lastPosY:手指最后滑动到的y轴位置;moveY:手指总共滑动的垂直距离
@Override
public boolean onTouchEvent(MotionEvent e) {
switch (e.getAction()){
case MotionEvent.ACTION_DOWN:
lastPosY=e.getY();
moveY=0;
break;
case MotionEvent.ACTION_MOVE:
//如果手指往下滑动
if(lastPosY<e.getY()){
if(onScroll!=null){
moveY+=(e.getY()-lastPosY)/2;
moveY=onScroll.scrollPullDown((int)moveY);
lastPosY=e.getY();
if(moveY>0)return true;
}
}
lastPosY=e.getY();
if(moveY>0)return true;
break;
case MotionEvent.ACTION_UP:
if(onScroll!=null)
onScroll.eventUp((int)moveY);
break;
}
return super.onTouchEvent(e);
}
lastPosY<e.getY():代表手指向下滑动
(e.getY()-lastPosY)/2:减少每次滑动LinearLayout的距离,保证滑动的流畅性
if(moveY>0)return true:防止在滑动LinearLayout的时候同时滑动列表,从而产生滑动时的抖动。
现在我们已经把滑动信息传递给了外界,下面我们用Activity来获取该信息:
private MyRecyclerView recyclerView;
private MyPullDownLayout pullDownLayout;
private LinearLayoutManager linearLayoutManager;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_pull_down);
init();
}
private void init(){
final List<String> list=new ArrayList<>();
//......
recyclerView=findViewById(R.id.recyclerview);
recyclerView.setAdapter(new MyRecyclerViewAdapter(list));
linearLayoutManager=new LinearLayoutManager(this);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setOnScroll(new MyRecyclerView.OnScroll() {
@Override
public float scrollPullDown(int dy) {
return 0;
}
@Override
public void eventUp(int dy) {
}
});
}
xml文件也给大家放一下:
<?xml version="1.0" encoding="utf-8"?>
<com.myviewtext.MyPullDownLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pulldown_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@mipmap/bg">
<TextView
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="蜀道之难,难于上青天!"
android:textColor="@color/white"/>
</LinearLayout>
<com.utils.MyRecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:clickable="true"/>
</com.myviewtext.MyPullDownLayout>
那么在Activity中要怎么判断是否需要滑动LinearLayout呢?其实很容易,如果RecyclerView列表的第一项完全显示并且手指还在往下滑,那么就需要滑动LinearLayout,代码如下(Activity类):
private void init(){
final List<String> list=new ArrayList<>();
//......
pullDownLayout=findViewById(R.id.pulldown_layout);
recyclerView=findViewById(R.id.recyclerview);
recyclerView.setAdapter(new MyRecyclerViewAdapter(list));
linearLayoutManager=new LinearLayoutManager(this);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setOnScroll(new MyRecyclerView.OnScroll() {
@Override
public float scrollPullDown(int dy) {
if(linearLayoutManager.findFirstCompletelyVisibleItemPosition()==0){
pullDownLayout.scrollY(dy);
return dy;
}
return 0;
}
@Override
public void eventUp(int dy) {
if(linearLayoutManager.findFirstVisibleItemPosition()==0){
pullDownLayout.scrollToPage(dy);
}
}
});
}
linearLayoutManager.findFirstCompletelyVisibleItemPosition()==0:如果列表第一项完全显示
linearLayoutManager.findFirstVisibleItemPosition()==0:如果列表第一项有显示
我们再来看看pullDownLayout对象的scrollY和scrollToPage方法:
public class MyPullDownLayout extends LinearLayout {
private Scroller scroller;
public MyPullDownLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init(){
scroller=new Scroller(getContext());
}
//......
@Override
public void computeScroll() {
if(scroller.computeScrollOffset()){
scrollTo(0,scroller.getCurrY());
postInvalidateDelayed(8);
}
}
private int getVerticalHeight(){
return getHeight()-getPaddingBottom()-getPaddingTop();
}
public void scrollY(int dy){
scrollTo(0,-dy);
}
public void scrollToPage(int dy){
if(dy<getVerticalHeight()/6){
scroller.startScroll(0,getScrollY(),0,-getScrollY());
}else{
scroller.startScroll(0,getScrollY(),0,-getVerticalHeight()-getScrollY());
}
invalidate();
}
}
dy<getVerticalHeight()/6:如果总共滑动的距离小于布局高度的1/6,则自动返回到RecyclerView所在页面,否则自动滑动到抽屉页面
到这里,滑动冲突就解决了(虽然不是利用拦截的手段,但也是一种解决办法,而且效果也不错)
下面是MyRecyclerView完整代码:
public class MyRecyclerView extends RecyclerView {
private float lastPosY,moveY; //lastPosY:手指最后滑动到的y轴位置;moveY:手指总共滑动的垂直距离
public MyRecyclerView(@NonNull Context context) {
super(context);
}
public MyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean onTouchEvent(MotionEvent e) {
switch (e.getAction()){
case MotionEvent.ACTION_DOWN:
lastPosY=e.getY();
moveY=0;
break;
case MotionEvent.ACTION_MOVE:
//如果手指往下滑动
if(lastPosY<e.getY()){
if(onScroll!=null){
moveY+=(e.getY()-lastPosY)/2;
moveY=onScroll.scrollPullDown((int)moveY);
lastPosY=e.getY();
if(moveY>0)return true;
}
}
lastPosY=e.getY();
if(moveY>0)return true;
break;
case MotionEvent.ACTION_UP:
if(onScroll!=null)
onScroll.eventUp((int)moveY);
break;
}
return super.onTouchEvent(e);
}
public interface OnScroll{
//向下滑动时回调
float scrollPullDown(int dy);
//手指离开屏幕时回调
void eventUp(int dy);
}
private OnScroll onScroll;
public void setOnScroll(OnScroll onScroll){
this.onScroll=onScroll;
}
}
MyRecyclerViewAdapter代码:
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> {
private List<String> list;
public MyRecyclerViewAdapter(List<String> list){
this.list=list;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list,parent,false);
ViewHolder holder = new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.tv.setText(list.get(position));
}
@Override
public int getItemCount() {
return list.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
TextView tv;
public ViewHolder(@NonNull View view) {
super(view);
tv=view.findViewById(R.id.tv);
}
}
}
item_list布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="@color/black"
android:background="@color/white"/>
</LinearLayout>