android自定义view底部可上拉页面

来源:互联网 发布:陕师大网络远程教育 编辑:程序博客网 时间:2024/06/06 00:25

最近有需求,要写一个隐藏在底部,但是支持上下拉的菜单框,于是自己写了一个模型,刚好可以复习一下自定义view、事件分发及属性动画的知识。

废话不多说,先上效果图:

这里写图片描述

界面很简单,但是有几个难点(大神请略过):

  • 触摸区域处理;

  • 子控件手势拦截;

  • 配合属性动画的使用;

先看一下布局:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="300dp"    android:orientation="vertical"    android:background="#ffc2c2">    <RelativeLayout        android:id="@+id/rl_topbar"        android:layout_width="match_parent"        android:layout_height="50dp"        android:background="#a9a4a4">        <ImageView            android:id="@+id/iv_icon"            android:layout_width="30dp"            android:layout_height="30dp"           android:layout_centerVertical="true"            android:layout_marginLeft="15dp"            android:src="@drawable/arrow_top"            />    </RelativeLayout>    <LinearLayout        android:id="@+id/rl_checkbox"        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="1">        <CheckBox            android:layout_width="0dp"            android:layout_height="match_parent"            android:layout_weight="1"            android:text="开关一"            android:gravity="center"            android:focusable="false"/>        <CheckBox            android:layout_width="0dp"            android:layout_height="match_parent"            android:layout_weight="1"            android:text="开关一"            android:gravity="center"            android:focusable="false"/>        <CheckBox            android:layout_width="0dp"            android:layout_height="match_parent"            android:layout_weight="1"            android:text="开关一"            android:gravity="center"            android:focusable="false"/>    </LinearLayout></LinearLayout>

一个头部RelativeLayout+3个checkoutbox;

步骤一:定义一个view,我们暂且叫他BottomCard(底部卡片),让他隐藏在布局底部,漏出灰色的头部部分。

//很简单,在构造方法中引入xml布局, public BottomCard(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        View inflate = LayoutInflater.from(context).inflate(R.layout.layout_bottom_card, this, true);    }
//在视图发生改变时,也就是在屏幕上显示时,将BottomCard的marginbottom设置为负的(BottomCard的高度-头部的高度(50dp))。 @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        mBaseMarginBottom = -(getMeasuredHeight() - dipTopx(50));        setRootMarginBottom(getRootLayoutParams(), mBaseMarginBottom);    } private void setRootMarginBottom(RelativeLayout.LayoutParams layoutParams, int marginBottom) {        layoutParams.bottomMargin = marginBottom;        if (layoutParams.bottomMargin >= 0) {            layoutParams.bottomMargin = 0;            expandStatus = EXPANDED;        } else if (layoutParams.bottomMargin <= mBaseMarginBottom) {            layoutParams.bottomMargin = mBaseMarginBottom;            expandStatus = UNEXPANDED;        }        layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);        setLayoutParams(layoutParams);    }

步骤二:让bBttomCard随手势滑入滑出(其实就是根据手势在y轴上的移动而调整BottomMargin的值)。

 @Override    public boolean onTouchEvent(MotionEvent event) {        int distance = 0;        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                mDownY = event.getRawY();                break;            case MotionEvent.ACTION_MOVE:                float moveY = event.getRawY();                distance = (int) (moveY - mDownY);                moveRootMarginBottom(distance);                mDownY = moveY;                break;            case MotionEvent.ACTION_UP:                mDownY = 0;                break;        }        return true;    }

这里有一点要注意,获取y轴坐标时一定要用getRawY(),不能用getY(),因为前者是获取手指位置相对于屏幕的y轴坐标,而后者是获取手指位置相对于控件的y坐标,当手指滑出BottomCard的时y的值会不准确。

步骤三:处理子控件的事件分发

这个时候你会发现,BottomCard在拖拽头布局时没问题,但是拖拽BottomCard中头部下面的布局时会无效,这是因为里面的子控件抢了BottomCard的手势事件并且消费,所以不会传给BottomCard来处理,所以我们要对路面的子控件进行处理,只有当手指滑动时,将事件交给BottomCard处理,其他的如点击等由子控件自己来处理。

     /**     * 如果是滑动事件就将其拦截,交给onTouchEvent处理     */    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        if (ev.getAction() == MotionEvent.ACTION_DOWN) {            mDownY = ev.getRawY();        } else if (ev.getAction() == MotionEvent.ACTION_MOVE) {            if (Math.abs(ev.getRawY() - mDownY ) > 10) {                return true;            }        }         if (super.onInterceptTouchEvent(ev)) {            return true;        }        return false;    }

步骤四:当手势拖拽BottomCard向上或者向下滑动时松手,BottomCard会自动执行动画回到顶部或底部。

这里用的是属性动画ValueAnimator,因为ValueAnimator可以毁掉动画执行的整个过程的值,我们可以根据这个值来设置相对应的BottomMargin从而实现动画。

//当手指抬起时 case MotionEvent.ACTION_UP:                LayoutParams rootLayoutParams = getRootLayoutParams();                int currentBottomMargin = rootLayoutParams.bottomMargin;                if (expandStatus == UNEXPANDED && currentBottomMargin > mBaseMarginBottom) {                    startExpandAnim(currentBottomMargin);                    icon.setImageResource(R.drawable.arrow_bottom);                }                else if (expandStatus == EXPANDED && currentBottomMargin < 0) {                    startShrinkAnim(currentBottomMargin);                    icon.setImageResource(R.drawable.arrow_top);                }                mDownY = 0;                break; /**     * 开始展开动画     * @param currentBottomMargin  当前的BottomMargin     */    private void startExpandAnim(final int currentBottomMargin){        ValueAnimator anim = ValueAnimator.ofInt(currentBottomMargin , 0);        anim.setDuration(ANIM_DURATION);        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                int value = (Integer) animation.getAnimatedValue();                setRootMarginBottom(getRootLayoutParams() ,  value);            }        });        anim.start();    }    /**     * 开始收拢动画     * @param currentBottomMargin  当前的BottomMargin     */    private void startShrinkAnim(final int currentBottomMargin){        ValueAnimator anim = ValueAnimator.ofInt(currentBottomMargin , mBaseMarginBottom);        anim.setDuration(ANIM_DURATION);        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                int value = (Integer) animation.getAnimatedValue();                setRootMarginBottom(getRootLayoutParams() ,  value);            }        });        anim.start();    }

好的,其实这样就实现了整个功能,看似很简单,但是里面还是有很多知识点可以去学习的!

代码地址点这里!

0 0