美文网首页
自定义View-31 仿QQ消息未读拖拽回弹和爆炸效果

自定义View-31 仿QQ消息未读拖拽回弹和爆炸效果

作者: zsj1225 | 来源:发表于2018-07-22 11:04 被阅读98次

    1.要模仿的效果

    hnwuq-656lw.gif

    2 实现思路

    2.1 怎么才能够把一个View拖动到状态栏上面

    我们要把这个View放在 WindowManager 上面拖动,原来的View还是在原来位置只是隐藏,
    拖动的时候其实是新建了一个View,复制一张图片在WindowManager上面拖动

    2.2 回弹就是不断改变拖拽圆的位置

    2.3 爆炸效果是一个帧动画

    3 代码实现

    public class BubbleMessageTouchListener implements View.OnTouchListener, MessageBubbleView.MessageBubbleListener {
        private final Context mContext;
        private final WindowManager mWindowManager;
        private final WindowManager.LayoutParams mParams;
        private View mView;
        private final MessageBubbleView mMessageBubbleView;
        private BubbleDisappearListener mBubbleTouchListener;
        private FrameLayout mFrameLayout;
        private ImageView mBombImage;
    
        BubbleMessageTouchListener(View view, Context context, BubbleDisappearListener bubbleTouchListener) {
            mView = view;
            this.mContext = context;
            mMessageBubbleView = new MessageBubbleView(view.getContext());
            mBubbleTouchListener = bubbleTouchListener;
            mMessageBubbleView.setMessageBubbleListener(this);
            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            mParams = new WindowManager.LayoutParams();
            // 背景要透明
            mParams.format = PixelFormat.TRANSPARENT;
    
            mFrameLayout = new FrameLayout(context);
            mBombImage = new ImageView(context);
            mFrameLayout.addView(mBombImage);
    
            FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mBombImage.getLayoutParams();
            layoutParams.width = FrameLayout.LayoutParams.WRAP_CONTENT;
            layoutParams.height = FrameLayout.LayoutParams.WRAP_CONTENT;
            mBombImage.setLayoutParams(layoutParams);
        }
    
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    // 要在WindowManager上面搞一个View ,上一节写好的贝塞尔的View
                    mWindowManager.addView(mMessageBubbleView, mParams);
                    //获取当前View的bitmap
                    int[] location = new int[2];
                    mView.getLocationOnScreen(location);
                    Bitmap bitmap = getBitmapByView(mView);
                    //初始化固定圆
                    mMessageBubbleView.setDragBitmap(bitmap);
                    mMessageBubbleView.initFixedPoint(location[0] + mView.getWidth() / 2,
                            location[1] + mView.getWidth() / 2 - BubbleUtils.getStatusBarHeight(mContext));
                    //按下,隐藏当前的view.
                    mView.setVisibility(View.INVISIBLE);
                    break;
                case MotionEvent.ACTION_MOVE:
                    mMessageBubbleView.updateDragPoint(event.getRawX(), event.getRawY() - BubbleUtils.getStatusBarHeight(mContext));
                    break;
                case MotionEvent.ACTION_UP:
                    mMessageBubbleView.handleActionUp();
                    break;
    
                default:
                    break;
            }
            return true;
        }
    
        /**
         * 从一个View中获取Bitmap
         *
         * @param view
         * @return
         */
        private Bitmap getBitmapByView(View view) {
            view.buildDrawingCache();
            Bitmap bitmap = view.getDrawingCache();
            return bitmap;
        }
    
        @Override
        public void restore() {
            mWindowManager.removeView(mMessageBubbleView);
            mView.setVisibility(View.VISIBLE);
        }
    
        @Override
        public void dismiss(PointF pointF) {
            mWindowManager.removeView(mMessageBubbleView);
            // 要在 mWindowManager 添加一个爆炸动画
            mWindowManager.addView(mFrameLayout,mParams);
    
            mBombImage.setBackgroundResource(R.drawable.anim_bubble_pop);
    
            AnimationDrawable drawable = (AnimationDrawable) mBombImage.getBackground();
            mBombImage.setX(pointF.x-drawable.getIntrinsicWidth()/2);
            mBombImage.setY(pointF.y-drawable.getIntrinsicHeight()/2);
    
            drawable.start();
            // 等它执行完之后我要移除掉这个 爆炸动画也就是 mBombFrame
            mBombImage.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mWindowManager.removeView(mFrameLayout);
                    // 通知一下外面该消失
                    if(mBubbleTouchListener != null){
                        mBubbleTouchListener.dismiss();
                    }
                }
            },getAnimationDrawableTime(drawable));
        }
    
        private long getAnimationDrawableTime(AnimationDrawable drawable) {
            int numberOfFrames = drawable.getNumberOfFrames();
            long time = 0;
            for (int i=0;i<numberOfFrames;i++){
                time += drawable.getDuration(i);
            }
            return time;
        }
    
        public interface BubbleDisappearListener {
            /**
             * 消失
             */
            void dismiss();
        }
    }
    
    
        public interface BubbleDisappearListener {
            /**
             * 消失
             */
            void dismiss();
        }
    }
    public class BubbleUtils {
    
        /**
         * dip 转换成 px
         *
         * @param dip
         * @param context
         * @return
         */
        public static int dip2px( float dip, Context context) {
            DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
            return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, displayMetrics);
        }
    
        /**
         * 获取状态栏高度
         *
         * @return
         */
        public static int getStatusBarHeight(Context context) {
            //获取status_bar_height资源的ID
            int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
            if (resourceId > 0) {
                //根据资源ID获取响应的尺寸值
                return context.getResources().getDimensionPixelSize(resourceId);
            }
            return dip2px(25, context);
        }
    
        /**
         * As meaning of method name. 获得两点之间的距离 (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2) 开平方
         * Math.sqrt:开平方 Math.pow(p0.y - p1.y, 2):求一个数的平方
         *
         * @param p0
         * @param p1
         * @return
         */
        public static float getDistanceBetween2Points(PointF p0, PointF p1) {
            float distance = (float) Math.sqrt(Math.pow(p0.y - p1.y, 2)
                    + Math.pow(p0.x - p1.x, 2));
            return distance;
        }
    
        /**
         * Get point between p1 and p2 by percent. 根据百分比获取两点之间的某个点坐标
         *
         * @param p1
         * @param p2
         * @param percent
         * @return
         */
        public static PointF getPointByPercent(PointF p1, PointF p2, float percent) {
            return new PointF(evaluateValue(percent, p1.x, p2.x), evaluateValue(
                    percent, p1.y, p2.y));
        }
    
        /**
         * 根据分度值,计算从start到end中,fraction位置的值。fraction范围为0 -> 1
         *
         * @param fraction
         *            = 1
         * @param start
         *            = 10
         * @param end
         *            = 3
         * @return
         */
        public static float evaluateValue(float fraction, Number start, Number end) {
            // start = 10   end = 2
            //fraction = 0.5
            // result = 10 + (-8) * fraction = 6
            return start.floatValue() + (end.floatValue() - start.floatValue())
                    * fraction;
        }
    
        /**
         * Get the point of intersection between circle and line.
         * 获取通过指定圆心,斜率为lineK的直线与圆的交点。
         *
         * @param pMiddle
         *            The circle center point.
         * @param radius
         *            The circle radius.
         * @param lineK
         *            The slope of line which cross the pMiddle.
         * @return
         */
        public static PointF[] getIntersectionPoints(PointF pMiddle, float radius,
                                                     Double lineK) {
            PointF[] points = new PointF[2];
    
            //高中数学:几何
            float arctan, xOffset = 0, yOffset = 0;
            if (lineK != null) {
                // 计算直角三角形边长
                // 余切函数(弧度)
                arctan = (float) Math.atan(lineK);
                // 正弦函数
                xOffset = (float) (Math.sin(arctan) * radius);
                // 余弦函数
                yOffset = (float) (Math.cos(arctan) * radius);
            } else {
                xOffset = radius;
                yOffset = 0;
            }
            points[0] = new PointF(pMiddle.x + xOffset, pMiddle.y - yOffset);
            points[1] = new PointF(pMiddle.x - xOffset, pMiddle.y + yOffset);
    
            return points;
        }
    }
    
    
    public class MessageBubbleView extends View {
    
        private static final String TAG = "zsjTAG";
        private PointF mFixedPoint;
        private PointF mDragPoint;
        private int mFixedMaxRadius = 10;
        private int mFixedMinRadius = 5;
        private int mDragRadius = 12;
        private Paint mPaint;
        private PointF mP0;
        private PointF mP1;
        private PointF mP2;
        private PointF mP3;
        private PointF mControlPoint;
        private Bitmap mDragBitmap;
    
        public MessageBubbleView(Context context) {
            this(context, null);
        }
    
        public MessageBubbleView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public MessageBubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            mFixedMaxRadius = dip2px(mFixedMaxRadius);
            mFixedMinRadius = dip2px(mFixedMinRadius);
            mDragRadius = dip2px(mDragRadius);
            initPaint();
        }
    
        private void initPaint() {
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mPaint.setDither(true);
            mPaint.setColor(Color.RED);
        }
    
        private int dip2px(int dip) {
            return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics());
        }
    
    /*    @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    float fixedX = event.getX();
                    float fixedY = event.getY();
                    initFixedPoint(fixedX, fixedY);
                    break;
                case MotionEvent.ACTION_MOVE:
                    float dragX = event.getX();
                    float dragY = event.getY();
                    updateDragPoint(dragX, dragY);
                    break;
                case MotionEvent.ACTION_UP:
    
                    break;
    
                default:
                    break;
            }
            invalidate();
            return true;
        }*/
    
        public void updateDragPoint(float dragX, float dragY) {
            if (mDragPoint == null) {
                mDragPoint = new PointF( dragX, dragY);
            }
            mDragPoint.x =  dragX;
            mDragPoint.y = dragY;
            invalidate();
        }
    
    
        public void initFixedPoint(float fixedX, float fixedY) {
            if (mFixedPoint == null) {
                mFixedPoint = new PointF();
            }
            mFixedPoint.x = fixedX;
            mFixedPoint.y = fixedY;
            invalidate();
        }
    
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            if (mFixedPoint == null || mDragPoint == null) {
                return;
            }
            //绘制拖拽圆
            canvas.drawCircle(mDragPoint.x, mDragPoint.y, mDragRadius, mPaint);
            //移动的时候,固定圆缩小.根据拖拽圆和固定圆的距离缩小
            //计算拖拽圆和固定圆的距离
            double dragFixedDistance = getDragFixedDistance(mFixedPoint, mDragPoint);
            float fixedRadius = (float) (mFixedMaxRadius - dragFixedDistance / 14);
            if (fixedRadius > mFixedMinRadius) {
                //绘制固定圆
                canvas.drawCircle(mFixedPoint.x, mFixedPoint.y, fixedRadius, mPaint);
                Path bezierPath = getBezierPath(mFixedPoint, mDragPoint);
                canvas.drawPath(bezierPath, mPaint);
            }
            canvas.drawBitmap(mDragBitmap, mDragPoint.x - mDragBitmap.getWidth() / 2
                    , mDragPoint.y - mDragBitmap.getHeight() / 2, null);
        }
    
        private Path getBezierPath(PointF fixedPoint, PointF dagPoint) {
            double dragFixedDistance = getDragFixedDistance(mFixedPoint, mDragPoint);
            float fixedRadius = (float) (mFixedMaxRadius - dragFixedDistance / 14);
            float dx = Math.abs(fixedPoint.x - dagPoint.x);
            float dy = Math.abs(fixedPoint.y - dagPoint.y);
            float tanA = dy / dx;
            float a = (float) Math.atan(tanA);
    
            //P0 点
            if (mP0 == null) {
                mP0 = new PointF();
            }
            mP0.x = mDragPoint.x + (int) (mDragRadius * Math.sin(a));
            mP0.y = mDragPoint.y - (int) (mDragRadius * Math.cos(a));
    
            //P1 点
            if (mP1 == null) {
                mP1 = new PointF();
            }
            mP1.x = mFixedPoint.x + (int) (fixedRadius * Math.sin(a));
            mP1.y = mFixedPoint.y - (int) (fixedRadius * Math.cos(a));
    
    
            //P2 点
            if (mP2 == null) {
                mP2 = new PointF();
            }
            mP2.x = mFixedPoint.x - (int) (fixedRadius * Math.sin(a));
            mP2.y = mFixedPoint.y + (int) (fixedRadius * Math.cos(a));
    
    
            //P0 点
            if (mP3 == null) {
                mP3 = new PointF();
            }
            mP3.x = mDragPoint.x - (int) (mDragRadius * Math.sin(a));
            mP3.y = mDragPoint.y + (int) (mDragRadius * Math.cos(a));
    
    
            //绘制路径
            Path path = new Path();
            path.moveTo(mP0.x, mP0.y);
    
            //控制点选择固定圆和拖拽圆的中心点
            PointF controlPoint = getControlPoint();
            path.quadTo(controlPoint.x, controlPoint.y, mP1.x, mP1.y);
    
            path.lineTo(mP2.x, mP2.y);
            path.quadTo(controlPoint.x, controlPoint.y, mP3.x, mP3.y);
            path.close();
            return path;
        }
    
        private PointF getControlPoint() {
            if (mControlPoint == null) {
                mControlPoint = new PointF();
            }
            mControlPoint.x = (mDragPoint.x + mFixedPoint.x) / 2;
            mControlPoint.y = (mDragPoint.y + mFixedPoint.y) / 2;
            return mControlPoint;
        }
    
        private double getDragFixedDistance(PointF fixedPoint, PointF dagPoint) {
            return Math.sqrt((dagPoint.x - fixedPoint.x) * (dagPoint.x - fixedPoint.x) + (dagPoint.y - fixedPoint.y) * (dagPoint.y - fixedPoint.y));
        }
    
        public static void attach(View view, BubbleMessageTouchListener.BubbleDisappearListener bubbleTouchListener) {
            view.setOnTouchListener(new BubbleMessageTouchListener(view, view.getContext(),bubbleTouchListener));
        }
    
        public void setDragBitmap(Bitmap dragBitmap) {
            mDragBitmap = dragBitmap;
            invalidate();
        }
    
        public void handleActionUp() {
            double dragFixedDistance = getDragFixedDistance(mFixedPoint, mDragPoint);
            float fixedRadius = (float) (mFixedMaxRadius - dragFixedDistance / 14);
            if (fixedRadius > mFixedMinRadius) {
                //回弹
                // 0 - 1
                ValueAnimator valueAnimator = ValueAnimator.ofFloat(1);
                valueAnimator.setDuration(250);
                final PointF start = new PointF(mFixedPoint.x, mFixedPoint.y);
                final PointF end = new PointF(mDragPoint.x, mDragPoint.y);
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        float percent = (float) animation.getAnimatedValue();
                        PointF pointF = BubbleUtils.getPointByPercent(end, start, percent);
                        updateDragPoint(pointF.x, pointF.y);
                    }
                });
                valueAnimator.setInterpolator(new OvershootInterpolator(3f));
                valueAnimator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        if (mListener != null) {
                            mListener.restore();
                        }
                    }
                });
                valueAnimator.start();
            } else {
                //爆炸
                if (mListener != null){
                    mListener.dismiss(mDragPoint);
                }
            }
        }
    
    
        private MessageBubbleListener mListener;
    
        public void setMessageBubbleListener(MessageBubbleListener listener) {
            this.mListener = listener;
        }
    
        public interface MessageBubbleListener {
            // 还原
            public void restore();
    
            // 消失爆炸
            public void dismiss(PointF pointF);
        }
    }
    
    

    4 仿写效果

    gpjvn-6go3t.gif

    5 完整代码

    messagebubbleview

    相关文章

      网友评论

          本文标题:自定义View-31 仿QQ消息未读拖拽回弹和爆炸效果

          本文链接:https://www.haomeiwen.com/subject/girlmftx.html