自定义控件仿QQ小红点,粘性控件,贝塞尔曲线绘制,值动画

自定义控件仿QQ小红点,粘性控件


1.实现自由拖拽
2.使用路径贝塞尔随手指拖动自由绘制
3.接口回调,实现监听事件


先放出效果图

拖拽的鼻屎

有没有感觉像被拖拽的鼻屎

1.继承与View — so我们需要重写方法onMeasure()和onDraw().
感觉我不用多做解释考虑了。。。已经写了很详细了。卧槽

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

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

    public GooView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * 测量
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
      /**
     * 绘图,作画
     *
     * @param canvas
     */

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        this.canvas = canvas;
        //设定抗锯齿画笔
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        //画笔颜色
        paint.setColor(Color.BLUE);
        //固定圆的点
        mStickCenter = new PointF(100f, 200f);
        //固定的圆环半径
        assetsAadius = 100f;
        //固定圆的点半径
        if (distanceBetween2Points > assetsAadius) {
            mStickRodius = 4f;
        } else {
            mStickRodius = 10f - 6f * move2stickDistance / assetsAadius;
        }
        //画固定圆框
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(mStickCenter.x, mStickCenter.y, assetsAadius, paint);
        paint.setStyle(Paint.Style.FILL);
        //移动圆的圆心
        mDragCenter = getmDragCenter();
        //移动圆半径
        mDragRodius = 15f;
        //是否不再作画
        if (!cleanAllDraw) {
            //画出移动圆
            canvas.drawCircle(mDragCenter.x, mDragCenter.y, mDragRodius, paint);
            if (!beyondDistance) {//如果超出方位不再画贝塞尔曲线和原点
                //贝塞尔曲线绘制
                upDraw(canvas, paint);
                //得到当前移动圆圆心与

                //画固定圆
                canvas.drawCircle(mStickCenter.x, mStickCenter.y, mStickRodius, paint);
            }
            //设定文本画笔
            Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            textPaint.setColor(Color.WHITE);
            textPaint.setTextAlign(Paint.Align.CENTER);
            textPaint.setTextSize(10f);
            //画固定圆上边写上文本...
            canvas.drawText("A", mStickCenter.x, mStickCenter.y + mStickRodius * 0.3f, textPaint);
            //画移动圆,并在上边写上文本...
            canvas.drawText("B", mDragCenter.x, mDragCenter.y + mDragRodius * 0.3f, textPaint);
        }
        //重新设置为false;
        beyondDistance = false;
        cleanAllDraw = false;
    }

    /**
     * 根据传入的移动圆的圆心开始作贝塞尔曲线,不管怎样画,最终的画会覆盖之前的画
     *
     * @param canvas 画布
     * @param paint  画笔
     */
    private void upDraw(Canvas canvas, Paint paint) {
        //测到测量的两点圆中心点
        PointF mMiddlePoint = GeometryUtil.getMiddlePoint(mStickCenter, mDragCenter);
        //获得两圆圆心连线的斜率;
        float yOffset = mStickCenter.y - mDragCenter.y;
        float xOffset = mStickCenter.x - mDragCenter.x;
        Double lineK = null;
        if (xOffset != 0) {
            lineK = (double) (yOffset / xOffset);
        }
        //****
        paint.setColor(Color.RED);
        //得到固定圆的两个附着点
        PointF[] mStickPoints = GeometryUtil.getIntersectionPoints(mStickCenter, mStickRodius, lineK);
        //得到移动圆的两个附着点
        PointF[] mDragPoints = GeometryUtil.getIntersectionPoints(mDragCenter, mDragRodius, lineK);
        //画出求出的四个点(测试用)
        canvas.drawCircle(mStickPoints[0].x, mStickPoints[0].y, 2f, paint);
        canvas.drawCircle(mStickPoints[1].x, mStickPoints[1].y, 2f, paint);
        canvas.drawCircle(mDragPoints[0].x, mDragPoints[0].y, 2f, paint);
        canvas.drawCircle(mDragPoints[1].x, mDragPoints[1].y, 2f, paint);
        paint.setColor(Color.BLUE);
        //绘制路径
        Path path = new Path();
        //移动到这个点
        path.moveTo(mStickPoints[0].x, mStickPoints[0].y);
        //画一半贝塞尔曲线
        path.quadTo(mMiddlePoint.x, mMiddlePoint.y, mDragPoints[0].x, mDragPoints[0].y);
        //画出直线
        path.lineTo(mDragPoints[1].x, mDragPoints[1].y);
        //画另一半贝塞尔曲线
        path.quadTo(mMiddlePoint.x, mMiddlePoint.y, mStickPoints[1].x, mStickPoints[1].y);
        //封闭路径
        path.close();
        //根据设定路径作画
        canvas.drawPath(path, paint);

    }


这里需要采用三角函数的技术,但是万能的开源,有一个数学工具类,

提出来给你们看看

package com.oblivion.utils;

import android.graphics.PointF;

/**
 * 几何图形工具
 */
public class GeometryUtil {

    /**
     * As meaning of method name.
     * 获得两点之间的距离
     *
     * @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 middle point between p1 and p2.
     * 获得两点连线的中点
     *
     * @param p1
     * @param p2
     * @return
     */
    public static PointF getMiddlePoint(PointF p1, PointF p2) {
        return new PointF((p1.x + p2.x) / 2.0f, (p1.y + p2.y) / 2.0f);
    }

    /**
     * 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
     * @param start
     * @param end
     * @return
     */
    public static float evaluateValue(float fraction, Number start, Number end) {
        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 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;
        }
        points[0] = new PointF(pMiddle.x + xOffset, pMiddle.y - yOffset);
        points[1] = new PointF(pMiddle.x - xOffset, pMiddle.y + yOffset);

        return points;
    }
}

很简单吧。我们已经能画出一帧图片来了,所以。我们现在子需要对触摸点进行判断,然后对事件经行处理,就能得出入图所示

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN://记录按下点,作为移动圆圆心起始点
                downX_dot = event.getX();
                downY_dot = event.getY();
                PointF down_PointF = new PointF(downX_dot, downY_dot);
                //如果按下点的距离大于外圆环
                distanceBetween2Points = GeometryUtil.getDistanceBetween2Points(down_PointF, mStickCenter);
                setmDragCenter(new PointF(downX_dot, downY_dot));
                invalidate();
                state = State.touch;//状态修改为触摸状态
                setStateCallBack();
                break;
            case MotionEvent.ACTION_MOVE:
                moveX_dot = event.getX();
                moveY_dot = event.getY();
                //移动点所在坐标
                PointF move_pointF = new PointF(moveX_dot, moveY_dot);
                //抬起点与原点的距离
                move2stickDistance = GeometryUtil.getDistanceBetween2Points(move_pointF, mStickCenter);
                //判断移动点是否超出范围
                beyondDistance = move2stickDistance > assetsAadius ? true : false;
                setmDragCenter(new PointF(moveX_dot, moveY_dot));
                invalidate();
                setStateCallBack();
                break;
            case MotionEvent.ACTION_UP:
                float upX_dot = event.getX();
                float upY_dot = event.getY();
                //抬起点所在坐标
                up_pointF = new PointF(upX_dot, upY_dot);
                //抬起点与原点的距离
                up2stickDistance = GeometryUtil.getDistanceBetween2Points(up_pointF, mStickCenter);
                //判断是否移动了
                if (move2stickDistance > assetsAadius) {
                    cleanAllDraw = true;
                    invalidate();
                    state = State.remove;//清除状态
                } else {
                    //开始动画
                    setAnimator();
                    //设定回到默认值
                    cleanAllDraw = false;
                    state = State.retain;//保留状态
                }
                move2stickDistance = 0;
                setStateCallBack();
                break;
        }
        return true;
    }

事件完成里,大体框架能弄出来了,现在我们需要优化一下下,比如,我们需要什么时候将图片消失,社么时候存在,,这就。。。。
需要值动画进行处理

/**
     * 设定值动画
     */
    private void setAnimator() {
        ValueAnimator va = ValueAnimator.ofFloat(1.0f);
        va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float animatedFraction = animation.getAnimatedFraction();
                //获取手指离开点的距离
                PointF pointByPercent = GeometryUtil.getPointByPercent(up_pointF, mStickCenter, animatedFraction);
                //动态设定距离
                setmDragCenter(pointByPercent);
                invalidate();
                System.out.println(animatedFraction);
            }
        });
        va.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                state = State.retain;//保留状态
            }
        });
        //重力回弹
        va.setInterpolator(new OvershootInterpolator(4));
        //时长
        va.setDuration(500);
        va.start();
    }

最后写一下监听回调

  /**
     * 创建粘性控件的消息监听
     */
    public void setOnMessageStateListener(onMessageStateListener listener) {
        monMessageStateListener = listener;
    }

    /**
     * 创建消息监听的回调
     */
    public interface onMessageStateListener {
        /**
         * 正在操作消息
         */
        void onTouchMessage();

        /**
         * 保留消息
         */
        void onRetainMessage();

        /**
         * 清除消息
         */
        void onRemoveMessage();
    }

    /**
     * 响应接口回调
     */
    private void setStateCallBack() {
        if (monMessageStateListener != null) {

            if (state == State.retain) {
                monMessageStateListener.onRetainMessage();
            } else if (state == State.remove) {
                monMessageStateListener.onRemoveMessage();
            } else {
                monMessageStateListener.onTouchMessage();
            }
        }
    }

贴出MainActivity,看一下,怎么调用监听

package com.oblivion.gooview;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

import com.oblivion.ui.GooView;
import com.oblivion.utils.ToastUtils;

public class MainActivity extends AppCompatActivity {

    private GooView view;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        view = new GooView(this);
        setContentView(view);
        view.setOnMessageStateListener(new GooView.onMessageStateListener() {

            @Override
            public void onTouchMessage() {
                ToastUtils.setToast(getApplicationContext(),"触摸");
            }

            @Override
            public void onRetainMessage() {
                ToastUtils.setToast(getApplicationContext(),"保留");
            }

            @Override
            public void onRemoveMessage() {
                ToastUtils.setToast(getApplicationContext(),"清除");
            }
        });
    }
}

github源码下载

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值