1. 程式人生 > >Android中Path類的lineTo方法和quadTo方法畫線的區別

Android中Path類的lineTo方法和quadTo方法畫線的區別

   當我們需要在螢幕上形成畫線時,Path類的應用是必不可少的,而Path類的lineToquadTo方法實現的繪製線路形式也是不一樣的,下面就以程式碼的實現來直觀的探究這兩個方法的功能實現區別;

   1. Path--->quadTo(float x1, float y1, float x2, float y2):

  該方法的實現是當我們不僅僅是畫一條線甚至是畫弧線時會形成平滑的曲線,該曲線又稱為"貝塞爾曲線"(Bezier curve),其中,x1,y1為控制點的座標值,x2,y2為終點的座標值;

    貝塞爾曲線的形成,就比如我們把一條橡皮筋拉直,橡皮筋的頭尾部對應起點和終點,然後從拉直的橡皮筋中選擇任意一點(除頭尾對應的點外)扯動橡皮筋形成的彎曲形狀,而那個扯動橡皮筋的點就是控制點;

    下就面以一個Demo來結合理解quadTo函式的應用,程式碼如下:

       1).自定義View:

package com.feixun.hu.pt;

import android.content.Context;
import android.gesture.GestureStroke;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Paint.Style;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

public class DrawingWithBezier extends View
{
    private float mX;
    private float mY;

    private final Paint mGesturePaint = new Paint();
    private final Path mPath = new Path();
    
    public DrawingWithBezier(Context context)
    {
        super(context);
        mGesturePaint.setAntiAlias(true);
        mGesturePaint.setStyle(Style.STROKE);
        mGesturePaint.setStrokeWidth(5);
        mGesturePaint.setColor(Color.WHITE);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        // TODO Auto-generated method stub
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                touchDown(event);
                 break;
            case MotionEvent.ACTION_MOVE:
                touchMove(event);
        }
        //更新繪製
        invalidate();
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        //通過畫布繪製多點形成的圖形
        canvas.drawPath(mPath, mGesturePaint);
    }

    //手指點下螢幕時呼叫
    private void touchDown(MotionEvent event)
    {
      
        //mPath.rewind();
    	//重置繪製路線,即隱藏之前繪製的軌跡
        mPath.reset();
        float x = event.getX();
        float y = event.getY();
        
        mX = x;
        mY = y;
        //mPath繪製的繪製起點
        mPath.moveTo(x, y);
    }
    
    //手指在螢幕上滑動時呼叫
    private void touchMove(MotionEvent event)
    {
        final float x = event.getX();
        final float y = event.getY();

        final float previousX = mX;
        final float previousY = mY;

        final float dx = Math.abs(x - previousX);
        final float dy = Math.abs(y - previousY);
        
        //兩點之間的距離大於等於3時,生成貝塞爾繪製曲線
        if (dx >= 3 || dy >= 3)
        {
            //設定貝塞爾曲線的操作點為起點和終點的一半
            float cX = (x + previousX) / 2;
            float cY = (y + previousY) / 2;

            //二次貝塞爾,實現平滑曲線;previousX, previousY為操作點,cX, cY為終點
            mPath.quadTo(previousX, previousY, cX, cY);

            //第二次執行時,第一次結束呼叫的座標值將作為第二次呼叫的初始座標值
            mX = x;
            mY = y;
        }
    }
    
}

     2).MainActivity:

package com.feixun.hu.pt;

import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;

public class MainActivity extends Activity
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        //setContentView(new MySurfaceView(this));
        setContentView(new DrawingWithBezier(this));
        //setContentView(new DrawingWithoutBezier(this));      
    }
}

該Demo實現使用者在手機螢幕上滑動手指時,可根據手指滑動的位置繪製出相應的線條,類似輸入法手勢的繪製,所以程式碼中的畫筆Paint命名為mGesturePaint;

比如,我們在螢幕上繪製S這個圖案,則形成的圖案如下: 

           

   2. Path--->lineTo(float x, float y) :

該方法實現的僅僅是兩點連成一線的繪製線路,這樣,當我們用這個方法繪製曲線時,缺陷就出來了;下面的例子,同樣還是和上面的Demo差不多,只不過Path呼叫的是lineTo方法,如下:

       1). 自定義View:

package com.feixun.hu.pt;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Bitmap.Config;
import android.graphics.Paint.Style;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

public class DrawingWithoutBezier extends View
{
    private float mX;
    private float mY;

    private final Paint mGesturePaint = new Paint();
    private final Path mPath = new Path();
    
    public DrawingWithoutBezier(Context context)
    {
        super(context);
        mGesturePaint.setAntiAlias(true);
        mGesturePaint.setStyle(Style.STROKE);
        mGesturePaint.setStrokeWidth(5);
        mGesturePaint.setColor(Color.WHITE);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        // TODO Auto-generated method stub
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                touchDown(event);
                 break;
            case MotionEvent.ACTION_MOVE:
                touchMove(event);
        }
        //更新繪製
        invalidate();
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        canvas.drawPath(mPath, mGesturePaint);
    }

    //手指點下螢幕時呼叫
    private void touchDown(MotionEvent event)
    {
      
        //mPath.rewind();
        mPath.reset();
        float x = event.getX();
        float y = event.getY();
        
        mX = x;
        mY = y;
        
        //mPath繪製的繪製起點
        mPath.moveTo(x, y);
    }
    
    //手指在螢幕上滑動時呼叫
    private void touchMove(MotionEvent event)
    {
        final float x = event.getX();
        final float y = event.getY();

        final float previousX = mX;
        final float previousY = mY;

        final float dx = Math.abs(x - previousX);
        final float dy = Math.abs(y - previousY);
        
        //兩點之間的距離大於等於3時,連線連線兩點形成直線
        if (dx >= 3 || dy >= 3)
        {
        	//兩點連成直線
            mPath.lineTo(x, y);
            
            //第二次執行時,第一次結束呼叫的座標值將作為第二次呼叫的初始座標值
            mX = x;
            mY = y;
        }
    }
    
}

       2).MainActivity:

package com.feixun.hu.pt;

import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;

public class MainActivity extends Activity
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        //setContentView(new MySurfaceView(this));
        //setContentView(new DrawingWithBezier(this));
        setContentView(new DrawingWithoutBezier(this));      
    }
}
同樣地,用該例子繪製S形圖案,形成的圖案如下:

         

   結論 :對比前面quadTo方法繪製的S,lineTo繪製的S在彎曲部分很明顯的不能形成平滑的彎曲,會出現明顯的兩點形成一線的突痕。可能圖片看的不是清楚,自行執行這個Demo,然後在螢幕上繪製彎曲曲線或者圓,對比檢視他們的形狀區別就一目瞭然;

   3. SurfaceView繪製貝塞爾曲線:

       上面的繪製圖案方式都是基於View來繪製,當然,我們也可以結合SurfaceView和Rect來實現繪製貝塞爾曲線,這樣繪製的效果相對會比較好,而且效率也相對較高,畢竟相對SurfaceView而言,在動態繪製點線方面較之View更加出色;

       如下程式碼:

      1).自定義SurfaceView:

package com.feixun.hu.pt;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Paint.Style;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MySurfaceView extends SurfaceView 
{
    private Context mContex;
    private float mX;
    private float mY;

    private SurfaceHolder sfh;
    private Canvas canvas;
    private float mCurveEndX;
    private float mCurveEndY;

    private final Paint mGesturePaint = new Paint();
    private final Path mPath = new Path();
    private final Rect mInvalidRect = new Rect();
    
    private boolean isDrawing;

    public MySurfaceView(Context context)
    {
        super(context);
        mContex = context;
        sfh = this.getHolder();
        mGesturePaint.setAntiAlias(true);
        mGesturePaint.setStyle(Style.STROKE);
        mGesturePaint.setStrokeWidth(5);
        mGesturePaint.setColor(Color.WHITE);
        // TODO Auto-generated constructor stub
    }

    public void drawCanvas() {
        try {
            canvas = sfh.lockCanvas();
            if (canvas != null) {
                canvas.drawColor(Color.BLACK);
                canvas.drawPath(mPath, mGesturePaint);
            }
        } catch (Exception e) {
            // TODO: handle exception
        } finally {
            if (canvas != null)
                sfh.unlockCanvasAndPost(canvas);
        }
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        // TODO Auto-generated method stub
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                touchDown(event);
                invalidate();
                return true;

            case MotionEvent.ACTION_MOVE:
                if (isDrawing)
                {
                    Rect rect = touchMove(event);
                    if (rect != null) {
                        invalidate(rect);
                    }
                    return true;
                }           
                break;
            case MotionEvent.ACTION_UP:
                if (isDrawing)
                {
                    touchUp(event);
                    invalidate();
                    return true;
                }
                break;        
        }
        return super.onTouchEvent(event);
    }

    private void touchDown(MotionEvent event)
    {
        isDrawing = true;
        mPath.reset();
        float x = event.getX();
        float y = event.getY();
        
        mX = x;
        mY = y;
        
        mPath.moveTo(x, y);
        
        mInvalidRect.set((int) x, (int) y, (int) x , (int) y);
        mCurveEndX = x;
        mCurveEndY = y;
    }
    
    private Rect touchMove(MotionEvent event)
    {
        Rect areaToRefresh = null;

        final float x = event.getX();
        final float y = event.getY();

        final float previousX = mX;
        final float previousY = mY;

        final float dx = Math.abs(x - previousX);
        final float dy = Math.abs(y - previousY);
        
        if (dx >= 3 || dy >= 3)
        {
            areaToRefresh = mInvalidRect;
            areaToRefresh.set((int) mCurveEndX , (int) mCurveEndY ,
                    (int) mCurveEndX, (int) mCurveEndY);
            
          //設定貝塞爾曲線的操作點為起點和終點的一半
            float cX = mCurveEndX = (x + previousX) / 2;
            float cY = mCurveEndY = (y + previousY) / 2;

            //實現繪製貝塞爾平滑曲線;previousX, previousY為操作點,cX, cY為終點
            mPath.quadTo(previousX, previousY, cX, cY);
            //mPath.lineTo(x, y);

            // union with the control point of the new curve
            /*areaToRefresh矩形擴大了border(寬和高擴大了兩倍border),
             * border值由設定手勢畫筆粗細值決定
             */
            areaToRefresh.union((int) previousX, (int) previousY,
                    (int) previousX, (int) previousY);
           /* areaToRefresh.union((int) x, (int) y,
                    (int) x, (int) y);*/

            
            // union with the end point of the new curve
            areaToRefresh.union((int) cX, (int) cY ,
                    (int) cX, (int) cY);

            //第二次執行時,第一次結束呼叫的座標值將作為第二次呼叫的初始座標值
            mX = x;
            mY = y;
            drawCanvas();
        }
        return areaToRefresh;
    }
    
    private void touchUp(MotionEvent event)
    {
        isDrawing = false;
    }
}

      2). MainActivity:

package com.feixun.hu.pt;

import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;

public class MainActivity extends Activity
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(new MySurfaceView(this));
        //setContentView(new DrawingWithBezier(this));
        //setContentView(new DrawingWithoutBezier(this));      
    }
}