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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章