025.自定义View中应用贝塞尔曲线

之前一直看QQ的未读消息拖拽消失设计得很好,我一直觉得那个设计很好,他们的UI是真心强,于是,我也一直想写个一样的玩意来玩玩。最近刚好在复习View相关的知识,就拿这个来练手,下面先来看实现的效果图:

这里写图片描述这里写图片描述这里写图片描述
这是我希望实现的效果,这个效果的实现在第二个图能看出一点端倪。这里面的曲线绘制,使用的是贝塞尔曲线。下面用几个例子简单介绍下贝塞尔曲线,参考网上大神的文章,我对原文大神的代码做了一点点修改。

什么是贝塞尔曲线?

贝赛尔曲线(Bézier曲线)是电脑图形学中相当重要的参数曲线。更高维度的广泛化贝塞尔曲线就称作贝塞尔曲面,其中贝塞尔三角是一种特殊的实例。贝塞尔曲线于1962年,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计。贝塞尔曲线最初由Paul de Casteljau于1959年运用de Casteljau算法开发,以稳定数值的方法求出贝塞尔曲线。
接下来从一个简单的二阶贝塞尔曲线开始

接下来从一个简单的二阶贝塞尔曲线开始

package com.example.draftcircleviewtest;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.FrameLayout;
import android.widget.ImageView;
/**
 * 贝塞尔曲线的测试
 * 蓝色的点是辅助点
 * @author Administrator
 *
 */
public class MyBezierView extends FrameLayout {

    private Paint mPaint;
    private Path mPath;
    private Point startPoint;
    private Point endPoint;
    private Point assistPoint;

    private Context mContext;
    public MyBezierView(Context context ) {
        this(context,null);
    }

    public MyBezierView( Context context ,AttributeSet attrs )
    {
        this(context, attrs , 0); 
    }
    public MyBezierView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    ImageView tipImageView;

    private void init( Context context )
    {
        this.mContext = context;
        this.mPaint = new Paint();
        this.mPath = new Path();

        startPoint = new Point(100, 300);
        endPoint = new Point(1100, 300);
        assistPoint = new Point(600, 800);


        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setStrokeWidth(5);

        //设置背景颜色,再设置背景为空,这样子是为了能一开始就draw
        //否则会发现onDraw不被调用
        setBackgroundColor(Color.WHITE);
        getBackground().setAlpha(0);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub


        mPaint.setColor(Color.BLACK);
        mPaint.setStyle( Style.STROKE );
        mPaint.setStrokeWidth( 5);

        mPath.reset();
        Log.e("MyView", "onDraw");
        mPath.moveTo(startPoint.x, startPoint.y);
            // 重要的就是这句
        mPath.quadTo(assistPoint.x, assistPoint.y, endPoint.x, endPoint.y);
           // 画路径
        canvas.drawPath(mPath, mPaint);
            // 画辅助点
        mPaint.setColor(Color.BLUE);
        mPaint.setStrokeWidth( 20);
        canvas.drawPoint(assistPoint.x, assistPoint.y, mPaint);
        canvas.drawPoint(startPoint.x, startPoint.y, mPaint);
        canvas.drawPoint(endPoint.x, endPoint.y, mPaint);

        super.onDraw(canvas);    
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_MOVE:
            assistPoint.x = (int) event.getX();
            assistPoint.y = (int) event.getY();
            invalidate();
            break;

        default:
            break;
        }
        return true;
    }
}

效果:这里写图片描述
这样一个二阶贝塞尔曲线需要三个点,起点,终点,一个辅助点,初始化画笔以后,我们就可以在onDraw上绘制曲线,很简单,就是如下代码:

  mPath.moveTo(startPoint.x, startPoint.y);
            // 重要的就是这句
        mPath.quadTo(assistPoint.x, assistPoint.y, endPoint.x, endPoint.y);
           // 画路径
        canvas.drawPath(mPath, mPaint);

首先我们把画笔移动到起点,然后,在路线上设置为贝塞尔曲线,填入辅助点和终点,最后在canvas上画出就可以了。拖动的效果实现也很简单,我们重写onTouchEvent,捕捉keydown事件和keyup事件,在事件发生的时候,修改assistPoint位置为事件发生所在的位置就好了。

接下来我们绘制一条复杂一点的贝塞尔曲线。
这条曲线,是由2条二阶贝塞尔,2条三节贝塞尔曲线组成的。

思路

先根据相邻点(P1,P2, P3)计算出相邻点的中点(P4, P5),然后再计算相邻中点的中点(P6)。然后将(P4,P6, P5)组成的线段平移到经过P2的直线(P8,P2,P7)上。接着根据(P4,P6,P5,P2)的坐标计算出(P7,P8)的坐标。最后根据P7,P8等控制点画出三阶贝塞尔曲线。
点和线的解释

黑色点:要经过的点,例如温度 蓝色点:两个黑色点构成线段的中点 黄色点:两个蓝色点构成线段的中点 灰色点:贝塞尔曲线的控制点 红色线:黑色点的折线图 黑色线:黑色点的贝塞尔曲线,也是我们最终想要的效果。

那么我们知道,我们绘制贝塞尔曲线,除了需要知道p1 p2 p3 还需要知道 P8 P7 P9 (P5下面的那个红点当成是P9吧),现在的情况是,我们已经知道了P1,P2,,P3,我们需要知道P7 P8 ,其实P7 P8不是固定值,只要确保P7 P8 P9 不在线上就,那么我们可以这样来计算P7 P8 P9,我们先算中点,得到P4 P5的值,然后,我们取P6 的值,然后,我们知道P4 P5 P6 在同一条直线上,我们平移P4 P5 P6,让P6到P2位置,得到P7 P8 ,这样,只要P1、P2、P3 不在同一直线上,那么P8 也不会和P1 P2 在同一直线上,P7也不会和P2 P3在同一直线上。按照这个思路,我们可以知道:

   P8 = P2 - P6 + P4。

实现的代码如下:

package com.example.draftcircleviewtest;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import android.widget.FrameLayout;

public class MultiBezierView extends FrameLayout {

    private static final int LINEWIDTH = 5;
    private static final int POINTWIDTH = 10;

    private Paint mPaint;
    private Path mPath;

    private Context mContext;
     /** 即将要穿越的点集合 */
    private ArrayList<Point> mPoints;
     /** 中点集合 */
    private ArrayList<Point> mMidPoints;
     /** 中点的中点集合 */
    private ArrayList<Point> mMidMidPoints;
     /** 移动后的点集合(控制点) */
    private ArrayList<Point> mAssistPoints;


    public MultiBezierView(Context context) {
        this(context, null);
    }
    public MultiBezierView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public MultiBezierView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mContext = context;
        init();
    }

    private void init()
    {
        mPaint = new Paint();
        mPath = new Path();

        mPaint.setStyle( Style.STROKE );
        mPaint.setStrokeWidth( 5 );
        mPaint.setDither(true);
        mPaint.setAntiAlias( true );

        getScreenParams();
        setBackgroundColor(Color.WHITE);
        getBackground().setAlpha(0);
        initPoints();
        initMidPoints();
        initMidMidPoints();
        initAssistPoints( mPoints , mMidPoints , mMidMidPoints );
    }

    private void initPoints()
    {
        mPoints = new ArrayList<Point>();
        int pointWidthSpace = mScreenWidth / 5;
        int pointHeightSpace = 100;

        for( int i = 0 ; i < 5 ; i ++ )
        {
             Point point;
                // 一高一低五个点
                if (i%2 != 0) {
                    point = new Point((int) (pointWidthSpace*(i + 0.5)), mScreenHeigh/2 - pointHeightSpace);
                } else {
                    point = new Point((int) (pointWidthSpace*(i + 0.5)), mScreenHeigh/2);
                }


             mPoints.add(point);
        }

    }

    private void initMidPoints()
    {
        mMidPoints = new ArrayList<Point>();
        for( int i = 0 ; i < mPoints.size() -1 ; i ++ )
        {
            Point midPoint = new Point(( mPoints.get(i).x + mPoints.get( i + 1 ).x )/2, 
                    (mPoints.get(i).y + mPoints.get(i + 1).y)/2);
            mMidPoints.add(midPoint);
        }
    }

    private void initMidMidPoints()
    {
        mMidMidPoints = new ArrayList<Point>();

        for( int i = 0 ; i < mMidPoints.size()-1; i ++ )
        {
            Point midMidPoint = new Point( (mMidPoints.get(i).x + mMidPoints.get(i+1).x )/2,
                    (mMidPoints.get(i).y + mMidPoints.get(i+1).y )/2);

            mMidMidPoints.add(midMidPoint);
        }
    }

    private void initAssistPoints(List<Point> points , List<Point> midPoints , List<Point> midMidPoints ) 
    {
        mAssistPoints = new ArrayList<Point>();

        for( int i = 0 ; i < points.size() ; i ++ )
        {
            if( i ==0 || i == points.size()-1   )
            {
                continue;
            }else
            {
                Point assistBefore = new Point();
                assistBefore.x = midPoints.get(i-1).x + (points.get(i).x- mMidMidPoints.get(i-1).x);
                assistBefore.y = midPoints.get(i-1).y + (points.get(i).y- mMidMidPoints.get(i-1).y);

                Point assistAfter = new Point();
                assistAfter.x = midPoints.get(i).x + (points.get(i).x- mMidMidPoints.get(i-1).x);
                assistAfter.y = midPoints.get(i).y + (points.get(i).y- mMidMidPoints.get(i-1).y);
                mAssistPoints.add(assistBefore);
                mAssistPoints.add(assistAfter);
            }
        }
    }


    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        //画原始点
        drawPoints(canvas);
        //画原始点连接的直线
        drawLineForPoints(canvas);
        //画中点
        drawMidPoints(canvas);
        //画中点的中点即图中的P6
        drawMidMidPoints(canvas);
        //画辅助点
        drawAssistPoints( canvas);
        drawBezierPath( canvas );
    }

    private void drawPoints(Canvas canvas)
    {
        mPaint.setStrokeWidth(POINTWIDTH);

        for( Point point : mPoints)
        {
            canvas.drawPoint(point.x, point.y, mPaint);

        }
    }

    private void drawLineForPoints( Canvas canvas )
    {
        mPaint.setStrokeWidth(LINEWIDTH);
        mPaint.setColor(Color.RED );
        mPath.moveTo(mPoints.get(0).x, mPoints.get(0).y);
        for( int i = 1 ; i < mPoints.size(); i ++ )
        {

            mPath.lineTo(mPoints.get(i).x, mPoints.get(i).y);
        }
        canvas.drawPath(mPath, mPaint);
    }

    private void drawMidPoints(Canvas canvas)
    {
        mPaint.setStrokeWidth(POINTWIDTH);
        mPaint.setColor(Color.BLUE);
        for( Point point : mMidPoints)
        {
            canvas.drawPoint(point.x, point.y, mPaint);

        }
    }
    private void drawMidMidPoints(Canvas canvas)
    {
        mPaint.setStrokeWidth(POINTWIDTH);
        mPaint.setColor(Color.YELLOW);
        for( Point point : mMidMidPoints)
        {
            canvas.drawPoint(point.x, point.y, mPaint);

        }
    }

    private void drawAssistPoints(Canvas canvas)
    {
        mPaint.setStrokeWidth(POINTWIDTH);
        mPaint.setColor(Color.GRAY);
        for( Point point : mAssistPoints)
        {
            canvas.drawPoint(point.x, point.y, mPaint);

        }
    }

    private void drawBezierPath( Canvas canvas )
    {
        mPaint.setStrokeWidth(LINEWIDTH);
        mPaint.setColor(Color.BLACK);
        mPath.reset();
        mPath.moveTo(mPoints.get(0).x, mPoints.get(0).y);

        for( int i = 0 ; i < mPoints.size()-1 ; i ++ )
        {
            if( i == 0 )
            {
                // 第一条为二阶贝塞尔
                mPath.quadTo(mAssistPoints.get(i).x, mAssistPoints.get(i).y, mPoints.get(i+1).x, mPoints.get(i+1).y);
            }
            else//一直到最后一条之前,是三阶贝塞尔曲线
                if( i < mPoints.size() -2 )
                {
                    mPath.cubicTo(mAssistPoints.get(i*2-1).x,mAssistPoints.get(i*2-1).y,
                            mAssistPoints.get(i*2).x, mAssistPoints.get(i*2).y,
                             mPoints.get(i+1).x, mPoints.get(i+1).y);

                }
                else//最后一条也是二阶贝塞尔曲线
                    if( i  == mPoints.size() -2 )
                    {
                        mPath.quadTo(mAssistPoints.get(mAssistPoints.size()-1).x, mAssistPoints.get(mAssistPoints.size()-1).y, mPoints.get(i+1).x, mPoints.get(i+1).y);

                    }

        }
        canvas.drawPath(mPath, mPaint);
    }


     public  int mScreenWidth=-1,mScreenHeigh=-1;


     private void getScreenParams( )
     {
         getWindowHeigh(mContext);
         getWindowWidth(mContext);
     }


    public int getWindowWidth(Context context) {
        if( mScreenWidth <= 0 )
        {
            WindowManager wm = (WindowManager) (context
                    .getSystemService(Context.WINDOW_SERVICE));
            DisplayMetrics dm = new DisplayMetrics();
            wm.getDefaultDisplay().getMetrics(dm);
             mScreenWidth = dm.widthPixels;
        }
        return mScreenWidth;
    }


    public int getWindowHeigh(Context context) {

        if( mScreenHeigh <=0 )
        {
            WindowManager wm = (WindowManager) (context
                    .getSystemService(Context.WINDOW_SERVICE));
            DisplayMetrics dm = new DisplayMetrics();
            wm.getDefaultDisplay().getMetrics(dm);
            mScreenHeigh = dm.heightPixels;
        }
        return mScreenHeigh;
    }

}

接下来,我们开始设计粘性的动画,我们想要的效果图如下:
这里写图片描述
其实,这个可以看成是两个圆+2条二阶贝塞尔曲线,经过分析,我们可以得到如下:
这里写图片描述

那么,如今的问题就是求垂直于圆心连线的线与圆的交点坐标 PA PB PC PD 这四个点坐标。
我们很容易证明如下:
点PA的圆心角满足如下图:
这里写图片描述

我们很容易得到如下:
∠α=arctan( (y0-y1)/( x1 - x0 ) ) = -arctan((y1-y0)/(x1-x0))
另γ = -∠α = arctan((y1-y0)/(x1-x0))
令∠β = 270°-∠α ,也就是β = 270°+γ β为点A的圆心角
B的圆心角为90-∠α
xA = x0+ r * cos( 270° - ∠α ) =x0+ r*(-sin∠α )= x0-r*sin∠α = x0+r*sin( -∠α ) = x0 + r* sin( arctan((y1-y0)/(x1-x0)) ) = x0 + r * sin γ
yA = x0 + r*sin(270° - ∠α) = x0 -r*cosa = x0 - r*cosγ
同理可以得到B坐标
xB = x0-r*sinγ
yB = x0 + r*cosγ

所以我们可以得到下面的代码**(完整的项目代码在文章末尾,包括本文所有的代码)**:
 /**大圆的半径*/
    public static final int BIG_CIRCLE_RADIUS = 40;
    /**小圆心,固定不变的*/
    private Point smallCircleCenter;
    /**大圆心*/
    private Point bigCircleCenter;
    /**小圆上的两个点,他们的连线垂直两个圆心的连线。*/
    private Point smallCirclePoint1,smallCirclePoint2;
    /**大圆上的两个点,他们的连线垂直两个圆心的连线。*/
    private Point bigCirclePoint1,bigCirclePoint2;
    /**当前的圆心距*/
    private float centersDistance;
    /**当前小圆的半径*/
    private int smallCircleRadius = SMALL_CIRCLE_MAX_RADIUS;


private void calculate()
    {
        if( bigCircleCenter == null )
        return ;

        centersDistance = (float) Math.sqrt(Math.pow(smallCircleCenter.x-bigCircleCenter.x,2) + Math.pow(smallCircleCenter.y-bigCircleCenter.y,2)) ;

        if( centersDistance >= CENTERS_MAX_DISTANCE )
            smallDismiss = true;

        int smalldx = (int) (centersDistance/CENTERS_MAX_DISTANCE * ( SMALL_CIRCLE_MAX_RADIUS - SMALL_CIRCLE_MIN_RADIUS ));
        smallCircleRadius = SMALL_CIRCLE_MAX_RADIUS- smalldx;

        double s1 = 90;    
        if( bigCircleCenter.x != smallCircleCenter.x )
            s1 =  Math.atan( (bigCircleCenter.y-smallCircleCenter.y)/( bigCircleCenter.x-smallCircleCenter.x));

        float smallOffsetX = (float) (smallCircleRadius * Math.sin(s1));
        float smallOffsetY = (float) (smallCircleRadius * Math.cos(s1));

        smallCirclePoint1 = new Point();
        smallCirclePoint1.x = (int) (smallOffsetX + smallCircleCenter.x);
        smallCirclePoint1.y = (int) (smallCircleCenter.y - smallOffsetY);

        smallCirclePoint2 = new Point();
        smallCirclePoint2.x = (int) ( smallCircleCenter.x-smallOffsetX);
        smallCirclePoint2.y = (int) (smallCircleCenter.y + smallOffsetY);

        float bigOffsetX = (float) (BIG_CIRCLE_RADIUS * Math.sin(s1));
        float bigOffsetY = (float) (BIG_CIRCLE_RADIUS * Math.cos(s1));

        bigCirclePoint1 = new Point();
        bigCirclePoint1.x = (int) (bigOffsetX + bigCircleCenter.x);
        bigCirclePoint1.y = (int) (bigCircleCenter.y - bigOffsetY);

        bigCirclePoint2 = new Point();
        bigCirclePoint2.x = (int) ( bigCircleCenter.x - bigOffsetX);
        bigCirclePoint2.y = (int) (bigCircleCenter.y + bigOffsetY);

        midCenter = new Point();
        midCenter.x = (bigCircleCenter.x + smallCircleCenter.x )/2;
        midCenter.y = (bigCircleCenter.y + smallCircleCenter.y )/2;
    }

    接下来就是绘制贝塞尔曲线,注意绘制的时候, 要使用lineTo,连接圆上的两个点,这样才能形成一个封闭的区间,之后才能填充颜色。
/**
     * 绘制贝塞尔曲线
     * @param canvas
     */
    private void drawBezierPath( Canvas canvas )
    {
        mPaint.setStrokeWidth(PAINT_LINE_WIDTH);
        mPaint.setColor(paintColor);

        mPath.reset();
        //下面的lineTo不可以少,因为我们要形成一个封闭的空间,这样才能填充
        mPath.moveTo( smallCirclePoint1.x, smallCirclePoint1.y);
        mPath.quadTo(midCenter.x, midCenter.y, bigCirclePoint1.x, bigCirclePoint1.y);
        mPath.lineTo(bigCirclePoint2.x, bigCirclePoint2.y);
         mPath.quadTo(midCenter.x, midCenter.y, smallCirclePoint2.x, smallCirclePoint2.y);
        mPath.lineTo(smallCirclePoint1.x, smallCirclePoint1.y);
        canvas.drawPath(mPath, mPaint);
    }

当我们设置Paint的Style为Stroke的时候
上面的代码效果如下:
这里写图片描述

当我们设置
mPaint.setStyle( Paint.Style.FILL_AND_STORKE);
填充效果如下:
这里写图片描述

接下来我们画圆以后效果:
这里写图片描述

再加入动画效果,代码如下:

//动画对象
ObjectAnimator animator; 
    //是否重复播放
    boolean isrepeat = false;
    public void startAni( )
    {
        if(  lastf == 0.0f)
        {    
            animator = ObjectAnimator.ofFloat(this, "bigCircleCenter", 0, 2.0f);
            animator.setRepeatCount(ValueAnimator.INFINITE);
            isrepeat = true;
        }else 
        {    
            animator = ObjectAnimator.ofFloat(this, "bigCircleCenter", lastf, 2.0f);
            isrepeat = false;
        }
        animator.setDuration( (int)(2000*(( 2.0f - lastf)/2.0f)));

        animator.start();
    }
//上次播放动画的位置,当lastf<=1.0f执行动画,当lastf>1.0f的时候,播放逆动画 
float lastf = 0.0f;
   public void setBigCircleCenter( float f )
    {
        int center =0;
        int smalldx = 0;
        if( f <= 1.0f){
           //计算大圆圆心的位置,这个情况下圆心位置在增大
            center = (int) (f * 800 + 400);
             //计算小圆半径缩小值,这个时候值在增大
            smalldx = (int) ((SMALL_CIRCLE_MAX_RADIUS - SMALL_CIRCLE_MIN_RADIUS)*f); 
        }else{
        //计算大圆圆心的位置,这个值在变小
            center = (int)(1200-(f-1.0f)*800);
           //计算小圆半径缩小值,这个时候值在变小
            smalldx = (int) ((SMALL_CIRCLE_MAX_RADIUS - SMALL_CIRCLE_MIN_RADIUS)-(SMALL_CIRCLE_MAX_RADIUS - SMALL_CIRCLE_MIN_RADIUS)*(f-1.0f));
        }
        bigCircleCenter.x = center;
        bigCircleCenter.y = center;
        smallCircleRadius = SMALL_CIRCLE_MAX_RADIUS- smalldx;

        lastf = f;

        invalidate();
        //暂停恢复动画以后,先执行一次动画,动画结束以后,关闭当前动画,
        //再启动新的循环动画
        if( !isrepeat)
        {

            if( lastf ==0.0f || lastf == 2.0f )
            {
                stopAni();
                lastf = 0;
                startAni();
            }
        }
    }

当Paint的Style为Stroke效果如下:
这里写图片描述

设置Paint的Style为FILL_AND_STROKE,并且隐藏点,得到效果:
这里写图片描述

拖拽的实现也很简单,我们重写onTouchEvent事件,在Down和Move的时候,修改大圆心的位置,当移动太远,就隐藏小圆,在ACTION_UP的时候判断圆心距,如果圆心距不在指定范围内,就不显示大圆了,否则,就把大圆回归原位,同时如果小圆还显示着,就执行动画。下面只放出关键的代码:
onTouchEvent实现代码如下:

@Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            if( bigCircleCenter == null )
                bigCircleCenter = new Point();

            bigCircleCenter.x = (int) event.getX();
            bigCircleCenter.y = (int) event.getY();

            calculate();
            if( centersDistance < SMALL_CIRCLE_MAX_RADIUS + 20 )
                invalidate();
            else{
                bigCircleCenter =null;
                return false;
            }
            break;
        case MotionEvent.ACTION_MOVE:
            if( bigCircleCenter == null )
                bigCircleCenter = new Point();

            bigCircleCenter.x = (int) event.getX();
            bigCircleCenter.y = (int) event.getY();
            invalidate();
            break;
        case MotionEvent.ACTION_UP: 

            if( bigCircleCenter == null || ( bigCircleCenter.x == beforeX && bigCircleCenter.y == beforeY) )
            {
                return false;
            }

            if( smallDismiss && centersDistance < CENTERS_MIN_DISTANCE )
            {
                smallDismiss = false;
                bigCircleCenter = new Point(beforeX, beforeY);
                invalidate();

            }else if( !smallDismiss)
            {
                float f = centersDistance / CENTERS_MAX_DISTANCE ;
                animator = ObjectAnimator.ofFloat(this, "bigCircleCenter", 0, 1.0f);
                animator.setDuration( (int)(1000 * f));
                animator.start();
            }else 
            {
                stopAni();
                bigCircleCenter = null;
                invalidate();
            }
            break;
        default:
            break;
        }

        return true;
    }

修改前面的动画执行代码改为如下:
/**
* 松开的时候,执行回复的动画
* @param f
*/
public void setBigCircleCenter( float f )
{
int centerX =0;
int centerY =0;

    if( bigCircleCenter.x == 45&&bigCircleCenter.y == 45 )
    {
        stopAni();
        afterAni();
        return ;
    }
    //计算新的中心点
    centerX = (int) ( bigCircleCenter.x + (beforeX - bigCircleCenter.x )*f);
    centerY = (int) ( bigCircleCenter.y + (beforeY - bigCircleCenter.y )*f);

    bigCircleCenter.x = centerX;
    bigCircleCenter.y = centerY;         

    invalidate();

    if( f == 1.0f )
    {

        afterAni();
    }
}

下面看下最终的效果:
这里写图片描述
这个控件还是做的比较简单,像腾讯那样做的话,我们还需要重写onLayout和onSizeChanged方法保存当前控件的布局参数,然后再使用WindowManager在触摸的时候添加到窗口上,并且窗口是整个屏幕大小的,这样就实现气泡拖拽全局的效果。还可以扩展一些事件接口,这样更灵活,这些这边就不实现了,以后有空完善了,再发一份。

代码还有一些BUG,仅供参考。附上项目的源码地址:
代码上的onTouchEvent有问题,请改成博客上的。
http://download.csdn.net/detail/savelove911/9626154

猜你喜欢

转载自blog.csdn.net/savelove911/article/details/52486331
今日推荐