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

2018-07-22  本文已影响98人  zsj1225

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

上一篇下一篇

猜你喜欢

热点阅读