Android 自定义LinearLayout实现滑动下拉抽屉的功能

23 篇文章 0 订阅

先看效果图:

先来说说思路:我们把该页面分为两部分,分别是头部的抽屉布局(海洋色背景)和主内容布局(白色背景),这两部分的布局是呈线性关系,即抽屉在上,主页面在下,并且它们的父布局应该是一个可滑动的LinearLayout线性布局

所以,我们的目标就是自定义一个可滑动的LinearLayout,并且设置它的子布局都向上移动一个自定义LinearLayout的高度

一、自定义控件的测量和布局

自定义LinearLayout,假设为MyPullDownLayout:

public class MyPullDownLayout extends LinearLayout {
    public MyPullDownLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }
}

我们先对子控件进行简单的测量:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //测量子控件的大小
    for(int i=0;i<getChildCount();i++){
        View view=getChildAt(i);
        measureChild(view,widthMeasureSpec,heightMeasureSpec);
    }
}

在onLayout中设置每一个子控件的位置:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l,t, r, b);
    int height=getVerticalHeight();
    //设置子控件的位置
    for(int i=0;i<getChildCount();i++){
        View view=getChildAt(i);
        view.layout(view.getLeft(),view.getTop()-height,view.getRight(),view.getBottom()-height);
    }
}

private int getVerticalHeight(){
    return getHeight()-getPaddingBottom()-getPaddingTop();
}

int height = getVerticalHeight() 即为这个自定义控件的高度,并让每个子控件都向上移动一个height的高度

 二、自定义控件的滑动实现

我们创建一个手势检测器(GestureDetector)来辅助我们滑动控件:

private int currentPos=0;
private GestureDetector detector=new GestureDetector(new GestureDetector.SimpleOnGestureListener(){
    public boolean onScroll(MotionEvent e1, MotionEvent e2,
                            float distanceX, float distanceY) {
        //相对滑动:Y方向滑动多少距离,view就跟着滑动多少距离
        //手指向上滑动
        if(e2.getY()<e1.getY()&&getScrollY()>=0){
            //滑到的y值超过0时,反弹回去
            if(getScrollY()>0)scrollBy(0,-getScrollY());
            //否则什么也不做
        }
        else if(getScrollY()<=0){
            //如果手指向下滑动并且没有超过抽屉页的滑动范围,就滑动页面
            if(!(e2.getY()>e1.getY()&&getScrollY()==-getVerticalHeight())){
                scrollBy(0,(int)distanceY);
                //手指向下滑动
                if(e2.getY()>e1.getY())currentPos=getScrollY()/(getVerticalHeight()/4);
                //手指向上滑动
                else currentPos=getScrollY()/(4*getVerticalHeight()/5);
                if(currentPos>0)currentPos=0;
                if(currentPos<-1)currentPos=-1;
            }
        }
        return super.onScroll(e1,e2,distanceX,distanceY);
    }
});
@Override
public boolean onTouchEvent(MotionEvent event) {
    //将event信息传给detector;
    detector.onTouchEvent(event);
}
private int getVerticalHeight(){
    return getHeight()-getPaddingBottom()-getPaddingTop();
}

getScrollY():y轴方向已经滑动的距离。如果在抽屉页,则getScrollY()为负数

currentPos:手指离开屏幕后,控件所在的位置(抽屉页或者主页面)

currentPos=getScrollY()/(getVerticalHeight()/4):只需滑动1/4控件高度的距离就可以自动滑动到抽屉页

currentPos=getScrollY()/(4*getVerticalHeight()/5):只需滑动1-4/5=1/5控件高度的距离就可以自动滑动到主页面 

 创建Scorller对象来实现页面的自动滑动效果:

private Scroller scroller;
public MyPullDownLayout(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    init();
}
private void init(){
    scroller=new Scroller(getContext());
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    //将event信息传给detector;
    detector.onTouchEvent(event);
    switch (event.getAction()){
        case MotionEvent.ACTION_UP:
            //手指离开屏幕后开始自动滑动
            scroller.startScroll(0,getScrollY(),0,getVerticalHeight()*currentPos-getScrollY());
            invalidate();
            break;
    }
    return true;
}

@Override
public void computeScroll() {
    if(scroller.computeScrollOffset()){
        scrollTo(0,scroller.getCurrY());
        postInvalidateDelayed(10);
    }
}

scroller.startScroll():前两个参数是x,y方向已经滑动的距离,后两个参数是x,y方向还需要滑动的距离

computeScroll():利用scroller.getCurrY()每次滑动一点点距离,调用postInvalidateDelayed(10)后会回调该方法

三、XML布局文件注意事项

 为了体现抽屉控件的宽高占整个父控件宽高的效果,需要把抽屉布局(Layout)的width和height设为match_parent。然后主页面布局部分就放在抽屉布局的下方,具体代码如下:

<?xml version="1.0" encoding="utf-8"?>
<com.myviewtext.MyPullDownLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    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"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            android:text="蚕丛及鱼凫,开国何茫然!"
            android:textColor="@color/white"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            android:text="尔来四万八千岁,不与秦塞通人烟。西当太白有鸟道,可以横绝峨眉巅。"
            android:textColor="@color/white"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            android:text="地崩山摧壮士死,然后天梯石栈相钩连。"
            android:textColor="@color/white"/>
    </LinearLayout>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="臣本布衣,躬耕南阳,苟全性命于乱世,不求闻达于诸侯。"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,谘臣以当世之事,由是感激,遂许先帝以驱驰。"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="后值倾覆,受任于败军之际,奉命于危难之间:尔来二十有一年矣。"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧虑,恐付托不效,以伤先帝之明;故五月渡泸,深入不毛。"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="今南方已定,甲兵已足,当奖帅三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。"/>
</com.hualinfo.myviewtext.MyPullDownLayout>

自定义LinearLayout完整代码如下:

public class MyPullDownLayout extends LinearLayout {
    private int currentPos=0;
    private Scroller scroller;
    public MyPullDownLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //测量子控件的大小
        for(int i=0;i<getChildCount();i++){
            View view=getChildAt(i);
            measureChild(view,widthMeasureSpec,heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l,t, r, b);
        int height=getVerticalHeight();
        //设置子控件的位置
        for(int i=0;i<getChildCount();i++){
            View view=getChildAt(i);
            view.layout(view.getLeft(),view.getTop()-height,view.getRight(),view.getBottom()-height);
        }
    }

    private void init(){
        scroller=new Scroller(getContext());
    }

    private GestureDetector detector=new GestureDetector(new GestureDetector.SimpleOnGestureListener(){
        public boolean onScroll(MotionEvent e1, MotionEvent e2,
                                float distanceX, float distanceY) {
            //相对滑动:Y方向滑动多少距离,view就跟着滑动多少距离
            //手指向上滑动
            if(e2.getY()<e1.getY()&&getScrollY()>=0){
                //滑到的y值超过0时,反弹回去
                if(getScrollY()>0)scrollBy(0,-getScrollY());
                //否则什么也不做
            }
            else if(getScrollY()<=0){
                //如果手指向下滑动并且没有超过抽屉页的滑动范围,就滑动页面
                if(!(e2.getY()>e1.getY()&&getScrollY()==-getVerticalHeight())){
                    scrollBy(0,(int)distanceY);
                    //手指向下滑动
                    if(e2.getY()>e1.getY())currentPos=getScrollY()/(getVerticalHeight()/4);
                    //手指向上滑动
                    else currentPos=getScrollY()/(4*getVerticalHeight()/5);
                    if(currentPos>0)currentPos=0;
                    if(currentPos < -1)currentPos=-1;
                }
            }
            return super.onScroll(e1,e2,distanceX,distanceY);
        }
    });

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //将event信息传给detector;
        detector.onTouchEvent(event);
        switch (event.getAction()){
            case MotionEvent.ACTION_UP:
                scroller.startScroll(0,getScrollY(),0,getVerticalHeight()*currentPos-getScrollY());
                invalidate();
                break;
        }
        return true;
    }

    @Override
    public void computeScroll() {
        if(scroller.computeScrollOffset()){
            scrollTo(0,scroller.getCurrY());
            postInvalidateDelayed(10);
        }
    }

    private int getVerticalHeight(){
        return getHeight()-getPaddingBottom()-getPaddingTop();
    }
}

本篇到这里就结束,下一篇我们来解决该自定义Layout与RecyclerView滑动冲突的问题:

https://blog.csdn.net/zz51233273/article/details/108355290

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值