Android 下拉式抽屉折叠动画

自定义listview工具类1、

public class ViewMeasureUtils {/*** 根据父 View 规则和子 View 的 LayoutParams,计算子类的宽度(width)测量规则** @param view*/public static int getChildWidthMeasureSpec(View view, int parentWidthMeasureSpec) {// 获取父 View 的测量模式int parentWidthMode = MeasureSpec.getMode(parentWidthMeasureSpec);// 获取父 View 的测量尺寸int parentWidthSize = MeasureSpec.getSize(parentWidthMeasureSpec);// 定义子 View 的测量规则int childWidthMeasureSpec = 0;// 获取子 View 的 LayoutParamsViewGroup.LayoutParams layoutParams = (ViewGroup.LayoutParams) view.getLayoutParams();if (parentWidthMode == MeasureSpec.EXACTLY || parentWidthMode == MeasureSpec.AT_MOST) {/* 这是当父类的模式是 dp 的情况 */if (layoutParams.width > 0) {childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY);} else if (layoutParams.width == ViewGroup.LayoutParams.WRAP_CONTENT) {childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(parentWidthSize, MeasureSpec.AT_MOST);} else if (layoutParams.width == ViewGroup.LayoutParams.MATCH_PARENT) {childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(parentWidthSize, MeasureSpec.EXACTLY);}} else if (parentWidthMode == MeasureSpec.UNSPECIFIED) {/* 这是当父类的模式是 MATCH_PARENT 的情况 */if (layoutParams.width > 0) {childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY);} else if (layoutParams.width == ViewGroup.LayoutParams.WRAP_CONTENT) {childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);} else if (layoutParams.width == ViewGroup.LayoutParams.MATCH_PARENT) {childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);}}// 返回子 View 的测量规则return childWidthMeasureSpec;}/*** 根据父 View 规则和子 View 的 LayoutParams,计算子类的宽度(width)测量规则** @param view*/public static int getChildHeightMeasureSpec(View view, int parentHeightMeasureSpec) {// 获取父 View 的测量模式int parentHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec);// 获取父 View 的测量尺寸int parentHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec);// 定义子 View 的测量规则int childHeightMeasureSpec = 0;// 获取子 View 的 LayoutParamsViewGroup.LayoutParams layoutParams = (ViewGroup.LayoutParams) view.getLayoutParams();if (parentHeightMode == MeasureSpec.EXACTLY || parentHeightMode == MeasureSpec.AT_MOST) {/* 这是当父类的模式是 dp 的情况 */if (layoutParams.height > 0) {childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);} else if (layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(parentHeightSize, MeasureSpec.AT_MOST);} else if (layoutParams.width == ViewGroup.LayoutParams.MATCH_PARENT) {childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(parentHeightSize, MeasureSpec.EXACTLY);}} else if (parentHeightMode == MeasureSpec.UNSPECIFIED) {/* 这是当父类的模式是 MATCH_PARENT 的情况 */if (layoutParams.height > 0) {childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);} else if (layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);} else if (layoutParams.height == ViewGroup.LayoutParams.MATCH_PARENT) {childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);}}// 返回子 View 的测量规则return childHeightMeasureSpec;}
}

 

自定义listview、


public class SlidingMenuVertical extends LinearLayout {
    private Scroller mScroller;
    private View view_top;
    private View view_bottom;
    private float downX;
    private float downY;
    private boolean opened = true;//状态是否开闭


    private OnSwitchListener onSwitchListener;


    private int duration_max = 300;//最长过度时间

    private int ambit_scroll = 100;//滑动界限,开闭

    private int y_opened = -1;    // * y_opened:抽屉打开时view_bootom的top y

    public SlidingMenuVertical(Context context) {
        this(context, null);
    }


    public SlidingMenuVertical(Context context, AttributeSet attrs) {
        super(context, attrs);
        mScroller = new Scroller(context);
        setOrientation(VERTICAL);
    }

    @Override
    protected void onFinishInflate() {
        // 当xml解析完成时的回调

        view_top = getChildAt(0);
        view_bottom = getChildAt(1);


    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);


        view_top.measure(widthMeasureSpec, ViewMeasureUtils.getChildHeightMeasureSpec(view_top, heightMeasureSpec));

//        view_middle.measure(widthMeasureSpec,ViewMeasureUtils.getChildHeightMeasureSpec(view_middle,heightMeasureSpec));
        view_bottom.measure(widthMeasureSpec, heightMeasureSpec);

    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {

        setY_opened();
        // 拦截
        // 竖直滑动时,去拦截
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();

                break;
            case MotionEvent.ACTION_MOVE:
                float moveX = event.getX();
                float moveY = event.getY();
                // 竖直滑动

                if (Math.abs(moveY - downY) > Math.abs(moveX - downX)) {
                    //上面隐藏
                    if (opened == false) {

                        return false;
                    }

                    //上面显示并且下滑
                    if (opened == true && (moveY - downY) > 0) {

                        return false;
                    }

                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }
        return super.onInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float moveX = event.getX();
                float moveY = event.getY();

                int dy = (int) (downY - moveY + 0.5f);// 四舍五入 20.9 + 0.5-->20


//                Log.e("dy","++++++++++++++++++++++++++++"+dy);

                int scrollY = getScrollY();
                //mDownY - moveY>0上滑

                if (scrollY + dy > 0) {
                    scrollBy(0, dy);
                    if (scrollY + dy > getHeight_top()) {
                        scrollTo(0, getHeight_top());
                    }
                }

                downX = moveX;
                downY = moveY;

                break;
            case MotionEvent.ACTION_UP:
//                Log.e("heigth_top", "+++++++++++++++++" + height_top);
//                Log.e("scrollY", "+++++++++++++++++" + getScrollY());
                if (opened) {


                    open(!(getScrollY() > ambit_scroll || getScrollY() > getHeight_top() / 3));

                } else {

                    open(getScrollY() < getHeight_top() - ambit_scroll || getScrollY() < getHeight_top() * 2 / 3);

                }

                break;

        }
        // 消费掉
        return true;
    }

    /**
     * 开闭抽屉
     *
     * @param open
     */
    public void open(boolean open) {
        setY_opened();

        this.opened = open;
        //打开
        if (open) {

//            Log.e("打开", "+++++++++++++++++++++++++++++");


            int startX = getScrollX();// 起始的坐标X
            int startY = getScrollY();// 起始的坐标Y

            int endX = 0;
            int endY = 0;

            int dx = endX - startX;// 增量X
            int dy = endY - startY;// 增量Y
            // 1px = 10
            int duration = Math.abs(dy) * 10;
            if (duration > duration_max) {
                duration = duration_max;
            }

            mScroller.startScroll(startX, startY, dx, dy, duration);
        } else {


            Log.e("关闭", "+++++++++++++++++++++++++++++" + getScrollY());
            int startX = getScrollX();// 起始的坐标X
            int startY = getScrollY();// 起始的坐标Y

            int endX = 0;
            int endY = getHeight_top();

            int dx = endX - startX;// 增量X
            int dy = endY - startY;// 增量Y

            // 1px = 10
            int duration = Math.abs(dy) * 10;
            if (duration > duration_max) {
                duration = duration_max;
            }

            // 模拟数据变化
            mScroller.startScroll(startX, startY, dx, dy, duration);
        }

        invalidate();// 触发ui绘制 --> draw() --> dispatchDraw()--> drawChild -->
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {// 如果正在计算的过程中
            // 更新滚动的位置
            scrollTo(0, mScroller.getCurrY());
            invalidate();
        }

    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);

//        Log.e("y_now", ScreenUtils.getViewScreenLocation(view_bottom)[1] + "++++++++++++++++++++++");
//
//        Log.e("y_closed", y_opened - height_top + "++++++++++++++++++++++");

        if (onSwitchListener != null) {
            onSwitchListener.onSwitching(t - oldt < 0 ? true : false,
                    getY_now(), getY_opened(), getY_opened() - getHeight_top());
            if (getY_now() == getY_opened()) {
//                Log.e("true", "++++++++++++++++++++++++");
                onSwitchListener.onSwitched(true);
            }
            if (getY_now() == getY_opened() - getHeight_top()) {
//                Log.e("false", "++++++++++++++++++++++++");

                onSwitchListener.onSwitched(false);
            }

        }
    }

    public boolean isOpened() {
        return opened;
    }

    public int getDuration_max() {
        return duration_max;
    }

    /**
     * 设置松手后 开闭最长过渡时间
     *
     * @param duration_max
     */
    public void setDuration_max(int duration_max) {
        this.duration_max = duration_max;
    }

    public View getView_top() {
        return view_top;
    }

    public View getView_bottom() {
        return view_bottom;
    }

    public int getHeight_top() {
        return view_top.getMeasuredHeight();
    }

    /**
     * 获取 * y_opened:抽屉打开时view_bootom的top y
     */
    private void setY_opened(){

        if (y_opened<0){

            y_opened=getViewScreenLocation(view_bottom)[1];
            Log.e("y _open",y_opened+"++++++++++++++++++++");
        }
    }
    /**
     * y_opened:抽屉打开时view_bootom的top y
     *
     * @return
     */
    public int getY_opened() {
        if (y_opened<0){
            Log.e("还未计算出来","+++++++++++++++++++++++++++++++++++");
            return 0;
        }
        return y_opened;
    }

    /**
     * y_now:抽屉实时view_bootom的top y
     *
     * @return
     */
    public int getY_now() {
        return getViewScreenLocation(view_bottom)[1];
    }

    public int getAmbit_scroll() {
        return ambit_scroll;
    }

    /**
     * 修改滑动界限 值,值越大  开闭越难  单位ms
     *
     * @param ambit_scroll <height_top
     */

    public void setAmbit_scroll(int ambit_scroll) {
        this.ambit_scroll = ambit_scroll;
    }

    /**
     * 计算指定的 View 在屏幕中的坐标。
     */
    public  int[] getViewScreenLocation(View view) {
        int[] location = new int[2];
        // 获取控件在屏幕中的位置,返回的数组分别为控件左顶点的 x、y 的值
        view.getLocationOnScreen(location);

        return location;
    }
    public interface OnSwitchListener {

        /*
        滑动中
        y_now:实时view_bottom的top y, y_opened:抽屉打开时view_bootom的top y,y_closed:抽屉关闭时view_bottom的top y  top y:在屏幕中的top y坐标

         */
        public void onSwitching(boolean isToOpen, int y_now, int y_opened, int y_closed);

        /*
        滑动停止,状态是否开闭
         */
        public void onSwitched(boolean opened);
    }

    public void setOnSwitchListener(OnSwitchListener onSwitchListener) {
        this.onSwitchListener = onSwitchListener;
    }
}

2、activity实现代码,可打开可关闭抽屉动画,可监听动画距离——渐变效果

final TextView tv_middle = (TextView) findViewById(R.id.tv_middle);
final SlidingMenuVertical slidingMenuVertical = ((SlidingMenuVertical) findViewById(R.id.slidingMenu));
slidingMenuVertical.setDuration_max(2300);
slidingMenuVertical.setAmbit_scroll(100);
slidingMenuVertical.setOnSwitchListener(new SlidingMenuVertical.OnSwitchListener() {/*滑动中
y_now:实时view_bottom的top y, y_opened:抽屉打开时view_bootom的top y,y_closed:抽屉关闭时view_bottom的top y  top y:在屏幕中的top y坐标*/@Overridepublic void onSwitching(boolean isToOpen, int y_now, int y_opened, int y_closed) {tv_middle.setBackgroundColor(Color.argb((int) (1.0f * (y_opened - y_now) / (y_opened - y_closed) * 255),Color.red(0xff3F51B5), Color.green(0xff3F51B5), Color.blue(0xff3F51B5)));tv_middle.setTextColor(Color.argb((int) (1.0f * (y_opened - y_now) / (y_opened - y_closed) * 255),Color.red(0xffffffff), Color.green(0xffffffff), Color.blue(0xffffffff)));}@Overridepublic void onSwitched(boolean opened) {if (opened) {tv_middle.setBackgroundColor(0xffffffff);tv_middle.setTextColor(0xff454545);}}
});findViewById(R.id.tv_switch).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {slidingMenuVertical.open(!slidingMenuVertical.isOpened());}
});

 

3、layout.xml文件。

说明:SlidingMenuVertical里面第一个item就是抽屉内容——可以是view,可以是ViewGroup,第二个item就是抽屉下面的内容,第二个item里面ScrollView外部view可开关抽屉内容

<com.tianxin.choutis.SlidingMenuVerticalandroid:id="@+id/myct"android:layout_below="@+id/kgte"android:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:id="@+id/myte"android:layout_width="match_parent"android:layout_height="150dp"android:background="@color/colorAccent"android:text="Hello World!" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:id="@+id/tv_middle"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center"android:textColor="#454545" /><ScrollViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:background="#e6e6e6"android:overScrollMode="never"android:scrollbars="vertical"><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="下面\n下面\n下面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n下面\n" /></ScrollView></LinearLayout></com.tianxin.choutis.SlidingMenuVertical>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/414981.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

18.抽象模板方法———获取程序运行的时间

需求&#xff1a;获取一段程序运行的时间。原理&#xff1a;   获取程序开始和结束的额时间并相见即可  获取时间&#xff1a;System.currentTimeMillis(); 当代码完成优化后&#xff0c;就可以解决这类问题。这种方式&#xff0c;叫模板方法设计模式。 什么是模板方法呢&a…

js随机从数组中取出几个元素

这篇文章为转载&#xff0c;我的需求是从题库中&#xff0c;随机抽几道题&#xff0c;作为新试卷。代码如下&#xff1a; var items [1,2,4,5,6,7,8,9,10];1.从数组items中随机取出一个元素 var item items[Math.floor(Math.random()*items.length)];2.从前面的一篇随机数组…

工作214:结构 vue操作一个很有意思的报错 [Vue warn]: You may have an infinite update loop in a component

结构 vue操作一个很有意思的报错 [Vue warn]: You may have an infinite update loop in a component render function. 代码&#xff1a; <template><span class"show-filters" &#xff1a;class"show !show">{{ show ? 隐藏过滤器 ↑ …

Android 实现选中与非选中样式效果

drawable文件 <?xml version"1.0" encoding"utf-8"?> <selector xmlns:android"http://schemas.android.com/apk/res/android"><item android:drawable"drawable/log_button_bgok" android:state_focused"true&…

beta冲刺总结

团队成员及分工 姓名学号分工陈家权031502107前端&#xff08;消息模块&#xff09;赖晓连031502118前端&#xff08;问答模块&#xff09;雷晶031502119服务器林巧娜031502125前端&#xff08;首页模块&#xff09;一、项目预期计划及现实进展 项目预期计划现实进展解决页面异…

cesium polygon 悬浮在半空中

效果如下&#xff1a; 代码&#xff1a; let arr [[120.87140893447473, 31.877030830389447, 128.64],[120.87140872335587, 31.876963534304792, 128.64],[120.87202301763111, 31.87696299560446, 128.63],[120.87202432001283, 31.877030271988385, 128.63]]arr [].con…

工作215:点击按钮报错

获取值有点问题 点击同一按钮获取值有问题

phalcon无限重定向

问题 换了个新电脑&#xff0c;后来重新配置phalcon环境。由于用得是windows&#xff0c;而且还用得是2.0.5得版本&#xff0c;官网已经没提供这个版本下载了。而旧电脑已经被格式化了&#xff0c;?。 寻找旧版本 通过一番搜索&#xff0c;发现了一个issue&#xff0c;提到pha…

工作按钮(216):点击按钮报错--bug修复--直接写接口里面

this.title "编辑";getAction(path.join(this.url.query, id)).then(res > {this.form res.data;}).catch(err > {this.form JSON.parse(JSON.stringify(this.defaultForm));this.$message.warning("获取数据失败&#xff01;");}).finally(() &g…

cesium billboard 设置距离控制可见度

核心代码如下 viewer.entities.add({position: Cesium.Cartesian3.fromDegrees(longitude, latitude, height),billboard: {image: /imgs/disease/1.png,scaleByDistance

!KMP算法完整教程

KMP算法完整教程 全称: Knuth_Morris_Pratt Algorithm(KMP算法) 类型: 高级检索算法 功能: 字符串匹配查找 提出者: D.E.Knuth(克努兹),J.H.Morris(莫瑞…

windows安装nvm

最近mac电脑坏了&#xff0c;需要送修&#xff0c;暂时切换到windows平台进行开发。 下面安装nvm。 先保证电脑没有安装node github下载地址&#xff1a; https://github.com/coreybutler/nvm-windows/releases 找到nvm-setup.zip 下载解压安装即可。 我都是使用的默认安…

工作215:打印出父子组件的this

父亲的this VueComponent {_uid: 1842, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …} $attrs: (...) $children: (2) [VueComponent, VueComponent] $createElement: ƒ (a, b, c, d) $el: div.container $listeners: (...) $options: {pare…

修改git bash主题配色和字体

这win下默认的主题令人窒息 打开git bash&#xff0c;使用命令cd ~然后用Atom命令打开文件.minttyrc.atom .minttyrc如果默认没有.minttyrc文件&#xff0c;自己新创建的也行。里面的内容填写如下&#xff1a;FontConsolas FontHeight14ForegroundColour131,148,150 Background…

jdk和maven配置

Java 1、首先准备一下文件 2、直接安装jdk&#xff0c;我是一直下一步&#xff0c;路径也是默认 3、开始配置环境变量 电脑--属性--高级--环境变量&#xff0c;新建系统变量 JAVA_HOME 和 CLASSPATH变量名&#xff1a;JAVA_HOME变量值&#xff1a;C:\Program Files\Java\…

java 数据结构详解,数组,集合,HashMap

数组的特性&#xff1a; 数组在内存中是一块连续的存储单元存储起来的&#xff0c;声明数组的时候我们必须声明其长度&#xff0c;这样才会为我们声明一个连续的存储区域。 这种存储方式造成我们想要往数组中存储一个数据时那么其后面各个元素都要往后移动&#xff0c;同样的&…

工作216:JS-JS创建数组的三种方法

隐式创建 var arr["Audi","BMW","Volvo"]; 直接实例化 var arrnew Array("Audi","BMW","Volvo"); 创建数组并给数组元素赋值 var arrnew Array(); arr[0]"Audi"; arr[1]"BMW"; arr[2]&q…

fs-extra导出换行txt文件

我的需求是要对数据库中的标注数据进行导出&#xff0c;用到fs-extra&#xff0c;下面附上demo代码 import * as path from "path"; import * as fse from "fs-extra"; const subdir path.join(__dirname, "subdir"); const pathExists await…

工作217:重置逻辑

第一步 按钮 <el-button click"resetQuery" icon"el-icon-refresh">重置</el-button> 第二步 resetQuery() {this.query {};this.list();},

四级菜单实现(Python)

menu_dict { 山东 : { 青岛 : { 四方:{兴隆路,平安路,杭州路}, 黄岛:{}, 崂山:{} }, 济南 : { 历城:{}, 槐荫:{}, 高新:{} }, }, 江苏 : { 苏…