viewgroup自定义组件 --- 侧滑组件

继承ViewGroup制作的自定义组件常常是一些布局或者组合组件:
和View的自定义组件类似,需要重写onMeause和onLayout测量组件的宽高和布局,因为viewGruop里面包含了子组件,所有在测量和定位的时候都是以子组件为中心进行测量和定位;不需要重写onDraw方法,因为viewGroup是一个容器类,我们只需要调用它的子组件的draw方法即可

还要注意事件分发时,要重写onInteceptTouchEvent,判断事件来临时需不需要向子组件传递下去;

下面以一下侧滑组件为例:

我们创建的组件由两个子组件组合而成,该组件可以左右侧滑,而里面的子组件“菜单栏”可以上下滑动;
1. 创建一个类,继承viewGroup,重写onMeasure和onLayout和构造器
    Measure  
        菜单栏( 宽度等于自己的真实宽度   高度和父类的控件相同)
        主界面  (宽高都和父控件相同)
    layout:
        菜单栏  (右边为 0 ,0  左边为 -自己宽度长 0 )
        主界面   (和父类的相同)

2. 监控事件分发:
    当我们在屏幕上滑动时,如何区分是左右滑动还是数值滑动,传递给viewgroup还是它的子类scrollView;
    需要一个判断用户滑动的长度,大于此长度就认为是滑动;此长度 == ViewConfiguration.get(context).getScaleTouchSlop
    用上面的长度,在OnInteceptTouchEvent的Move动作中,如果x轴上变化距离大于此变量并且y小于此长度就是水平滑动,返回true,阻断事件分发;反之fasle

3. 左右的滑动效果
    滑动效果的主要在onTouchEvent方法里面完成:
    switch(down  move up)三个动作,down里面记录初始的x坐标,move里面用当前坐标减去初始坐标就是x轴的位移;
    在move中,
        view.getScrollX()获取开始接触屏幕时的坐标,此坐标+移动的x轴坐标差就是移动后的坐标,判断此坐标如果大于0直接scrollTo(0,0);如果小于-菜单栏的,则移动到左边的菜单栏
        view.scrollTo(int x,int y)滑动到具体的xy坐标
        view.scrollBy(int dx,int dy); 滑动了多少距离  当前坐标加dx和dy

    慢慢滑动的效果:
        在我们抬起手指,查看当前的x轴坐标,如果小于菜单栏/2,往左慢慢滑动菜单栏,大于菜单栏/2,慢慢滑动右边
        借助Scroller辅助类帮助滑动:
            startScroll(int startX, int startY, int dx, int dy, int duration)    在规定的时间内完成滑动,模拟滑动,但此方法不会使view滑动

            computeScrollOffset()   如果模拟没完成,返回true,完成则false
            //重写view的方法,drawchild  -- >> child.draw() -->> computeScroll
            computeScroll()
            在此方法中,我们通过判断computeScrollOffset看模拟是否完成,如果没完成就滑动组件scrollTo(Scroller.getcurrX,0)然后调用              invalidate重画draw即可实现再次调用此方法,直到模拟完成
public class SildView extends ViewGroup {

    private static final String TAG = "SilDView";
    private Scroller scroller;
    private int lastX,currentX,lastY;
    private int mTouchSlip;
    private boolean viewFlag = false;       //viewFlag为界面标志

    //AttributeSet attr是啥意思
    public SildView(Context context,AttributeSet attrs) {
        super(context,attrs);
        // TODO Auto-generated constructor stub
        scroller = new Scroller(context);
        mTouchSlip = ViewConfiguration.get(context).getScaledTouchSlop();       //获取滑动识别的最小距离
        Log.i(TAG, ""+mTouchSlip);
    }

    /*测量布局*/
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //菜单布局
        View menuView = this.getChildAt(0);
        menuView.measure(menuView.getLayoutParams().width, heightMeasureSpec);
        //内容布局
        View contextView = this.getChildAt(1);
        contextView.measure(widthMeasureSpec, heightMeasureSpec);
    }
    /*布局下去*/
    @Override
    protected void onLayout(boolean arg0, int l, int t, int r, int b) {
        // TODO Auto-generated method stub
        View menuView = this.getChildAt(0);
        menuView.layout(-menuView.getMeasuredWidth(), 0, 0, b);

        View contextView = this.getChildAt(1);
        contextView.layout(0, 0, r, b);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        switch(event.getAction()){
            case MotionEvent.ACTION_DOWN:
                lastX = (int)event.getX();          //左后一次down动作的x
                break;

            case MotionEvent.ACTION_MOVE:
                currentX = (int)event.getX();           //滑动过程中
                int change = lastX - currentX;          //增量
                int scrollX = getScrollX() + change;        //getScrollX是获取屏幕左边的X坐标 ,加上增量就是滑动后的坐标  
                if(scrollX < -this.getChildAt(0).getWidth()){
                    //最左边了
                    scrollTo(-this.getChildAt(0).getWidth(),0);
                }else if(scrollX > 0){
                    //大于0直接滑到内容界面
                    scrollTo(0,0);
                }else{
                    scrollBy(change,0);
                }
                lastX = currentX;
                break;

            case MotionEvent.ACTION_UP:
                int midX = -this.getChildAt(0).getWidth()/2;
                int x = getScrollX();
                if(x < midX){
                    viewFlag = false;
                }else{
                    viewFlag = true;    
                }
                switchUI(viewFlag);
                break;
        }
        return true;
    }

    /**
     * 菜单界面和功能界面的切换
     * true为功能界面
     * false为菜单界面
     * 注:滑动是从当前界面滑动到目标位置,而不是直接跳转到目标position
     */
    public void switchUI(boolean flagUI){
        int dx = 0;
        int currentX = getScrollX();
        if(flagUI){
            dx = 0 - currentX;
        }else{
            dx = -this.getChildAt(0).getWidth() - currentX;
        }

        //模拟滑动过程

        scroller.startScroll(currentX, 0, dx, 0,Math.abs(dx)*5);

        invalidate();           //此函数会经过viewGroup下面的drawChild()-->child.draw()-->computeScroll()最终完成滑动并显示出来
    }

    @Override
    public void computeScroll() {
        // TODO Auto-generated method stub
        if(scroller.computeScrollOffset()){         //true表示正在模拟数据
            //更新左X坐标
            scrollTo(scroller.getCurrX(),0);

            invalidate();   
        }
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // TODO Auto-generated method stub
        switch(ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                lastX = (int)ev.getX();
                lastY = (int)ev.getY();
                Log.i(TAG, "ACTION_DOWN: "+ lastX);
                break;
            case MotionEvent.ACTION_MOVE:
                int currentX = (int)ev.getX();
                int currentY = (int)ev.getY();
                int diffX = lastX - currentX;
                int diffY = lastY - currentY;
                diffX = Math.abs(diffX);
                diffY = Math.abs(diffY);
                Log.i(TAG, "ACTION_MOVE: "+ diffX);
                /*****
                 * 必须加一个竖直判断,因为上下滑动的平率很容易让x之间的距离大于设定的距离,
                 * 所以,缓慢上下滑动还能够在scrollView组件里面滑动,速度稍微快一点就转到
                 * viewgroup的左右滑动当中去了
                 */
                if(diffX > mTouchSlip && diffY < mTouchSlip){   // 
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                Log.i(TAG, "ACTION_UP: ");
                break;
        }

        return super.onInterceptTouchEvent(ev);
    }

    //功能界面是否打开
    public boolean isShown(){
        return viewFlag;
    }

    //打开功能界面
    public void showView(){
        switchUI(true);
        viewFlag = true;
    }

    //掩藏功能界面
    public void hideView(){
        switchUI(false);
        viewFlag = false;
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

帅气好男人_Jack

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值