android收集自定义控件Android知识

Android 学习笔记 自定义控件之侧滑菜单

2017-02-24  本文已影响604人  maoqitian


下里面来一发控件效果图:

主界面(侧滑菜单关闭).png 主界面和侧滑菜单(侧滑菜单打开).png

看完效果,开始撸这个控件:

/**
 * Created by 毛麒添 on 2017/2/23 0023.
 * 自定义侧滑菜单控件
 */

public class MySlideMenu extends FrameLayout {

    private View leftMenu;//左边栏对象
    private View mainMenu;//主界面对象
    private FloatEvaluator floatEvaluator;//浮点数计算器
    private IntEvaluator intEvaluator;//整数计算器
    private ViewDragHelper viewDragHelper;

    public MySlideMenu(Context context) {
        super(context);
        init();
    }

    public MySlideMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public MySlideMenu(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init(){
        viewDragHelper=ViewDragHelper.create(this,callback);
        floatEvaluator=new FloatEvaluator();
        intEvaluator=new IntEvaluator();
    }

@Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        //检测控件异常
        if(getChildCount()!=2){
            throw new IllegalArgumentException("MySlideMenu only have two childView!");
        }
        leftMenu = getChildAt(0);
        mainMenu = getChildAt(1);
    }

 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //让viewDragHelper帮助我们判断是否拦截
        return viewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //让触摸事件交给viewDragHelper来处理
        viewDragHelper.processTouchEvent(event);
        return true;
    }

 private ViewDragHelper.Callback  callback=new ViewDragHelper.Callback() {
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return false;

        }

        @Override
        public int getViewHorizontalDragRange(View child) {
            return super.getViewHorizontalDragRange(child);
        }

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            return super.clampViewPositionHorizontal(child, left, dx);
        }

        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
        }
    };
}
    private int width;//控件宽度
    private float dragRange;//拖拽范围
    
    /**
     * 该方法在onMeasure执行完成后执行,可以在该方法中初始化自己和子View的宽高
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = getMeasuredWidth();
        dragRange = width*0.6f;
    }

   private ViewDragHelper.Callback callback=new ViewDragHelper.Callback() {
      @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return child==leftMenu||child==mainMenu;
        }
        @Override
        public int getViewHorizontalDragRange(View child) {
            return (int) dragRange;
        }
};
private ViewDragHelper.Callback callback=new ViewDragHelper.Callback() {
 @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if(child==mainMenu){
                if(left<0) left=0;//限制左边界
                if(left>dragRange)left= (int) dragRange;//限制右边界
            }
            return left;
        }

      
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
           if(changedView==leftMenu){
               //固定侧滑菜单
               leftMenu.layout(0,0,mainMenu.getMeasuredWidth(),mainMenu.getMeasuredHeight());
               int newLeft=mainMenu.getLeft()+dx;
               if(newLeft<0) newLeft=0;//限制左边界
               if(newLeft>dragRange) newLeft= (int) dragRange;//限制右边界
               //两个菜单一起伴随滑动
               mainMenu.layout(newLeft,mainMenu.getTop()+dy,mainMenu.getRight()+dx,mainMenu.getBottom()+dy);
           }
};
 private ViewDragHelper.Callback callback=new ViewDragHelper.Callback() {
@Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
           if(mainMenu.getLeft()<dragRange/2){//在拖拽范围的左边,关闭
               close();
           }else {//在拖拽范围的右边,打开
               open();
           }
       }
};
//侧滑面板打开
    public void open() {
        viewDragHelper.smoothSlideViewTo(mainMenu, (int) dragRange,mainMenu.getTop());
        ViewCompat.postInvalidateOnAnimation(MySlideMenu.this);//刷新
    }
    //侧滑面板关闭
    public void close() {
        viewDragHelper.smoothSlideViewTo(mainMenu,0,mainMenu.getTop());
        ViewCompat.postInvalidateOnAnimation(MySlideMenu.this);//刷新
    }

@Override
    public void computeScroll() {
        if(viewDragHelper.continueSettling(true)){
            ViewCompat.postInvalidateOnAnimation(MySlideMenu.this);//刷新
        }
    }
 @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
           if(changedView==leftMenu){
               //固定侧滑菜单
               leftMenu.layout(0,0,mainMenu.getMeasuredWidth(),mainMenu.getMeasuredHeight());
               int newLeft=mainMenu.getLeft()+dx;
               if(newLeft<0) newLeft=0;//限制左边界
               if(newLeft>dragRange) newLeft= (int) dragRange;//限制右边界
               //两个菜单一起伴随滑动
               mainMenu.layout(newLeft,mainMenu.getTop()+dy,mainMenu.getRight()+dx,mainMenu.getBottom()+dy);
           }
            //计算滑动百分比
            float fraction=mainMenu.getLeft()/dragRange;
            //执行伴随动画
            executeAnim(fraction);
}

private void executeAnim(float fraction) {
        //移动侧边栏
        ViewHelper.setTranslationX(leftMenu,intEvaluator.evaluate(fraction,-leftMenu.getMeasuredWidth()/2,0));
        //放大侧边栏
        ViewHelper.setScaleX(leftMenu,floatEvaluator.evaluate(fraction,0.5f,1f));
        ViewHelper.setScaleY(leftMenu,floatEvaluator.evaluate(fraction,0.5f,1f));
        //改变侧边栏的透明度
        ViewHelper.setAlpha(leftMenu,floatEvaluator.evaluate(fraction,0.3f,1f));
        //给侧边栏背景添加黑色遮罩效果
        getBackground().setColorFilter((Integer) ColorUtil.evaluateColor(fraction, Color.BLACK,Color.TRANSPARENT), PorterDuff.Mode.SRC_OVER);
    }
ViewHelper所在兼容包.png
/**
     * 设置外部监听回调
     */
    private onDragStateChangeListener listener;

    public void setOnDragStateChangeListener(onDragStateChangeListener listener){
        this.listener=listener;
    }

    public interface onDragStateChangeListener{
        /**
         * 侧滑菜单打开
         */
        void onOpen();

        /**
         * 侧滑菜单处于关闭
         */
        void onClose();

        /**
         *正在拖拽,将此时的百分比随时暴露给调用者
         */
        void Draging(float fraction);
    }
private DragState currentState=DragState.STATE_CLOSE;//默认是关闭状态

    /**
     * 枚举侧滑菜单的开关状态
     */
    public enum DragState{
        STATE_OPEN,STATE_CLOSE
    }

/**
     * 获取侧滑菜单状态
     */
    public DragState getDragState(){
        return currentState;
    }

@Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
           if(changedView==leftMenu){
               //固定侧滑菜单
               leftMenu.layout(0,0,mainMenu.getMeasuredWidth(),mainMenu.getMeasuredHeight());
               int newLeft=mainMenu.getLeft()+dx;
               if(newLeft<0) newLeft=0;//限制左边界
               if(newLeft>dragRange) newLeft= (int) dragRange;//限制右边界
               //两个菜单一起伴随滑动
               mainMenu.layout(newLeft,mainMenu.getTop()+dy,mainMenu.getRight()+dx,mainMenu.getBottom()+dy);
           }
            //计算滑动百分比
            float fraction=mainMenu.getLeft()/dragRange;
            //执行伴随动画
            executeAnim(fraction);
            //根据百分比来值来确定侧滑菜单是打开还是关闭
            if(fraction==0&&currentState!=DragState.STATE_CLOSE){//如果百分比是0,且当前状态不是关闭
                currentState=DragState.STATE_CLOSE;
                //调用回调方法
                listener.onClose();
            }else if(fraction==1&&currentState!=DragState.STATE_OPEN){
                currentState=DragState.STATE_OPEN;
                //调用回调方法
                listener.onOpen();
            }
            if(listener!=null){
                //只要listener存在,就将百分比暴露出去
                listener.Draging(fraction);
            }
        }

打这里,侧滑菜单控件类的大部分已经完成,但是,还差一个小问题,那就是这时候的侧滑菜单拖动打开或者关闭一定要大于或者小于拖动范围的二分之一才能打开或者关闭,而成熟应用的的侧滑菜单都是手指一划就可以打开或者关闭,其实这是根据手指滑动的速度来做,上面介绍方法的时候已介绍过onViewReleased(View releasedChild, float xvel, float yvel)方法可以获取X轴和Y轴的速度,所以将其改造为:

@Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
           if(mainMenu.getLeft()<dragRange/2){//在拖拽范围的左边,关闭
               close();
           }else {//在拖拽范围的右边,打开
               open();
           }
            //当用户稍微滑动一下,根据X轴方向的速度来打开或者关闭侧滑菜单
            if(xvel>200&&currentState!=DragState.STATE_OPEN){
                open();
            }else if(xvel<-200&&currentState!=DragState.STATE_CLOSE){
                close();
            }
       }

到此,整个自定义控件侧滑菜单类已经完成,但是还有一些小瑕疵,当侧滑面侧滑面板是打开状态下,发现主界面的ListView还是可以滑动,也就是说侧滑菜单打开的时候主界面的点击事件没有被拦截,而主界面子View我使用根布局是LinerLayout,所有可以自定义一个LinerLayout,让其在侧滑菜单是打开的状态下拦截事件并消费掉就可以了(事件分发拦截机制这里不多说),并且在打开的状态下点击主界面就可以关闭侧滑菜单,逻辑很简单:

/**
 * Created by 毛麒添 on 2017/2/24 0024.
 * 当自定的侧滑菜单打开的时候,右侧的主界面菜单不应该能滑动,
 * 自定义一个LinearLayout拦截并消费该触摸事件
 */

public class MyLinerLayout extends LinearLayout {

    private MySlideMenu mySlideMenu;

    public MyLinerLayout(Context context) {
        super(context);
    }

    public MyLinerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyLinerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setSlideMenu(MySlideMenu mySlideMenu){
        this.mySlideMenu=mySlideMenu;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if(mySlideMenu!=null&& mySlideMenu.getDragState()== MySlideMenu.DragState.STATE_OPEN){
            //如果该侧滑面板是打开,则拦截消费触摸事件
           return true;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(mySlideMenu!=null&& mySlideMenu.getDragState()== MySlideMenu.DragState.STATE_OPEN){
            if(event.getAction()==MotionEvent.ACTION_UP){//在侧滑面板打开的状态时候点一下主界面应该关闭侧滑面板
                mySlideMenu.close();
            }
            //如果该侧滑面板是打开,则拦截消费触摸事件
            return true;
        }
        return super.onTouchEvent(event);
    }
}

好了,扯了一大堆,总算了是把这个自定义控件完成了,布局和MainActivity比较简单,这里就不贴了。
整体一步一步走下来,还是能对技术有不少提升的。如果有错误,希望大家可以给我提出来,大家一起学习进步。

源码下载地址:https://github.com/maoqitian/MySlideMenu

上一篇 下一篇

猜你喜欢

热点阅读