Android开发之QQ黏性控件(QQ消息未读提醒,在固定范围内,拖拽回弹,超出范围,松手消失)

Android开发之QQ黏性控件(QQ消息未读提醒,在固定范围内,拖拽回弹,超出范围,松手消失)

自腾讯手机QQ新版推出后,想必大家都发现了,QQ的未读消息提醒,效果真的很炫酷,即在固定范围内,拖拽未读消息提醒控件,会在拖拽中心点到原中心点绘制出贝塞尔曲线,然而超过了固定范围后,松手后产生气泡消失,本人研究了下该黏性控件,已基本实现了其功能,下面贴出代码,望各路大神批评指正,灰常感谢,♪(^∇^*)

先上效果图:


核心代码如下:
MainActivity
package com.example.testvistousview;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

ViscosityView
package com.example.testvistousview.customview;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.FloatEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.support.v4.view.MotionEventCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.CycleInterpolator;
import android.view.animation.OvershootInterpolator;

import com.example.testvistousview.Utils.GeometryUtils;

/**
 * 粘性控件:消息未读提醒
 */
public class ViscosityView extends View {

    private Paint mPaint;
    private PointF[] mDragPoint;
    private PointF[] mStickPoint;
    private PointF mControlPoint;
    private PointF mDragCenterPoint;
    private PointF mStickCenterPoint;
    private float mDragRadius;
    private float mStickRadius;
    private float xOffset;
    private float yOffset;
    private double lineK;
    private int statusHeight;
    float maxDistance = 200.0f;
    private boolean isOutOfRange = false;
    private boolean disappear = false;
    private Paint mTextPaint;

    public interface OnDragEventListener {
        void onDisappear();

        void onReset(boolean isToReset);
    }

    public OnDragEventListener mOnDragEventListener;

    public OnDragEventListener getmOnDragEventListener() {
        return mOnDragEventListener;
    }

    public void setmOnDragEventListener(OnDragEventListener mOnDragEventListener) {
        this.mOnDragEventListener = mOnDragEventListener;
    }

    public ViscosityView(Context context) {
        this(context, null);
    }

    public ViscosityView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ViscosityView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //ANTI_ALIAS_FLAG抗锯齿
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.RED);

        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setColor(Color.WHITE);
        mTextPaint.setTextSize(24);
        mTextPaint.setTypeface(Typeface.DEFAULT_BOLD);
        mTextPaint.setTextAlign(Paint.Align.CENTER);

        mDragPoint = new PointF[2];
        mStickPoint = new PointF[2];
        mControlPoint = new PointF();
        mDragCenterPoint = new PointF(150f, 150f);
        mStickCenterPoint = new PointF(220f, 250f);
        mStickRadius = 30f;
        mDragRadius = 30f;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        /**
         * 将画布向上平移状态栏高度的位置
         */
        //保存当前的的状态
        canvas.save();
        canvas.translate(0, -statusHeight);

        //计算连接部分----利用变量
        //计算变量:
        //1.求出固定圆的半径(暂时假设为12f        float tempStickRadius = getStickRadius();

        //2.求出四个附着点的坐标
        xOffset = mStickCenterPoint.x - mDragCenterPoint.x;
        yOffset = mStickCenterPoint.y - mDragCenterPoint.y;
        if (xOffset != 0) {
            lineK = yOffset / xOffset;
        }
        mStickPoint = GeometryUtils.getIntersectionPoints(mStickCenterPoint, tempStickRadius, lineK);
        mDragPoint = GeometryUtils.getIntersectionPoints(mDragCenterPoint, mDragRadius, lineK);

        //3.求出控制点的坐标
        mControlPoint = GeometryUtils.getPointByPercent(mDragCenterPoint, mStickCenterPoint, 0.618f);

        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(mStickCenterPoint.x, mStickCenterPoint.y, maxDistance, mPaint);
        mPaint.setStyle(Paint.Style.FILL);

        if (!disappear) {
            if (!isOutOfRange) {
                //绘制两圆的连接部分采用贝塞尔曲线
                //绘制路径
                Path path = new Path();
                path.moveTo(mStickPoint[0].x, mStickPoint[0].y);
                path.quadTo(mControlPoint.x, mControlPoint.y, mDragPoint[0].x, mDragPoint[0].y);
                path.lineTo(mDragPoint[1].x, mDragPoint[1].y);
                path.quadTo(mControlPoint.x, mControlPoint.y, mStickPoint[1].x, mStickPoint[1].y);
                path.close();

                Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);//设置抗锯齿的属性
                int[] colors = new int[]{
                        Color.RED,
                        Color.GRAY,
                        Color.YELLOW,
                        Color.GREEN,
                        Color.BLUE
                };
                Shader shader = new LinearGradient(0, 0, 10, 10, colors, null, Shader.TileMode.REPEAT);
                paint.setShader(shader);

                canvas.drawCircle(mDragPoint[0].x, mDragPoint[0].y, 3.0f, paint);
                canvas.drawCircle(mDragPoint[1].x, mDragPoint[1].y, 3.0f, paint);
                canvas.drawCircle(mStickPoint[0].x, mStickPoint[0].y, 3.0f, paint);
                canvas.drawCircle(mStickPoint[1].x, mStickPoint[1].y, 3.0f, paint);

                //绘制贝塞尔曲线
                canvas.drawPath(path, mPaint);

                //绘制gudingyu固定圆
                canvas.drawCircle(mStickCenterPoint.x, mStickCenterPoint.y, tempStickRadius, mPaint);
            }
            //绘制拖拽圆
            canvas.drawCircle(mDragCenterPoint.x, mDragCenterPoint.y, mDragRadius, mPaint);
            canvas.drawText("23", mDragCenterPoint.x, mDragCenterPoint.y + mDragRadius / 3.0f, mTextPaint);
        }
        //恢复到画布刚刚保存的状态
        canvas.restore();
    }

    private float getStickRadius() {
        float distance = GeometryUtils.getDistanceBetween2Point(mStickCenterPoint, mDragCenterPoint);
        distance = Math.min(distance, maxDistance);
        float percent = distance / maxDistance;
        FloatEvaluator evaluator = new FloatEvaluator();
        Float evaluate = evaluator.evaluate(percent, 1.0f, 0.2f);
        System.out.println("percent------------------------" + percent);
        return evaluate * mStickRadius;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (MotionEventCompat.getActionMasked(event)) {
            case MotionEvent.ACTION_DOWN:
                isOutOfRange = false;
                disappear = false;
                float downX = event.getRawX();
                float downY = event.getRawY();
                updateDragCenter(downX, downY);

                break;
            case MotionEvent.ACTION_MOVE:
                float moveX = event.getRawX();
                float moveY = event.getRawY();
                updateDragCenter(moveX, moveY);

                //实时判断拖拽圆和固定圆两圆的的圆心距与最大距离的比较
                if (GeometryUtils.getDistanceBetween2Point(mDragCenterPoint, mStickCenterPoint) > maxDistance) {
                    isOutOfRange = true;
                    invalidate();
                }
                break;
            case MotionEvent.ACTION_UP:
                //表示已经超出范围了
                if (isOutOfRange) {
                    //实时的判断是否超出范围
                    if (GeometryUtils.getDistanceBetween2Point(mDragCenterPoint, mStickCenterPoint) > maxDistance) {
                        disappear = true;

                        if (mOnDragEventListener != null) {
                            mOnDragEventListener.onDisappear();
                        }
                        invalidate();
                    } else {
                        updateDragCenter(mStickCenterPoint.x, mStickCenterPoint.y);
                        if (mOnDragEventListener != null) {
                            mOnDragEventListener.onReset(true);
                        }
                    }
                } else {
                    final PointF lastPointCenter = new PointF(mDragCenterPoint.x, mDragCenterPoint.y);
                    //updateDragCenter(mStickCenterPoint.x,mStickCenterPoint.y);
                    ValueAnimator mValueAnimator = ValueAnimator.ofFloat(1.0f);
                    mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                        @Override
                        public void onAnimationUpdate(ValueAnimator animation) {
                            float fraction = animation.getAnimatedFraction();
                            PointF pointF = GeometryUtils.getPointByPercent(lastPointCenter, mStickCenterPoint, fraction);
                            updateDragCenter(pointF.x, pointF.y);
                        }
                    });
                    mValueAnimator.addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            super.onAnimationEnd(animation);

                            if (mOnDragEventListener != null) {
                                mOnDragEventListener.onReset(true);
                            }
                        }
                    });
                    //mValueAnimator.setInterpolator(new CycleInterpolator(4));
                    mValueAnimator.setInterpolator(new OvershootInterpolator());
                    mValueAnimator.setDuration(200);
                    mValueAnimator.start();
                }
                break;
        }
        return true;
    }

    /**
     * 更新拖拽圆的圆心坐标
     *
     * @param x
     * @param y
     */
    private void updateDragCenter(float x, float y) {
        mDragCenterPoint.set(x, y);
        invalidate();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        Rect rect = new Rect();
        this.getWindowVisibleDisplayFrame(rect);
        statusHeight = rect.top;
        System.out.println("statusHeight-------------------------" + statusHeight);
    }
}

GeometryUtils
package com.example.testvistousview.Utils;

import android.animation.FloatEvaluator;
import android.graphics.PointF;

/**
 * 定义几何图形工具
 */
public class GeometryUtils {


    /**
     * 计算两点之间的距离
     *
     * @param p0 第一个点的坐标值
     * @param p1 第二个点的坐标值
     * @return 两点之间的距离
     */
    public static float getDistanceBetween2Point(PointF p0, PointF p1) {
        float yOffset = p0.y - p1.y;
        float xOffect = p0.x - p1.x;
        float distance = (float) Math.sqrt(Math.pow(yOffset, 2) + Math.pow(xOffect, 2));
        return distance;
    }

    /**
     * 计算两点连线的的中心坐标
     *
     * @param p0
     * @param p1
     * @return
     */
    public static PointF getMiddleBetween2Point(PointF p0, PointF p1) {
        float yOffset = p0.y - p1.y;
        float xOffect = p0.x - p1.x;
        return new PointF(xOffect / 2.0f, yOffset / 2.0f);
    }

    /**
     * 根据百分比获得两点连线上的某个位置的坐标
     *
     * @param p0
     * @param p1
     * @param percent
     * @return
     */
    public static PointF getPointByPercent(PointF p0, PointF p1, float percent) {
        FloatEvaluator mFloatEvaluator = new FloatEvaluator();
        Float xPoint = mFloatEvaluator.evaluate(percent, p0.x, p1.x);
        Float yPoint = mFloatEvaluator.evaluate(percent, p0.y, p1.y);
        return new PointF(xPoint, yPoint);
    }

    /**
     * 获取通过指定圆心且斜率为lineK的直线与圆的交点
     *
     * @param pMiddle
     * @param radius
     * @param lineK
     * @return
     */
    public static PointF[] getIntersectionPoints(PointF pMiddle, float radius, Double lineK) {
        PointF[] pointFs = new PointF[2];

        float radian, xOffset = 0, yOffset = 0;
        if (lineK != null) {
            radian = (float) Math.atan(lineK);
            xOffset = (float) (Math.sin(radian) * radius);
            yOffset = (float) (Math.cos(radian) * radius);

        } else {
            xOffset = radius;
            yOffset = 0;
        }

        pointFs[0] = new PointF(pMiddle.x + xOffset, pMiddle.y - yOffset);
        pointFs[1] = new PointF(pMiddle.x - xOffset, pMiddle.y + yOffset);

        return pointFs;
    }
}

代码已贴出,正所谓取之于社会,回报于社会,望各路大神批评指正,不喜勿喷,灰常感谢,♪(^∇^*)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值