一个QQ消息拖拽气泡实现源码分析

最近想做类似QQ消息拖拽气泡的效果,在GitHub上找到一个实现的不错的DragPointView,看到源码感觉不错就来分析一下。
代码比较简单,主要有DragPointView,DragPointViewWindow,DragViewHelper三个比较重要的类。

  • DragPointView 原始的气泡
  • DragPointViewWindow 滑动时的气泡,显示时隐藏原始的气泡
  • DragViewHelper 拦截事件,创建DragPointViewWindow

其中DragPointView,DragPointViewWindow继承了 AbsDragPointView(继承TextView),是原始气泡的View和拖拽气泡的View。DragPointView依赖一个DragViewHelper,DragViewHelper负责拦截DragPointView触摸事件,并且添加一个全屏window来展示拖拽的气泡DragPointViewWindow。
为什么不是直接滑动DragPointView而要做一个DragPointView的复制的一份DragPointViewWindow来滑动呢?因为原始的DragPointView只能在自己的父控件内滑动,而我们要实现的QQ消息拖拽气泡在滑动时是全屏滑动的,所以拖动气泡是隐藏原始气泡,滑动复制了他的bitmap的DragPointViewWindow。

DragPointView
    @Override
    public void startRemove() {
        dragViewHelper.startRemove();
    }
    private void init() {
        dragViewHelper = new DragViewHelper(getContext(),this);
    }

代码很简单,有自定义view属性,创建了一个DragViewHelper。我们看看DragViewHelper

 public DragViewHelper(Context context, final DragPointView originView) {
        this.context = context;
        this.originView = originView;
        this.originView.setOnTouchListener(this);//拦截了DragPointView 的事件
        animRunnable = new Runnable() {
            @Override
            public void run() {
                windowView.startRemove();
            }
        };
    }

 @Override
    public boolean onTouch(View v, MotionEvent event) {
        int action = event.getAction() & MotionEvent.ACTION_MASK;
        if (action == MotionEvent.ACTION_DOWN) {
            ViewParent parent = v.getParent();
            if (parent == null) {
                return false;
            }
            parent.requestDisallowInterceptTouchEvent(true);//屏蔽父元素onInterceptTouchEvent判断,说明以后的ACTION_MOVE肯定能传过来
            addViewToWindow();//ACTION_DOWN就创建DragPointViewWindow
        }
        return windowView.onTouchEvent(event);//又把事件传递给了DragPointViewWindow
    }

    public void addViewToWindow() {
        if (windowManager == null) {
            windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        }
        if (windowView == null) {
            createWindowView();//创建DragPointViewWindow
        }
        if (windowParams == null ||
                layoutParams == null) {
            initParams();// 初始化windowParams ,layoutParams 
        }
        if (container == null) {
            container = new FrameLayout(context);// 全屏的FrameLayout
            container.setClipChildren(false);//false允许子控件超出父布局绘制,这个很有用
            container.setClipToPadding(false);//false允许子控件在父布局Padding内绘制
            windowView.setLayoutParams(layoutParams);
            container.addView(windowView, layoutParams);//DragPointViewWindow加入到FrameLayout
        }
        int[] ps = new int[2];
        originView.getLocationInWindow(ps);//控件在其父窗口中的坐标位置,getLocationOnScreen整个屏幕上
        layoutParams.setMargins(ps[0], ps[1], 0, 0);
        layoutParams.width = originView.getWidth();
        layoutParams.height = originView.getHeight();
        windowView.setOrigView(originView);
        originView.setDrawingCacheEnabled(true);
        Bitmap bitmap = Bitmap.createBitmap(originView.getDrawingCache());//原本气泡的Bitmap
        originView.setDrawingCacheEnabled(false);
        windowView.setOrigBitmap(bitmap);
        onPointDragListener = originView.getOnPointDragListener();
        windowView.setVisibility(View.VISIBLE);
        if(container.getParent() != null)
            windowManager.removeView(container);
        windowManager.addView(container, windowParams);// 加入到PhoneWindow
        originView.setVisibility(View.INVISIBLE);
    }

    private void createWindowView() {
        windowView = new DragPointViewWindow(context);
        windowView.setCanDrag(originView.isCanDrag());
        windowView.setCenterMinRatio(originView.getCenterMinRatio());
        windowView.setCenterRadius(originView.getCenterRadius());
        windowView.setColorStretching(originView.getColorStretching());
        windowView.setDragRadius(originView.getDragRadius());
        windowView.setClearSign(originView.getClearSign());
        windowView.setSign(originView.getSign());
        windowView.setMaxDragLength(originView.getMaxDragLength());
        windowView.setRecoveryAnimBounce(originView.getRecoveryAnimBounce());
        windowView.setRecoveryAnimDuration(originView.getRecoveryAnimDuration());
        windowView.setRecoveryAnimInterpolator(originView.getRecoveryAnimInterpolator());
        if (originView.getRemoveAnim() != null)
            windowView.setRemoveAnim(originView.getRemoveAnim().setView(windowView));
        windowView.setOnPointDragListener(this);
    }

    private void initParams() {
        windowParams = new WindowManager.LayoutParams();// 全屏的FrameLayout的布局属性
        windowParams.gravity = Gravity.LEFT | Gravity.TOP;
        windowParams.format = PixelFormat.TRANSLUCENT;
        windowParams.type = WindowManager.LayoutParams.TYPE_TOAST;
        windowParams.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
        windowParams.width = WindowManager.LayoutParams.MATCH_PARENT;
        windowParams.height = WindowManager.LayoutParams.MATCH_PARENT;
        // DragPointViewWindow在FrameLayout的布局属性
        layoutParams = new FrameLayout.LayoutParams(originView.getWidth(), originView.getHeight());
    }
   

我们看到,当有MotionEvent.ACTION_DOWN事件时,创建了一个显示原始气泡的DragPointViewWindow来代替进行全屏的滑动。这个DragPointViewWindow被添加到一个全屏的FrameLayout里面。这个FrameLayout是通过WindowManager直接添加到activity的PhoneWindow里面的。

DragPointViewWindow

DragPointViewWindow比较重要,要拦截滑动事件,绘制利用贝塞尔曲线做出的拖拽效果。

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!canDrag || ClearViewHelper.getInstance().isClearSigning(sign)
                || (mRecoveryAnim != null && mRecoveryAnim.isRunning())
                || (mRemoveAnim != null && mRemoveAnim.isRunning())) {
            return super.onTouchEvent(event);
        }
        if (mRecoveryAnim == null || !mRecoveryAnim.isRunning()) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    if(getParent() != null)
                    getParent().requestDisallowInterceptTouchEvent(true);//屏蔽父元素onInterceptTouchEvent判断,说明以后的ACTION_MOVE肯定能传过来
                    downX = event.getRawX();//初始点击位置,用来计算拖动效果
                    downY = event.getRawY();
                    isInCircle = true;
                    postInvalidate();//此时原始气泡已经不可见,绘制自己
                    break;
                case MotionEvent.ACTION_MOVE:
                    float dx = (int) event.getRawX() - downX;
                    float dy = (int) event.getRawY() - downY;
                    mCenterCircle.x = mWidthHalf - dx;
                    mCenterCircle.y = mHeightHalf - dy;
                    mDistanceCircles = MathUtils.getDistance(mCenterCircle, mDragCircle);
                    mIsDragOut = mIsDragOut ? mIsDragOut : mDistanceCircles > mMaxDragLength;
                    setX(origX + dx);
                    setY(origY + dy);
                    postInvalidate();
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    getParent().requestDisallowInterceptTouchEvent(false);
                    upX = getX();
                    upY = getY();
                    upAndCancelEvent();
                    break;
            }
        }
        return true;
    }

你可能感兴趣的:(一个QQ消息拖拽气泡实现源码分析)