Android 自定义LinearLayout实现滑动下拉抽屉的功能(解决滑动冲突)

23 篇文章 0 订阅

本篇我们来解决上一篇的遗留问题:自定义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>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值