貝塞爾曲線 - QQ消息汽包拖拽

1.概述


消息氣泡拖拽資料有很多,網上也有開源代碼,下載下來就可以用。為什么還要折騰呢?我想證明一下數學已經初中畢業,其次像貝塞爾這種效果還是很常見的,雖然目前我只有一個 APP 用了這個效果。我想一行代碼讓所有的控件都可以拖動爆炸,不是為了重復造輪子而是為了裝B。


QQ消息汽包拖拽

2.效果實現


** 2.1 效果分析 **

看上面的效果感覺有點麻煩,怎么做到任何控件都可以拖動爆炸,我想說網上應該僅此一家。首先可以不要搞得這么麻煩,比如我再上一張圖片看下:


簡單版.gif

??上面這個效果就比較簡單了,先分析一下實現方式。我手指在任何一個位置觸摸拖動都會是如上圖的這個樣式,這個實現的起來就相對簡單許多了:

2.1.1: 手指按下拖動的時候有一個拖拽的圓這個圓的半徑是不會變化的但是位置會隨著手指移動;
2.1.2: 手指按下拖動的時候有一個固定的圓這個圓的是會變化的但是位置不會變化,圓的半徑取決于兩個圓的距離,兩個圓的距離越大圓半徑越小,距離越小圓半徑越大;
2.1.2: 兩個圓之間有一個不規則的東西將兩個圓連在一起感覺像粘液一樣,這就是大家所說的貝塞爾效果。

2.2 效果實現

2.2.1: 監聽觸摸繪制兩個圓
??我們先挑簡單的寫,首先監聽手指觸摸不斷的繪制兩個圓(固定圓和拖拽圓),如果對觸摸監聽事件以及畫筆使用不是特別熟悉,請留意看看我之前的一些自定義 View 的文章。Android進階之旅 - 自定義View篇

/**
 * description: 消息氣泡拖拽的 View
 * author: Darren on 2017/7/21 10:40
 * email: 240336124@qq.com
 * version: 1.0
 */
public class MessageBubbleView extends View {
    // 拖拽圓的圓心點
    private PointF mDragPoint;
    // 固定圓的圓心點
    private PointF mFixationPoint;
    // 拖拽圓的半徑
    private int mDragRadius = 10;
    // 固定圓的半徑
    private int mFixationRadius = 7;
    // 固定圓的最小半徑
    private int FIXATION_RADIUS_MIN = 3;
    // 固定圓的最大半徑
    private int FIXATION_RADIUS_MAX = 7;
    // 用來繪制的畫筆
    private Paint mPaint;

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

    public MessageBubbleView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MessageBubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 初始化畫筆
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setDither(true);
        mPaint.setAntiAlias(true);
        // 初始化一些距離
        mDragRadius = dip2px(mDragRadius);
        mFixationRadius = dip2px(mFixationRadius);
        FIXATION_RADIUS_MIN = dip2px(FIXATION_RADIUS_MIN);
        FIXATION_RADIUS_MAX = dip2px(FIXATION_RADIUS_MAX);
    }

    private int dip2px(int dip) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dip,getResources().getDisplayMetrics());
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mDragPoint == null && mFixationPoint == null) {
            return;
        }

        // 1.繪制拖拽圓
        canvas.drawCircle(mDragPoint.x, mDragPoint.y, mDragRadius, mPaint);

        // 計算兩個圓之間的距離
        int distance = BubbleUtils.getDistance(mDragPoint, mFixationPoint);

        // 計算固定圓的半徑,距離越大圓半徑越小
        mFixationRadius = FIXATION_RADIUS_MAX - distance / 14;

        if (mFixationRadius > FIXATION_RADIUS_MIN) {
            // 2.繪制固定圓
            canvas.drawCircle(mFixationPoint.x, mFixationPoint.y, mFixationRadius, mPaint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                initPoint(event.getX(), event.getY());
                break;
            case MotionEvent.ACTION_MOVE:
                updateDragPoint(event.getX(), event.getY());
                break;
            case MotionEvent.ACTION_UP:

                break;
        }
        invalidate();
        return true;
    }

    /**
     * 更新拖拽圓的位置
     *
     * @param x
     * @param y
     */
    private void updateDragPoint(float x, float y) {
        mDragPoint.x = x;
        mDragPoint.y = y;
    }

    /**
     * 初始化圓的位置
     *
     * @param x
     * @param y
     */
    private void initPoint(float x, float y) {
        mDragPoint = new PointF(x, y);
        mFixationPoint = new PointF(x, y);
    }
}

2.2.2: 繪制貝塞爾曲線

貝塞爾曲線繪制起來有點小麻煩,我沒記錯的話是初中的數學知識,如果你不是特別了解貝塞爾曲線和三角函數可以百度一下,這里給兩個鏈接 貝塞爾曲線三角函數 文章中我就不做過多的解釋,下面講一下求解思路:


  
??看上面這張圖畫得不咋地但純手工,藍色部分和黑色部分是已知,黃色部分是輔助線是可以利用三角公式求出來的,紅色部分是未知。我們只要求得角 a,有了角 a 我們就能求 x 和 y 這樣我們就知道了 p0 的位置,依葫蘆畫瓢求能求得 p0,p1,p2,p3的值,有了四個點有了控制點自然就能畫貝塞爾曲線了。

    /**
     * 獲取 Bezier 曲線
     *
     * @return
     */
    public Path getBezierPath() {
        if (mFixationRadius < FIXATION_RADIUS_MIN) {
            return null;
        }

        Path bezierPath = new Path();
        // 貝塞爾曲線怎么求?

        // 計算斜率
        float dx = mFixationPoint.x - mDragPoint.x;
        float dy = mFixationPoint.y - mDragPoint.y;
        if (dx == 0) {
            dx = 0.001f;
        }
        float tan = dy / dx;
        // 獲取角a度值
        float arcTanA = (float) Math.atan(tan);

        // 依次計算 p0 , p1 , p2 , p3 點的位置
        float P0X = (float) (mFixationPoint.x + mFixationRadius * Math.sin(arcTanA));
        float P0Y = (float) (mFixationPoint.y - mFixationRadius * Math.cos(arcTanA));

        float P1X = (float) (mDragPoint.x + mDragRadius * Math.sin(arcTanA));
        float P1Y = (float) (mDragPoint.y - mDragRadius * Math.cos(arcTanA));

        float P2X = (float) (mDragPoint.x - mDragRadius * Math.sin(arcTanA));
        float P2Y = (float) (mDragPoint.y + mDragRadius * Math.cos(arcTanA));

        float P3X = (float) (mFixationPoint.x - mFixationRadius * Math.sin(arcTanA));
        float P3Y = (float) (mFixationPoint.y + mFixationRadius * Math.cos(arcTanA));
        // 求控制點 兩個點的中心位置作為控制點
        PointF controlPoint = BubbleUtils.getPointByPercent(mDragPoint, mFixationPoint, 0.5f);

        // 整合貝塞爾曲線路徑
        bezierPath.moveTo(P0X, P0Y);
        bezierPath.quadTo(controlPoint.x, controlPoint.y, P1X, P1Y);
        bezierPath.lineTo(P2X, P2Y);
        bezierPath.quadTo(controlPoint.x, controlPoint.y, P3X, P3Y);
        bezierPath.close();

        return bezierPath;
    }

下一節我們來實現一下如何能夠讓任何一個 View 都能拖動消失,就像 QQ 的消息氣泡一樣,當然到時可能又免不了源碼分析。

所有分享大綱:Android進階之旅 - 自定義View篇

視頻講解地址:http://pan.baidu.com/s/1nvNZSTV

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容