自定义view——贝塞尔曲线之仿qq消息气泡拖拽让所有view拖动爆炸

之前写过一篇文章:自定义view——贝塞尔曲线之仿qq消息气泡拖拽
http://blog.csdn.net/qq_24675479/article/details/79430672

这里将完善上个项目,让其所有view都可以首先拖动爆炸
这里写图片描述

首先工具类

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);
    }


    /**
     * 获取状态栏的高度
     */
    public static int getStatusBarHeight(Context context) {
        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            return context.getResources().getDimensionPixelOffset(resourceId);
        }
        return dip2px(25, context);
    }
    /**
     * 根据百分比获取两点之间的某个点坐标
     */
    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;
    }
}

修改MessageBubbleView

public class MessageBubbleView extends View {
    // 两个圆的圆形
    private PointF mFixationPoint, mDragPoint;
    // 拖拽圆的半径
    private int mDragRadius = 10;
    private Paint mPaint;
    // 固定圆的最大半径(初始半径)
    private int mFixationRadiusMax = 7;
    private int mFixationRadiusMin = 3;
    private int mFixationRadius;
    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);
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mDragRadius = dp2x(mDragRadius);
        mFixationRadiusMax = dp2x(mFixationRadiusMax);
        mFixationRadiusMin = dp2x(mFixationRadiusMin);
    }

    private int dp2x(int dpValue) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics());
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mDragPoint == null || mFixationPoint == null) {
            return;
        }
        //绘制拖拽的圆
        canvas.drawCircle(mDragPoint.x, mDragPoint.y, mDragRadius, mPaint);

        //获得两个圆之间的距离
        double distance = getDistance(mDragPoint, mFixationPoint);
        mFixationRadius = (int) (mFixationRadiusMax - distance / 14);
        Path bezeierPath = getBezeierPath();

        if (bezeierPath != null) {//当大于最小的时候才绘制
            //绘制固定的圆
            canvas.drawCircle(mFixationPoint.x, mFixationPoint.y, mFixationRadius, mPaint);
            //绘制贝塞尔曲线
            canvas.drawPath(bezeierPath, mPaint);

        }
        if (mDragBitmap != null) {
            //绘制一个bitmap
            canvas.drawBitmap(mDragBitmap, mDragPoint.x - mDragBitmap.getWidth() / 2
                    , mDragPoint.y - mDragBitmap.getHeight() / 2, null);
        }
    }

    /**
     * 获取两个圆之间的距离
     */
    private double getDistance(PointF point1, PointF point2) {
        return Math.sqrt((point1.x - point2.x) * (point1.x - point2.x)
                + (point1.y - point2.y) * (point1.y - point2.y));
    }

   /* @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                float downX = event.getX();
                float downY = event.getY();
                initPoint(downX, downY);
                break;
            case MotionEvent.ACTION_MOVE:
                float moveX = event.getX();
                float moveY = event.getY();
                updatePoint(moveX, moveY);
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        invalidate();
        return true;

    }*/

    /**
     * 更新拖拽点的坐标
     */
    public void updatePoint(float moveX, float moveY) {
        mDragPoint.x = moveX;
        mDragPoint.y = moveY;
        invalidate();
    }

    /**
     * 初始化点
     */
    public void initPoint(float downX, float downY) {
        mFixationPoint = new PointF(downX, downY);
        mDragPoint = new PointF(downX, downY);
        invalidate();
    }

    /**
     * 获得贝塞尔的曲线
     *
     * @return
     */
    public Path getBezeierPath() {

        if (mFixationRadius < mFixationRadiusMin) {
            // 超过一定距离 贝塞尔和固定圆都不要画了
            return null;
        }

        Path bezeierPath = new Path();
        //求角a
        //求斜率
        float dy = mDragPoint.y - mFixationPoint.y;
        float dx = mDragPoint.x - mFixationPoint.x;
        float tanA = dy / dx;
        //求出角a
        double arcTanA = Math.atan(tanA);

        //p0
        float p0x = (float) (mFixationRadius * Math.sin(arcTanA) + mFixationPoint.x);
        float p0y = (float) (mFixationPoint.y - mFixationRadius * Math.cos(arcTanA));

        //p1
        float p1x = (float) (mDragRadius * Math.sin(arcTanA) + mDragPoint.x);
        float p1y = (float) (mDragPoint.y - mDragRadius * Math.cos(arcTanA));

        //p2
        float p2x = (float) (mDragPoint.x - mDragRadius * Math.sin(arcTanA));
        float p2y = (float) (mDragPoint.y + mDragRadius * Math.cos(arcTanA));

        //p3
        float p3x = (float) (mFixationPoint.x - mFixationRadius * Math.sin(arcTanA));
        float p3y = (float) (mFixationPoint.y + mFixationRadius * Math.cos(arcTanA));

        //绘制贝塞尔曲线
        bezeierPath.moveTo(p0x, p0y);
        //绘制第一条线
        PointF controlPoint = getControlPoint();
        bezeierPath.quadTo(controlPoint.x, controlPoint.y, p1x, p1y);//控制点+目标坐标
        //绘制到第二条
        bezeierPath.lineTo(p2x, p2y);
        bezeierPath.quadTo(controlPoint.x, controlPoint.y, p3x, p3y);
        //最后闭合
        bezeierPath.close();
        return bezeierPath;
    }

    /**
     * 获取控制点
     */
    public PointF getControlPoint() {
        return new PointF((mDragPoint.x + mFixationPoint.x) / 2, (mDragPoint.y + mFixationPoint.y) / 2);
    }

    /**
     * 设置可拖拽的事件
     */
    public static void attach(View view, BubbleMessageTouchListener.BubbleDisappearListener disappearListener) {
        view.setOnTouchListener(new BubbleMessageTouchListener(view, view.getContext(),disappearListener));
    }

    public void setDragBitmap(Bitmap dragBitmap) {
        this.mDragBitmap = dragBitmap;
    }

    //手指放开
    public void handleActionUp() {
        if (mFixationRadius > mFixationRadiusMin) {
            //回弹   ValueAnimator 值变化的动画  0 变化到 1
            ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
            animator.setDuration(250);
            final PointF start=new PointF(mDragPoint.x,mDragPoint.y);
            final PointF end=new PointF(mFixationPoint.x,mFixationPoint.y);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float precent = (float) animation.getAnimatedValue();//获得当前百分比
                    PointF pointF=BubbleUtils.getPointByPercent(start,end,precent);
                    //PointF pointF=BubbleUtils.getPointByPercent(mDragPoint,mFixationPoint,precent);//会没有回弹效果
                    updatePoint(pointF.x,pointF.y);
                }
            });
            //差值器  回弹的时候弹一下
            animator.setInterpolator(new OvershootInterpolator(3f));
            animator.start();
            // 还要通知 TouchListener 移除当前View 然后显示静态的 View
           animator.addListener(new AnimatorListenerAdapter() {
               @Override
               public void onAnimationEnd(Animator animation) {
                   //动画结束,移除
                   if(mListener!=null){
                       mListener.restore();
                   }
               }
           });
        } else {
            // 爆炸
            if(mListener != null){
                mListener.dismiss(mDragPoint);
            }
        }
    }

 private BubbleDisappearListener mListener;

    public void setListener(BubbleDisappearListener mListener) {
        this.mListener = mListener;
    }

    public interface BubbleDisappearListener {
         //恢复
        void restore();
        //爆炸消失
        void dismiss(PointF mDragPoint);
    }
}

BubbleMessageTouchListener:触摸事件终究处理类

public class BubbleMessageTouchListener implements View.OnTouchListener, MessageBubbleView.BubbleDisappearListener {
    // 原来需要拖动爆炸的View
    private View mStaticView;
    //消失后创建一个view
    private MessageBubbleView mMessageBubbleView;
    //创建一个windowManager
    private WindowManager mWindowManager;
    private WindowManager.LayoutParams mParams;
    private Context mContext;

    //爆炸
    private FrameLayout mBmobFrameLayout;
    private ImageView mBombImageView;
    BubbleDisappearListener mDisappearListener;

    public BubbleMessageTouchListener(View view, Context context, BubbleDisappearListener disappearListener) {
        this.mStaticView = view;
        mMessageBubbleView = new MessageBubbleView(context);
        mMessageBubbleView.setListener(this);
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        mParams = new WindowManager.LayoutParams();
        //设置背景透明
        mParams.format = PixelFormat.TRANSPARENT;
        this.mContext = context;
        this.mDisappearListener = disappearListener;
        //初始化爆炸
        mBmobFrameLayout = new FrameLayout(mContext);
        mBombImageView = new ImageView(mContext);
        mBombImageView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT
                , ViewGroup.LayoutParams.WRAP_CONTENT));
        mBmobFrameLayout.addView(mBombImageView);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:

                mWindowManager.addView(mMessageBubbleView, mParams);
                //应该是中心位置而不是按下的位置
                // mMessageBubbleView.initPoint(event.getRawX(), event.getRawY() - BubbleUtils.getStatusBarHeight(mContext));
                int[] location = new int[2];
                mStaticView.getLocationOnScreen(location);
                mMessageBubbleView.initPoint(location[0] + mStaticView.getWidth(),
                        location[1] + mStaticView.getWidth() / 2 - BubbleUtils.getStatusBarHeight(mContext));
                Bitmap bitmap = getBitmapByView(mStaticView);
                //给mMessageBubbleView添加一个拖拽的view
                mMessageBubbleView.setDragBitmap(bitmap);
                //控件消失
                mStaticView.setVisibility(View.INVISIBLE);
                break;
            case MotionEvent.ACTION_MOVE:
                mMessageBubbleView.updatePoint(event.getRawX(), event.getRawY() - BubbleUtils.getStatusBarHeight(mContext));
                break;
            case MotionEvent.ACTION_UP:
                mMessageBubbleView.handleActionUp();
                break;
        }

        return true;
    }

    /**
     * 从view中获取bitmap
     */
    private Bitmap getBitmapByView(View view) {
        view.buildDrawingCache();
        Bitmap bitmap = view.getDrawingCache();
        return bitmap;
    }

    //恢复
    @Override
    public void restore() {
        mWindowManager.removeView(mMessageBubbleView);
        mStaticView.setVisibility(View.VISIBLE);
    }

    @Override
    public void dismiss(PointF pointF) {
        // 要去执行爆炸动画 (帧动画)
        // 原来的View的View肯定要移除
        mWindowManager.removeView(mMessageBubbleView);
        mWindowManager.addView(mBmobFrameLayout, mParams);
        //添加一个动画效果
        mBombImageView.setBackgroundResource(R.drawable.anim_bubble_pop);
        AnimationDrawable drawable = (AnimationDrawable) mBombImageView.getBackground();
        //设置爆炸图片的位置
        mBombImageView.setX(pointF.x - drawable.getIntrinsicWidth() / 2);
        mBombImageView.setY(pointF.y - drawable.getIntrinsicHeight() / 2);
        drawable.start();
        // 等它执行完之后要移除掉这个 爆炸动画也就是 mBombFrame
        mBombImageView.postDelayed(new Runnable() {
            @Override
            public void run() {
                mWindowManager.removeView(mBmobFrameLayout);
                //通知外面要消失
                mDisappearListener.dismiss(mStaticView);
            }
        }, getAnimationDrawableTime(drawable));
    }

    private long getAnimationDrawableTime(AnimationDrawable drawable) {
        long time = 0;
        int frames = drawable.getNumberOfFrames();
        for (int i = 0; i < frames; i++) {
            time += drawable.getDuration(i);
        }
        return time;
    }

    /**
     * 提供接口给外部
     */
    public interface BubbleDisappearListener {
        void dismiss(View view);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值