QQ侧滑面板特效的实现

来源:互联网 发布:怎样兼职网络授课教师 编辑:程序博客网 时间:2024/04/29 16:13

ViewDragHelper的介绍

  要实现和QQ5.0侧滑的特效,需要借助谷歌在2013年I/O大会上发布的ViewDragHelper类,提供这个类目的就是为了解决拖拽滑动问题。
  使用v4包中的ViewDragHelper为了兼容低版本,所以在创建ViewDragHelper对象时如果找不到ViewDragHelper这个类,可以从sdk中拷贝出最新的v4包覆盖lib目录中的V4包即可。

  • 效果图:

效果图

/** * Created by Alan on 2016/8/18. * QQ5.0侧滑菜单特效实现 */ //自定义一个类继承FrameLayoutpublic class DragLayout extends FrameLayout {/** * ViewDragHelper: * v4包中的api,2013年谷歌I/O大会上提出,用于解决控件拖动问题。 */   private ViewDragHelper mDragHelper;   private View mMenuView;//菜单   private View mMainView;//主界面   private int mWidth;//控件宽度   private int mHeight;///控件高度   private int mMRange;//侧拉菜单打开的最大宽度   public DragLayout(Context context) {       super(context);       init();   }   public DragLayout(Context context, AttributeSet attrs) {       super(context, attrs);       init();   }   public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) {       super(context, attrs, defStyleAttr);       init();   }   //4.处理Callback接口中的回调方法   ViewDragHelper.Callback mCallack = new ViewDragHelper.Callback() {       /**        * 根据返回值决定子控件是否可以拖动, true表示可以拖动        * @param child 被捕获的子控件        * @param pointerId        * @return        */       @Override       public boolean tryCaptureView(View child, int pointerId) {           return true;       }       /**当开始拖动时回调*/       @Override       public void onViewCaptured(View capturedChild, int activePointerId) {           System.out.println("onViewCaptured -- 拖动");       }       /**        * 根据返回值决定子控件将要显示的位置        * @param child        * @param left        * @param dx        * @return        */       @Override       public int clampViewPositionHorizontal(View child, int left, int dx) {           if (child == mMainView) {               left = fixDragLeft(left);           }           return left;       }       /**        * 获取水平方向拖动的最大范围        * 注意:此方法半不会真正限制子控件的滑动范围,        * 如果要实现拖动效果,应该返回大于0的值        * @param child        * @return mMRange 范围        */       @Override       public int getViewHorizontalDragRange(View child) {           return mMRange;       }       /**        * 当拖动结束松开手时回调, 需要在此方法中设置菜单为打开或关闭状态        * 侧滑菜单何时打开和关闭?        * 打开的情况:     *   1) 滑动速度大于0        *   2)  速度是0且位置大于拖拽范围的一半        * 关闭的情况:其他的情况都是关闭        *         * @param releasedChild        * @param xvel 松开手时水平方向的速度,像素/秒,往右为正        * @param yvel        */       @Override       public void onViewReleased(View releasedChild, float xvel, float yvel) {           super.onViewReleased(releasedChild, xvel, yvel);           // 松开手时打开或关闭侧滑菜单           if (xvel > 0) {               open();           } else if (xvel == 0 && mMainView.getLeft() > mMRange / 2) {               open();           } else {               close();           }       }       /**        * 当前界面发生改变回调        * 处理逻辑: 关联滑动, 事件监听, 伴随动画        * @param changedView        * @param left        * @param top        * @param dx        * @param dy        */       @Override       public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {           //  super.onViewPositionChanged(changedView, left, top, dx, dy);           // 当滑动菜单时,同时滑动主界面           if (changedView == mMenuView) {               // 保持菜单界面位置不变               mMenuView.layout(0, 0, mWidth, mHeight);               // 关联滑动,菜单滑动多少,主界面也滑动多少               int newLeft = mMainView.getLeft() + dx;               newLeft = fixDragLeft(newLeft);               mMainView.layout(newLeft, 0, newLeft + mWidth, mHeight);           }           // 监听打开关闭拖动的状态           listenDragState();           // 伴随动画           animateChildren();       }   }; /**    * 伴随动画    */   private void animateChildren() {       float percent = ((float) mMainView.getLeft()) / mMRange;       //菜单界面       // 平移: [-mWidth / 2, 0]       // 透明度变化: [0.4, 1]       // 缩放: [0.5f, 1]       mMenuView.setTranslationX(evaluate(-mWidth/2,0,percent));       mMenuView.setAlpha(evaluate(0.4f, 1, percent));       mMenuView.setScaleX(evaluate(0.5f, 1f, percent));       mMenuView.setScaleY(evaluate(0.5f, 1f, percent));       // 主界面:  缩放[1, 0.8f]       mMainView.setScaleX(evaluate(1f, 0.8f, percent));       mMainView.setScaleY(evaluate(1f, 0.8f, percent));       Drawable drawable = getBackground();       if (drawable != null) {           int color = (int) evaluateColor(percent, Color.BLACK, Color.TRANSPARENT);           drawable.setColorFilter(color , PorterDuff.Mode.SRC_OVER);       }   }   /**    * 估值器    */   public float evaluate(float start,float end,float percent){       // 中间值 = 开始值 + (结束值 - 开始值) * 百分比       return start+(end - start)*percent;   }   public Object evaluateColor(float fraction, Object startValue, Object endValue) {       int startInt = (Integer) startValue;       int startA = (startInt >> 24) & 0xff;       int startR = (startInt >> 16) & 0xff;       int startG = (startInt >> 8) & 0xff;       int startB = startInt & 0xff;       int endInt = (Integer) endValue;       int endA = (endInt >> 24) & 0xff;       int endR = (endInt >> 16) & 0xff;       int endG = (endInt >> 8) & 0xff;       int endB = endInt & 0xff;       return (int)((startA + (int)(fraction * (endA - startA))) << 24) |               (int)((startR + (int)(fraction * (endR - startR))) << 16) |               (int)((startG + (int)(fraction * (endG - startG))) << 8) |               (int)((startB + (int)(fraction * (endB - startB))));   }//------------侧滑菜单事件监听------以下------------   /**    * 事件监听(打开,关闭,拖动)    */   private void listenDragState() {       int left = mMainView.getLeft();       if(left == 0){           mCurrentStatus = DragStatus.CLOSE;//关闭       }else if(left == mMRange){           mCurrentStatus = DragStatus.OPEN;//打开       }else{           mCurrentStatus = DragStatus.DRAGGING;//拖动       }       //当事件发生时,回调监听器中的方法       if(mOnDragListener != null){           if(mCurrentStatus == DragStatus.OPEN){               //回调监听器中相应的方法           }else if(mCurrentStatus == DragStatus.CLOSE){               mOnDragListener.onClose();           }else{               float v = ((float) mMainView.getLeft()) / mMRange;               mOnDragListener.onDraging(v);           }       }   }   public boolean isOpen() {       return  mCurrentStatus == DragStatus.OPEN;   }   //定义一个枚举   public enum DragStatus{       OPEN,CLOSE,DRAGGING   }   private DragStatus mCurrentStatus = DragStatus.CLOSE;//默认状态   /**    * 提供状态获取    * @return    */   public DragStatus getCurrentStatus() {       return mCurrentStatus;   }   /**    * 1.自定义的监听器    */   private OnDragListener mOnDragListener;   public interface OnDragListener {       void onOpen();       void onClose();       void onDraging(float value);   }   /**    * 给外部提供设置监听器的方法    * @param onDragListener    */   public void setOnDragListener(OnDragListener onDragListener){       mOnDragListener = onDragListener;   }   //--------------事件监听-----以上---------------------------   private int fixDragLeft(int left) {       if (left < 0) {           left = 0;       } else if (left > mMRange) {           left = mMRange;       }       return left;   }   /**    * 关闭菜单    */   private void close() {       mMenuView.layout(0, 0, mWidth, mHeight);       //  mMainView.layout(0,0,mWidth,mHeight);       //主界面平滑滚动       mDragHelper.smoothSlideViewTo(mMainView, 0, 0);       //刷新 调用顺序Invalidate --> onDraw() --> computeScroll(重写,在此方法里面需要继续刷新)       ViewCompat.postInvalidateOnAnimation(this);   }   /**    * 打开菜单    */   private void open() {       mMenuView.layout(0, 0, mWidth, mHeight);       //mMainView.layout(mMRange,0,mWidth+mMRange,mHeight);       // 平滑滚动到某一位置: 第一步       mDragHelper.smoothSlideViewTo(mMainView, mMRange, 0);       //刷新 调用顺序Invalidate --> onDraw() --> computeScroll(重写,在此方法里面需要继续刷新)       ViewCompat.postInvalidateOnAnimation(this);   }   @Override   public void computeScroll() {       super.computeScroll();       //第二步       //如果没有滑动到指定位置,需要继续刷新       if (mDragHelper.continueSettling(true)) {           ViewCompat.postInvalidateOnAnimation(this);       }   }   private void init() {       //1.创建ViewDragHelper对象       float sensitivity = 1.0f;//敏感值,数值越大则越容易滑动菜单       mDragHelper = ViewDragHelper.create(this, sensitivity, mCallack);   }   @Override   public boolean onInterceptTouchEvent(MotionEvent ev) {       // 2. 让ViewDragHelper决定是否拦截事件       return mDragHelper.shouldInterceptTouchEvent(ev);   }   @Override   public boolean onTouchEvent(MotionEvent event) {       // 3. 把事件交给ViewDragHelper处理       mDragHelper.processTouchEvent(event);       //按下时需要返回true,否则无法接收到后续的move和up事件       if (event.getAction() == MotionEvent.ACTION_DOWN) {           return true;       }       return super.onTouchEvent(event);   }   /**    * 当填充结束后回调此方法,注意:不能在此方法获取控件的宽高    */   @Override   protected void onFinishInflate() {       super.onFinishInflate();       // 健壮性处理       if (getChildCount() < 2) {           throw new IllegalStateException("DragLayout至少要有两个子控件");       }       mMenuView = getChildAt(0);       mMainView = getChildAt(1);   }   /**    * 执行此方法时,已经调用完了onMeasure,所以可以获取控件宽高    *    * @param w    * @param h    * @param oldw    * @param oldh    */   @Override   protected void onSizeChanged(int w, int h, int oldw, int oldh) {       super.onSizeChanged(w, h, oldw, oldh);       //获取控件的宽高       mWidth = getMeasuredWidth();       mHeight = getMeasuredHeight();       //设置主界面只能滑到 %60;       mMRange = (int) (mWidth * 0.6f);   }}

头像左右晃动的实现

监听当侧滑面板关闭时通过属性动画实现左右晃动的动画。

mDragLayout.setOnDragListener(new OnDragListener() {        @Override    public void open() {    }       @Override    public void onDragging(float percent) {        iv_header.setAlpha(1 - percent); // 拖动时设置透明度    }       @Override    public void close() {        showToast("close");         TranslateAnimation animation = new TranslateAnimation(0, 10, 0, 0);    animation.setDuration(100);    animation.setRepeatCount(4);    iv_header.startAnimation(animation);    }});

问题解决

处理菜单打开后主界面列表仍可滑动的问题

自定义主界面根布局控件,并重写拦截方法,当侧滑菜单打开时返回true拦截事件:

    public class MyLinearLayout extends LinearLayout {        private DragLayout dragLayout;        public MyLinearLayout(Context context) {            super(context);        }        public MyLinearLayout(Context context, AttributeSet attrs) {            super(context, attrs);        }        public void setDragLayout(DragLayout dragLayout) {            this.dragLayout = dragLayout;        }           @Override        public boolean onInterceptTouchEvent(MotionEvent ev) {            // 如果侧菜单打开,则拦截事件,不让列表点击或者滚动            if (dragLayout.isOpen()) {                return true;            }            return super.onInterceptTouchEvent(ev);        }         @Override        public boolean onTouchEvent(MotionEvent event) {            //如果菜单打开,不允许触摸主界面时,菜单列表滑动            if(layout!= null && layout.isOpen()){                return true;            }            return super.onTouchEvent(event);        }    }

完整源码:点击下载

0 0
原创粉丝点击