自定义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>

Android 下拉式抽屉折叠动画相关推荐

  1. Android PopupWindow使用,下拉式PopupWindow,底部式PopupWindow

    1.实现方法1                                        仿微信盆友圈弹出点赞.评论     demo连接:android开发PopupWindow实现跟随试弹出框 ...

  2. android 4.4 禁止下拉,Android开发中禁止下拉式的实现技巧

    我们开发项目的时候,经常会看到禁止的情况,而Android开发中并没有直接调用的接口,下面是爱站技术频道小编就给大家介绍的Android开发中禁止下拉式的实现技巧,希望网友们喜欢! 分享给大家供大家参 ...

  3. 怎么在html5中制作下拉导航栏,在PPT中制作下拉式导航菜单效果的方法

    为了提高PPT演示文稿的观赏性,用户可能希望在PPT幻灯片中添加下拉式导航菜单效果,通过该导航菜单在不同幻灯片间进行导航(如图1所示),制作下拉式导航菜单的具体操作步骤如下. 图1 下拉式导航菜单 ( ...

  4. 1.2文字排版、颜色、表格、图像形状、Jumbotron、信息提示框、按钮、按钮组、徽章、加载效果、分页、列表组、卡片、下拉菜单、折叠

    Bootstrap 5 默认设置 Bootstrap 5 默认的 font-size 为 16px, line-height 为 1.5. 此外,所有的 <p> 元素 margin-top ...

  5. 【原创】窥视懒人的秘密---android下拉刷新开启手势的新纪元

    小飒的成长史原创作品:窥视懒人的秘密---android下拉刷新开启手势的新纪元转载请注明出处 **************************************************** ...

  6. Android下拉刷新开源库对比(转)

    安卓下拉刷新开源库对比 作者:desmond1121 目前仅比对github上star数>1500的下拉刷新开源库,在比较完成之后可能会加入其它有代表性的库. Repo Repo Owner S ...

  7. 开发一个出生年份的下拉选择框供用户选择_你的下拉式菜单设计对了吗?

    追波范儿(dribbbledesign)------------------------------------------- 下拉菜单主要有两种类型:1. 用于导航的下拉菜单:2. 用于表单的下拉菜 ...

  8. 开发一个出生年份的下拉选择框供用户选择_关于下拉式菜单,这一篇足够了

    下拉菜单主要有两种类型: 1.用于导航的下拉菜单: 2.用于表单的下拉菜单. 在本文中,我们将对以下内容进行介绍: 01 结构剖析 下拉菜单的解剖结构与文本输入字段的解剖结构非常相似. 02 下拉菜单 ...

  9. Android下拉刷新和上拉加载更多

    Android下拉刷新和上拉加载更多 下拉刷新 通过android系统提供的组件:SwipeRefreshLayout 一.基本使用 1 xml中 添加 SwipeRefreshLayout 组件 该 ...

最新文章

  1. 两分钟让你知道什么是“Java重载”
  2. 我是一个SDN控制器
  3. 科大讯飞语音合成api
  4. 【转】调用约定__cdecl、__stdcall和__fastcall的区别
  5. est.java 2 错误 找不到符号_找不到Cython/Python符号PyString\u Typ
  6. python大纲_python学习大纲
  7. 学会使用 GDB 调试 Go 代码
  8. Python机器学习:多项式回归与模型泛化006验证数据集与交叉验证
  9. 使用pdf.js在移动端预览pdf文档
  10. Android 8.0 VDEX机制简介
  11. oracle 取表字段,oracle 取多级的表字段
  12. 网络操作系统和分布式系统区别简介
  13. LoadRunner教程01:性能测试常见用语
  14. sql语句练习(1) 含问题,答案,数据库表,数据
  15. Android APP漏洞自动化静态扫描检测工具-Qark
  16. mac简体拼音打出来是英文_Mac OS X自带中文拼音输入法详解
  17. 赛迪报告:除了“会呼吸”的肺,这些也能用3D打印实现!
  18. 抢购茅台,618只能用这种方法
  19. 最全数据集网站汇总,绝对是一个金矿请查收!
  20. 每日计划(2)——大二

热门文章

  1. [html] HTML全局属性(global attribute)有哪些(包含H5)?
  2. [css] 在实际编写css中你有遇到过哪些浏览器兼容性的问题?怎么解决的?
  3. 前端学习(2286):react之无状态组件
  4. “睡服”面试官系列第二十篇之generator函数的异步应用(建议收藏学习)
  5. 前端学习(765):使用构造函数的原因
  6. 前端学习(693):for循环案例之求出偶数奇数之和
  7. 第一百五十三期: 云迁移可能失败的5种方式以及成功的5种方式
  8. java学习(176):第一个xml的编写
  9. Qt 给应用程序添加图标
  10. 华为IoT平台NB编解码插件开发详细教程【上篇】