白話經典貝塞爾曲線及其在 Android 中的應用

1、前言

談到貝塞爾曲線可能很多人會浮現它高大上的數學公式。然而,在實際應用中,並不須要咱們去徹底理解或者推導出公式才能應用得上。實際狀況是,即便真的只是一個學渣,咱們應該也能很輕鬆的掌握貝塞爾曲線的大體原理及其在開發中的實際應用。html

2、白話貝塞爾曲線的原理

貝塞爾曲線有一階、二階、三階....一直到 N 階。實際應用中咱們經常使用的是二階、三階,高階能夠由低階來實現。我們這裏以二階爲例來說解一下貝塞爾曲線的基本原理。git

二階貝塞爾曲線三要素github

1 個起點,1 個終點,1 個控制點canvas

這個是咱們要知道的第一個知識點,而三階的話就是 2 個控制點,四階的話就是 3 個,以此類推,N 階的話就是 N - 1 個控制點。而起點和終點始終只有一個。bash

下面咱們來手動畫一個貝塞爾曲線。app

  1. 繪製 1 個起點,1 個終點和 1 個控制點,分別爲 S 、E、C。而後將 SC、CE 分別連線。以下圖所示。

二階貝塞爾一.jpg

  1. 從點 S 向 C 出發找到一個 D 點,從 C 向 E 出發找到一個 F 點,使得

SD / SC = CF / CEpost

而後鏈接 DF。以下圖所示。動畫

二階貝塞爾二.jpg

  1. 在 DF 之間找到點 M,使得

SD / SC = CF / CE = DM / DFui

二階貝塞爾三.jpg

總結下: (1) 二階貝塞爾中,起初是 3 個點,而後咱們再找 2 個點,而後再找 1 個點。這個點就是咱們要找到的點。 (2) 咱們須要由 S 向 C 出發,由 C 向 E 出現,找到全部的 D 和 F,再找到全部的 M。 (3) 將全部的 M 鏈接起來就構造出了最後的所須要的貝塞爾曲線了。this

對於更高階的三階甚至N階,其過程是同樣的。再借用一個圖,來詳細觀察一下其構造的過程。

二階貝塞爾曲線.gif

關於貝塞爾曲線的原理,介紹這麼多就夠了,再說就是多餘。若是實在還不明白或者想深刻的,推薦如下連接。固然,更建議本身多琢磨琢磨。

貝塞爾曲線 總結 貝塞爾曲線掃盲 貝塞爾曲線遊戲 bezier-circle

3、貝塞爾曲線在 Android 自定義 View 中的實戰

Android 中提供了一個 Path 類,其有 2 個方法 Path#quadTo() 和 Path#cubicTo()。分別用於構造二階和三階貝塞爾曲線的。其原型分別以下。

quadTo() 方法

/**
     * Add a quadratic bezier from the last point, approaching control point
     * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
     * this contour, the first point is automatically set to (0,0).
     *
     * @param x1 The x-coordinate of the control point on a quadratic curve
     * @param y1 The y-coordinate of the control point on a quadratic curve
     * @param x2 The x-coordinate of the end point on a quadratic curve
     * @param y2 The y-coordinate of the end point on a quadratic curve
     */
    public void quadTo(float x1, float y1, float x2, float y2) 
複製代碼

cubicTo() 方法

/**
     * Add a cubic bezier from the last point, approaching control points
     * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
     * made for this contour, the first point is automatically set to (0,0).
     *
     * @param x1 The x-coordinate of the 1st control point on a cubic curve
     * @param y1 The y-coordinate of the 1st control point on a cubic curve
     * @param x2 The x-coordinate of the 2nd control point on a cubic curve
     * @param y2 The y-coordinate of the 2nd control point on a cubic curve
     * @param x3 The x-coordinate of the end point on a cubic curve
     * @param y3 The y-coordinate of the end point on a cubic curve
     */
    public void cubicTo(float x1, float y1, float x2, float y2,
                        float x3, float y3)
複製代碼

參數中,後面的點表明終點,中間的點表明控制點。看起來高大上的貝塞爾曲線,在 Android 中咱們主要掌握這 2 個方法的應用基本就可拿下了。固然,實際開發過程當中,還有其餘問題須要解決,好比拆解,尋找起點,終點和控制點等有時候也是技術活。

下面來看兩個實際的案例。

完整的 demo github.com/ly20050516/…。對於不喜歡看文字講解的,能夠直接下載源碼進行調試看效果。

3.1 經典案例 —— 流動的水波

先上效果圖。

wave.gif

主要步驟以下:

流動的水波.jpg

核心代碼以下:

// 重置 path
        path.reset();
        // 將 path 移到起點 (0,h)
        path.moveTo(0,h);
        // 繪製第 1 部分,終點爲 (w / 2,h),控制點爲 (w / 4,h + WAVE_AMPLITTUDE),獲得一條下凹的曲線
        path.quadTo(w / 4,h + WAVE_AMPLITTUDE,w / 2,h);
        // 第 2 部分再以 (w / 2,h) 爲起點,以 (w,h) 爲終點,以 (w * 3 / 4,h - WAVE_AMPLITTUDE) 爲控制點,獲得一條上凸的曲線
        path.quadTo(w * 3 / 4,h - WAVE_AMPLITTUDE,w,h);
        // 第 3 部分和第 4 部分就是重複第 1 部分和第 2 部分。只是注意座標的計算
        path.quadTo(w * 5 / 4,h + WAVE_AMPLITTUDE,w * 3 / 2,h);
        path.quadTo(w * 7 / 4,h - WAVE_AMPLITTUDE,w * 2,h);
        // 而後將 path 封閉獲得一填充區域
        path.lineTo(w * 2,getHeight());
        path.lineTo(0,getHeight());
        path.close();

        // 下面的 offset 由屬性動畫來控制其值,變化範圍爲 (0,width)
        matrix.reset();
        // 隨着動畫的不斷更新來變換 path 的 offset,從而造成流動的動畫
        matrix.postTranslate(-offset,0);
        path.transform(matrix);

        // 最後繪製出須要的曲面,對,不是曲線了
        canvas.drawPath(path,paint);
複製代碼

3.2 經典案例 —— 仿 QQ 拖拽清除 tips

效果圖以下

qq.gif

主要步驟:

仿 QQ 擦除.jpg

核心代碼:

// 計算 2 點之間的距離
        float distance = (float) Math.sqrt(Math.pow((tipsViewMoveX - tipsViewX), 2) + Math.pow((tipsViewMoveY - tipsViewY), 2));
        // 圓的半徑隨着距離愈來愈遠變和愈來愈小
        radius = -distance / 15 + DEFAULT_RADIUS;

        if (radius <= 0) {
            isLimit = true;
            return;
        }

        if(tipsViewMoveX - tipsViewX == 0 || tipsViewMoveY - tipsViewY == 0) {
            return;
        }

        /**
         * 計算偏移量 offsetX 以及 offsetY
         *
         * 直線的斜率 k = (y2 - y1) / (x2 - x1) = tan𝞪,因此這裏 Math.atan(k) 就是計算出來的角度,再根據角度分別計算出 offsetX 與 offsetY。
         *
         */
        float offsetX = (float) (radius * Math.sin(Math.atan((tipsViewMoveY - tipsViewY) / (tipsViewMoveX - tipsViewX))));
        float offsetY = (float) (radius * Math.cos(Math.atan((tipsViewMoveY - tipsViewY) / (tipsViewMoveX - tipsViewX))));

        float x1 = tipsViewX - offsetX;
        float y1 = tipsViewY + offsetY;

        float x2 = tipsViewMoveX - offsetX;
        float y2 = tipsViewMoveY + offsetY;

        float x3 = tipsViewMoveX + offsetX;
        float y3 = tipsViewMoveY - offsetY;

        float x4 = tipsViewX + offsetX;
        float y4 = tipsViewY - offsetY;

        // 重置 path
        path.reset();
        // 移到點 (x1,y1)
        path.moveTo(x1,y1);
        // 以 (x1,y1) 爲起點,(x2,y2) 爲終點,控制點爲兩圓心的中心點,畫一條二階貝塞爾曲線
        path.quadTo(controllerX,controllerY,x2,y2);
        // 畫直線
        path.lineTo(x3,y3);
        // 再對稱畫一條二階貝塞爾曲線
        path.quadTo(controllerX,controllerY,x4,y4);
        // 畫直線,封閉區域
        path.lineTo(x1,y1);
複製代碼

4、總結

貝塞爾曲線在 Android 中的應用自己並不難,主要掌握好 Path#quadTo() 和 Path#cubicTo() 這兩個方法的使用便可。難就難在對目標圖形的拆分以及計算起點,終點和控制點上。

相關文章
相關標籤/搜索