【開源項目解析】QQ“一鍵下班”功能實現解析——學習Path及貝茲路徑的基本使用,path貝塞爾

來源:互聯網
上載者:User

【開源項目解析】QQ“一鍵下班”功能實現解析——學習Path及貝茲路徑的基本使用,path貝塞爾

早在很久很久以前,QQ就實現了“一鍵下班”功能。何為“一鍵下班”?當你QQ有資訊時,下部會有資訊數量提示紅點,點擊拖動之後,就會出現“一鍵下班”效果。本文將結合github上關於此功能的一個簡單實現,介紹這個功能的基本實現思路。

項目地址

https://github.com/chenupt/BezierDemo

最終實現效果

實現原理解析

我個人感覺,這個效果實現的很漂亮啊!那麼咱們就來看看實現原理是什麼~

註:下面內容請參照項目源碼觀看。

其實如果從代碼來看,實現的過程並不複雜,重點需要掌握的就是

  • path的用法
  • 貝茲路徑的使用。

這個項目的核心就是BezierView,繼承自FrameLayout,拖動的時候,相當於覆蓋在螢幕上一樣。在init()方法中主要進行了以下操作

private void init(){        path = new Path();        paint = new Paint();        paint.setAntiAlias(true);        paint.setStyle(Paint.Style.FILL_AND_STROKE);        paint.setStrokeWidth(2);        paint.setColor(Color.RED);        LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);        exploredImageView = new ImageView(getContext());        exploredImageView.setLayoutParams(params);        exploredImageView.setImageResource(R.drawable.tip_anim);        exploredImageView.setVisibility(View.INVISIBLE);        tipImageView = new ImageView(getContext());        tipImageView.setLayoutParams(params);        tipImageView.setImageResource(R.drawable.skin_tips_newmessage_ninetynine);        addView(tipImageView);        addView(exploredImageView);    }

初始化了Path和Paint對象,然後動態產生了兩個ImageView

  • exploredImageView 主要用來實現爆炸效果,預設不可見
  • tipImageView 手指進行拖動時的紅色表徵圖

exploredImageView設定的圖片資源是一個AnimationDrawable,下面是res中的聲明,控制每張圖片的播放順序和期間,這也很好理解

<?xml version="1.0" encoding="utf-8"?><animation-list    xmlns:android="http://schemas.android.com/apk/res/android"    android:oneshot="true">    <item android:drawable="@drawable/idp" android:duration="300"/>    <item android:drawable="@drawable/idq" android:duration="300"/>    <item android:drawable="@drawable/idr" android:duration="300"/>    <item android:drawable="@drawable/ids" android:duration="300"/>    <item android:drawable="@drawable/idt" android:duration="300"/>    <item android:drawable="@android:color/transparent" android:duration="300"/></animation-list>

我們在學習這種自訂控制項的時候,可以按照View的繪製過程,對代碼進行重點的查看,比如說,我們可以從
下面這個順序來對這個項目進行學習。

  • onMeasure()
  • onLayout()
  • onDraw()
  • onTouchEvent()

因為這個項目沒有重寫onMeasure(),所以我們直接從onLayout看看做了什麼

 @Override    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {        exploredImageView.setX(startX - exploredImageView.getWidth()/2);        exploredImageView.setY(startY - exploredImageView.getHeight()/2);        tipImageView.setX(startX - tipImageView.getWidth()/2);        tipImageView.setY(startY - tipImageView.getHeight()/2);        super.onLayout(changed, left, top, right, bottom);    }

代碼還是非常還理解的,無非就是初始化了ImageView的位置,在這裡出現了兩個變數,startX和startY,這兩個變數控制的是紅點的初始化座標,在整個過程中不會發生改變。

那麼onDraw()呢?

@Override    protected void onDraw(Canvas canvas){        if(isAnimStart || !isTouch){            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.OVERLAY);        }else{            calculate();            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.OVERLAY);            canvas.drawPath(path, paint);            canvas.drawCircle(startX, startY, radius, paint);            canvas.drawCircle(x, y, radius, paint);        }        super.onDraw(canvas);    }

onDraw()裡面的操作也並不複雜,如果正在執行動畫或者是沒有在觸摸模式,就畫一個透明的顏色,否則,就開始畫真正的介面了。calculate()這個方法是一個重點,從命名來看應該是計算了一些座標值,然後開始畫了兩個圓,這兩個圓的座標,一個是(startX,startY),另一個是(x,y),顏色和半徑都是相同的,這個是為了簡化計算,所以將兩個圓的半徑設定成相同的啦。

我們先繼續看一下在onTouchEvent()裡面進行了什麼操作

@Override    public boolean onTouchEvent(MotionEvent event) {        if(event.getAction() == MotionEvent.ACTION_DOWN){            // 判斷觸摸點是否在tipImageView中            Rect rect = new Rect();            int[] location = new int[2];            tipImageView.getDrawingRect(rect);            tipImageView.getLocationOnScreen(location);            rect.left = location[0];            rect.top = location[1];            rect.right = rect.right + location[0];            rect.bottom = rect.bottom + location[1];            if (rect.contains((int)event.getRawX(), (int)event.getRawY())){                isTouch = true;            }        }else if(event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL){            isTouch = false;            tipImageView.setX(startX - tipImageView.getWidth()/2);            tipImageView.setY(startY - tipImageView.getHeight()/2);        }        invalidate();        if(isAnimStart){            return super.onTouchEvent(event);        }        anchorX =  (event.getX() + startX)/2;        anchorY =  (event.getY() + startY)/2;        x =  event.getX();        y =  event.getY();        return true;    }

首先在按下的時候,取得了tipImageView的螢幕座標位置,然後根據觸摸點的位置,來判斷是否是觸摸狀態,從而改變isTouch的取值,而如果不是按下時間,則推出改變isTouch,從而觸摸狀態,還原tipImageVIew的位置。但是無論如何,都會執行invalidate(),來調用onDraw(),在那裡面就執行了實際的畫圓的操作,這個咱們一會在看。再往下呢,就是根據動畫狀態是否現正播放,來更新x、y座標,還有anchorX和anchorY的值。

x和y的值其實就是觸摸點的位置,主要用來控制手指所按下的圓的位置,那麼anchorX和anchorY呢?這兩個值其實就是控制錨點的座標,用於貝茲路徑的繪製。

說到了這裡,我相信你應該明白實現的基本思路了,但是最重要的,就是拉扯效果,到底是如何?的呢?那麼咱們就來看一下最重要的calculate()到底做了些什麼!

在看這段代碼之前,咱們先簡單學習一下貝茲路徑及如何繪製。

貝茲路徑於1962年由法國數學家Pierre Bézier第一次研究使用並給出了詳細的計算公式,So該曲線也是由其名字命名。

Path中給出的quadTo方法屬於二階貝賽爾曲線。
來看下高清無碼GIF動圖,從愛哥那邊偷的,別告訴他^_^

從上面的動圖中,我們可以發現,二階貝茲路徑,我們只需要確定三個點,就可以畫出一條平滑的曲線,P0和P2是起點和終點,而P1就是我們的錨點,也就是前面提到的anchorX和anchorY。

那麼問題來了,如果我們要實現這種拖拽展開的效果,需要知道幾個點呢?

先來張設計圖

可以看到,在設計圖中,有P1-P4四個座標點,是兩條圓外切線與圓的交點座標,因為需要P1-P2和P3-P4兩條貝茲路徑的歪曲程度相同,所以錨點只需要在P0到原點座標的連線上取一個點即可,所以,咱們就需要5個座標點。

容我喝口水^_^

來來來,咱們繼續!

那麼,既然知道了需要哪五個座標點,anchorX和anchorY在onTouchEvent()裡面已經算出來了,那麼,剩下的4個座標點怎麼求呢?其實這就是calculate()內部所做的主要工作。

由於將兩個圓的半徑設定為相同,可以精簡計算,所以下面的代碼也是假設兩個圓的半徑相同進行操作的,凱子哥再給你手繪一張高清無碼大圖

startX和startY是指定值,這裡我們以它為座標原點,另外一個圓的座標為(x,y),即手指觸摸的位置座標,兩圓半徑相同,則外切線平行,過(x,y)點做垂直線垂直於兩條切線。

現在,已知(startX,startY),(x,y),半徑radius,還有個直角,因此,我們只需要知道一個角度,然後就可以求出offsetX和offsetY,也就求出P1-P4的四點座標了~~~

那麼這個角度好求嗎?

簡單,再來張高清無碼大圖~

因為

  • ∠α=∠3
  • ∠3+∠2=90
  • ∠1+∠2=90

所以
∠α=∠1

這是初中的三段式麼…忘記了

那麼∠1怎麼求呢?簡單啊,(x,y)都知道了,

tan∠1= (y-startY)/(x-startX);

因此可得
∠1 = arctan((y-startY)/(x-startX))

知道角度,知道radius,還求不出offsetX和offsetY麼~

所以

float offsetX = (float) (radius*Math.sin(Math.atan((y - startY) / (x - startX))));float offsetY = (float) (radius*Math.cos(Math.atan((y - startY) / (x - startX))));

那麼。,,現在再來看下面的代碼,你還說你看不懂嗎?

private void calculate(){        float distance = (float) Math.sqrt(Math.pow(y-startY, 2) + Math.pow(x-startX, 2));        radius = -distance/15+DEFAULT_RADIUS;        if(radius < 9){            isAnimStart = true;            exploredImageView.setVisibility(View.VISIBLE);            exploredImageView.setImageResource(R.drawable.tip_anim);            ((AnimationDrawable) exploredImageView.getDrawable()).stop();            ((AnimationDrawable) exploredImageView.getDrawable()).start();            tipImageView.setVisibility(View.GONE);        }        // 根據角度算出四邊形的四個點        float offsetX = (float) (radius*Math.sin(Math.atan((y - startY) / (x - startX))));        float offsetY = (float) (radius*Math.cos(Math.atan((y - startY) / (x - startX))));        float x1 = startX - offsetX;        float y1 = startY + offsetY;        float x2 = x - offsetX;        float y2 = y + offsetY;        float x3 = x + offsetX;        float y3 = y - offsetY;        float x4 = startX + offsetX;        float y4 = startY - offsetY;        path.reset();        path.moveTo(x1, y1);        path.quadTo(anchorX, anchorY, x2, y2);        path.lineTo(x3, y3);        path.quadTo(anchorX, anchorY, x4, y4);        path.lineTo(x1, y1);        // 更改表徵圖的位置        tipImageView.setX(x - tipImageView.getWidth()/2);        tipImageView.setY(y - tipImageView.getHeight()/2);    }

算出4個點的座標,並且知道錨點位置,用path連起來就Ok啦

肚子餓了,這一篇就到這裡了,下去吃飯飯

相關項目及文章
  • 手機QQ5.0紅點拖拽消除的實現
  • QQ手機版 5.0“一鍵下班”設計小結
相關項目
  • 項目地址https://github.com/dodola/MetaballLoading
  • 項目地址https://github.com/THEONE10211024/WaterDropListView

-

尊重原創,轉載請註明:From 凱子哥(http://blog.csdn.net/zhaokaiqiang1992) 侵權必究!

著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.