博主聲明:
轉(zhuǎn)載請(qǐng)?jiān)陂_頭附加本文鏈接及作者信息没酣,并標(biāo)記為轉(zhuǎn)載。本文由博主 威威喵 原創(chuàng)报账,請(qǐng)多支持與指教当宴。
本文首發(fā)于此 博主:威威喵 | 博客主頁(yè):https://blog.csdn.net/smile_running
這幾天突然發(fā)現(xiàn) QQ 的消息拖拽動(dòng)畫效果還挺不錯(cuò)的,以前都沒去留意它踱阿,這幾看了一點(diǎn)關(guān)于貝塞爾曲線的知識(shí)管钳,這不剛好沙場(chǎng)練兵钦铁。于是從昨天開始呢,我就已經(jīng)開始補(bǔ)點(diǎn)高數(shù)的知識(shí)了才漆。雖然我現(xiàn)在已經(jīng)準(zhǔn)大四了牛曹,眨眼間就快畢業(yè)了,高數(shù)的知識(shí)還是從大一開始學(xué)習(xí)的醇滥,現(xiàn)在基本忘了差不多了黎比。
扯了一點(diǎn)關(guān)于我的學(xué)習(xí)經(jīng)歷,回到本篇問(wèn)題的關(guān)鍵鸳玩,QQ 消息拖拽效果是怎樣的呢阅虫?于是,我在模擬器裝了一個(gè) QQ 應(yīng)用怀喉,特地找了一下小號(hào)书妻,記得這個(gè)號(hào)好像是我初中申請(qǐng)的賬號(hào),以前那會(huì)兒 cf躬拢、飛車躲履、dnf 特別流行,搞了幾個(gè)小號(hào)搬磚聊闯,哈哈工猜。我們來(lái)看看消息拖拽的效果吧:
這樣的效果做起來(lái)并不簡(jiǎn)單,尤其是曲線的計(jì)算方面菱蔬,如果你也像我一樣忘了高數(shù)的知識(shí)點(diǎn)的話篷帅,建議你去翻翻三角函數(shù)那部分的知識(shí), 本文不會(huì)教你這些基本公式拴泌,也不會(huì)教你自定義 view 的基本流程魏身,本篇目的:計(jì)算和實(shí)現(xiàn)拖拽的粘性效果。如果這些基本知識(shí)不具備的話蚪腐,推薦你去看下我的自定義 view 相關(guān)文章箭昵。
有了上一篇(點(diǎn)擊這里:貝塞爾曲線(Bezier)之愛心點(diǎn)贊曲線動(dòng)畫效果)對(duì)貝塞爾曲線的基本了解和寫了一個(gè)小案例的鋪墊,在這次寫這個(gè) QQ 消息拖拽效果的時(shí)候回季,顯然輕松了許多家制。好了,廢話就說(shuō)這么多泡一,下面進(jìn)入重點(diǎn)內(nèi)容颤殴。
首先,看上面的效果顯示情況鼻忠,可以看成兩個(gè)小圓涵但,一個(gè)比較大一點(diǎn),可以拖拽出去,另一個(gè)小一點(diǎn)贤笆,但會(huì)隨著兩個(gè)圓的距離改變大小蝇棉。我們的步驟:在 onDraw 里面繪制兩個(gè)圓,用手指可以拖動(dòng)一個(gè)大圓芥永,并且小圓的大小會(huì)隨著兩圓的距離更改篡殷。這部分代碼非常簡(jiǎn)單,我就不做多的介紹了埋涧,如果你對(duì)下面代碼有不解之處板辽,還請(qǐng)自己補(bǔ)充知識(shí)。直接貼代碼:
package nd.no.xww.qqmessagedragview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
/**
* @author xww
* @desciption : 仿 QQ 消息拖拽消失的效果(大圓:不會(huì)消失棘催,且大小一致劲弦。小圓:與大圓的距離協(xié)調(diào)改變大小)
* @date 2019/8/2
* @time 8:54
*/
public class QQMessageDragView extends View {
private Paint mPaint;
//大圓
private float mBigCircleX;
private float mBigCircleY;
private final int BIG_CIRCLE_RADUIS = 50;
//小圓
private float mSmallCircleX;
private float mSmallCircleY;
private int mSmallDefRaduis = 40;
private int mSmallHideRaduis = 15;
private int mSmallCircleRaduis = mSmallDefRaduis;
private Bitmap mMessageBitmap;
private void init() {
mPaint = new Paint();
mPaint.setDither(true);
mPaint.setAntiAlias(true);
mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
mMessageBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.message);
mMessageBitmap = Bitmap.createScaledBitmap(mMessageBitmap, 150, 150, false);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ? MeasureSpec.getSize(widthMeasureSpec) : 200
, MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY ? MeasureSpec.getSize(heightMeasureSpec) : 200);
}
public QQMessageDragView(Context context) {
this(context, null);
}
public QQMessageDragView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public QQMessageDragView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void onDraw(Canvas canvas) {
if (mSmallCircleRaduis > mSmallHideRaduis) {
canvas.drawCircle(mSmallCircleX, mSmallCircleY, mSmallCircleRaduis, mPaint);
}
canvas.drawCircle(mBigCircleX, mBigCircleY, BIG_CIRCLE_RADUIS, mPaint);
// canvas.drawBitmap(mMessageBitmap, mBigCircleX, mBigCircleY, mPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float downX = event.getX();
float downY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mSmallCircleRaduis = mSmallDefRaduis;
mSmallCircleX = mBigCircleX = downX;
mSmallCircleY = mBigCircleY = downY;
break;
case MotionEvent.ACTION_MOVE:
mBigCircleX = event.getX();
mBigCircleY = event.getY();
int disCircle = calculateDisCircle(mSmallCircleX, mSmallCircleY, mBigCircleX, mBigCircleY);
mSmallCircleRaduis = mSmallDefRaduis - disCircle / mSmallHideRaduis;
break;
case MotionEvent.ACTION_UP:
mSmallCircleRaduis = 0;
break;
}
invalidate();
return true;
}
// 兩點(diǎn)之間的距離公式 √(x2-x1)2+(y2-y1)2
private int calculateDisCircle(float mSmallCircleX, float mSmallCircleY, float mBigCircleX, float mBigCircleY) {
return (int) Math.sqrt(Math.pow((mSmallCircleX - mBigCircleX), 2) + Math.pow((mSmallCircleY - mBigCircleY), 2));
}
}
運(yùn)行上面的代碼醇坝,你就會(huì)看到和我一樣的效果:
好了邑跪,上面的代碼只是做了一個(gè)鋪墊,也是必須實(shí)現(xiàn)的第一步呼猪。接下來(lái)重頭戲開始画畅,我們講講一些數(shù)學(xué)相關(guān)知識(shí)吧,本人高數(shù)也不怎么樣宋距,大學(xué)除了基本必修高數(shù)轴踱,也沒去深入學(xué)習(xí),不過(guò)這也不影響我們下面的操作谚赎。
首先淫僻,扔出一張草圖,畫的就這樣壶唤,將就看吧:
這上面應(yīng)該不難看懂吧雳灵,兩個(gè)紅色圓就相當(dāng)于我們拖拽的圓一樣,從上面的草圖中,我們目前已知的有 c1 c2 r1 r2 這四個(gè)屬性值,c1 c2 代表圓心坐標(biāo)昼弟,r1 r2 是半徑。
當(dāng)用手指去拖拽大圓的時(shí)候,它們之間的聯(lián)系就用那兩根藍(lán)色的曲線來(lái)表示岛啸,兩曲線對(duì)應(yīng)的在兩圓上的坐標(biāo)點(diǎn)就是 p1 p2 p3 p4 四個(gè)點(diǎn)钓觉,這四個(gè)點(diǎn)會(huì)伴隨這兩圓的距離發(fā)生改變,你可以想象一下效果坚踩。
那么荡灾,從上圖中,我們就要去計(jì)算 p1 p2 p3 p4 這四個(gè)點(diǎn)的坐標(biāo),然后將四點(diǎn)封閉起來(lái)繪制成路徑即可批幌〈∪瘢可是,說(shuō)的比較輕巧荧缘,從目前我們已知的條件當(dāng)中皆警,能用得上的就 c1 c2 r1 r2 四個(gè)了,如何去求呢截粗?看接下來(lái)的這張圖:
從這張圖的計(jì)算過(guò)程中信姓,我們可以求得綠色三角形的角 a 的相關(guān)方程式。因?yàn)槲覀円阎?c1 圓心的坐標(biāo)值绸罗,就可以得出 p1 點(diǎn)的坐標(biāo)值意推,如上圖 p1x p1y 的值。
這樣的話珊蟀,我們可以利用三角函數(shù)公式得出 b 邊和 c 邊的值菊值,如上圖,最終得到的一個(gè)方程式中育灸,僅存在一個(gè)角 a 是我們未知的腻窒,接下來(lái)我們就要去計(jì)算角 a 的值,看下圖:
來(lái)到第一張圖描扯,看上面的黃色輔助線定页,假設(shè)它形成的是直角。我們就可以得到這兩條輔助線的邊長(zhǎng) dy 和 dx 绽诚。又根據(jù)三角形的補(bǔ)角和兩平行線之間的夾角相等的定理典徊,我們得出圖中的三個(gè)角 a 都是一樣的大小。
這樣我們可以得到一個(gè)等式:tanA = dy / dx 恩够,最終卒落,角 a = arctan( tanA )
這時(shí)候我們就取到了 a 相關(guān)的等式了,而 dx dy 都是可以計(jì)算出來(lái)的蜂桶,所以一連串下來(lái)儡毕,相關(guān)的等式都成立了,從而就可以計(jì)算出一個(gè)點(diǎn) p1扑媚,獲得 p1 點(diǎn)后腰湾,p2 p3 p4 不就手到擒來(lái)嘛。
最后要想形成貝塞爾曲線的效果疆股,除了 p1 p2 p3 p4 以外费坊,我們還需要一個(gè)控制點(diǎn),如圖上的點(diǎn) M旬痹,它是形成曲線的控制點(diǎn)附井,也是至關(guān)重要的一個(gè)點(diǎn)讨越,它的坐標(biāo)就是 M點(diǎn) ( (c1x+c2x) / 2 , (c1y+c2y) / 2 )
那么本篇數(shù)學(xué)相關(guān)的計(jì)算部分就已經(jīng)結(jié)束了,你還以為程序員不需要數(shù)學(xué)知識(shí)嘛永毅,哈哈把跨。下面就是該怎么寫程序了,把數(shù)學(xué)公式化為程序代碼沼死,這就得看你的編程水平啦着逐。
我寫了好一會(huì)兒,都是那個(gè)坐標(biāo)值正負(fù)的問(wèn)題卡了我挺久的漫雕,不過(guò)最終還是把代碼給搞出來(lái)了滨嘱,四個(gè)點(diǎn)的計(jì)算方法如下:
private float p1X;
private float p1Y;
private float p2X;
private float p2Y;
private float p3X;
private float p3Y;
private float p4X;
private float p4Y;
//控制點(diǎn)
private float controlX;
private float controlY;
private float dx, dy;
private double angleA;
private double tanA;
private Path bezierPath;
private Path mBezierPath;
/**
* 貝塞爾 p1 p2 p3 p4 四個(gè)點(diǎn)坐標(biāo)的計(jì)算
*
* @return
*/
private Path drawDragBezier() {
if (mSmallCircleRaduis < mSmallHideRaduis) {
return null;
}
dx = mBigCircleX - mSmallCircleX;
dy = mBigCircleY - mSmallCircleY;
tanA = dy / dx;
angleA = Math.atan(tanA);
//控制點(diǎn)的計(jì)算
controlX = (mSmallCircleX + mBigCircleX) / 2;
controlY = (mSmallCircleY + mBigCircleY) / 2;
p1X = (float) (mSmallCircleX + Math.sin(angleA) * mSmallCircleRaduis);
p1Y = (float) (mSmallCircleY - Math.cos(angleA) * mSmallCircleRaduis);
p2X = (float) (mBigCircleX + Math.sin(angleA) * BIG_CIRCLE_RADUIS);
p2Y = (float) (mBigCircleY - Math.cos(angleA) * BIG_CIRCLE_RADUIS);
p3X = (float) (mBigCircleX - Math.sin(angleA) * BIG_CIRCLE_RADUIS);
p3Y = (float) (mBigCircleY + Math.cos(angleA) * BIG_CIRCLE_RADUIS);
p4X = (float) (mSmallCircleX - Math.sin(angleA) * mSmallCircleRaduis);
p4Y = (float) (mSmallCircleY + Math.cos(angleA) * mSmallCircleRaduis);
//繪制路徑
bezierPath = new Path();
bezierPath.moveTo(p1X, p1Y);
bezierPath.quadTo(controlX, controlY, p2X, p2Y);
bezierPath.lineTo(p3X, p3Y);
bezierPath.quadTo(controlX, controlY, p4X, p4Y);
bezierPath.close();
return bezierPath;
}
然后呢,使用就很簡(jiǎn)單了浸间。返回一個(gè)路徑太雨,我們只要畫出來(lái)就好了,修改 onDraw 代碼如下:
@Override
protected void onDraw(Canvas canvas) {
mBezierPath = drawDragBezier();
if (mBezierPath != null) {
canvas.drawCircle(mSmallCircleX, mSmallCircleY, mSmallCircleRaduis, mPaint);
canvas.drawPath(mBezierPath, mPaint);
}
canvas.drawCircle(mBigCircleX, mBigCircleY, BIG_CIRCLE_RADUIS, mPaint);
}
好了吧魁蒜,點(diǎn)擊運(yùn)行囊扳,你將會(huì)看到如下的效果:
最后做了一點(diǎn)點(diǎn)小優(yōu)化,拖拽時(shí)沒有超出范圍可以回到原來(lái)的位置兜看,若超出拖拽的極限方法锥咸,導(dǎo)致兩個(gè)圓失去關(guān)聯(lián)時(shí),代表要摧毀那個(gè)大圓细移,手指松開那一剎那搏予,要將它隱藏掉,效果如下:
那么至此弧轧,我們的QQ消息的粘性動(dòng)畫已經(jīng)實(shí)現(xiàn)了雪侥,代碼倒是不難,難的是通過(guò)數(shù)學(xué)公式來(lái)計(jì)算出 p1 p2 p3 p4 點(diǎn)的坐標(biāo)值精绎,這可能會(huì)卡住很多人速缨,主要還是因?yàn)閿?shù)學(xué)功底不足,還是抽時(shí)間補(bǔ)補(bǔ)數(shù)學(xué)代乃,它可是個(gè)很有魅力的機(jī)靈鬼旬牲。
補(bǔ)充:(對(duì)上面的特效進(jìn)行優(yōu)化處理)
今天,8 月 8 日搁吓,早上 5 點(diǎn)半左右原茅,臺(tái)灣不幸遭到了地震,連我在福建中北部地帶都能偶感晃動(dòng)堕仔,我好像迷迷糊糊中感覺床在搖晃擂橘,是 6 點(diǎn)多級(jí)的地震,在此祝愿臺(tái)灣人民安好贮预。而且贝室,受臺(tái)風(fēng)的影響,家里下了好大的雨仿吞,不過(guò)倒是清涼了許多滑频。
好了,讓我們來(lái)優(yōu)化一下這個(gè)效果吧唤冈,博主之前還沒有處理的一些細(xì)節(jié)問(wèn)題峡迷,比如這個(gè) QQ 消息拖動(dòng),如果我們沒有將它拖斷掉你虹,也就是線還連著绘搞,上次的做法是將它的坐標(biāo)賦值給初始按下的坐標(biāo),這導(dǎo)致的效果是一瞬間就回去了傅物,動(dòng)畫太過(guò)生硬夯辖,體驗(yàn)不是特別好董饰,接下來(lái)我們來(lái)優(yōu)化一下蒿褂,讓它慢慢的回去,有一個(gè)過(guò)渡時(shí)間卒暂。
上次的代碼是這樣做的啄栓,直接回到手指起始按下的那一個(gè)點(diǎn)位置:
case MotionEvent.ACTION_UP:
if (!isAttached) {
//被扯斷了
isShowed = false;
} else if (mSmallCircleRaduis >= mSmallHideRaduis) {//小圓的半徑如果大于顯示的半徑,意味著沒有拖段線
isShowed = true;//大圓要顯示
//回到原來(lái)手指按下的位置
mBigCircleX = mSmallCircleX;
mBigCircleY = mSmallCircleY;
}
mSmallCircleRaduis = 0;//每次手松開也祠,小圓半徑規(guī) 0
break;
這個(gè)肯定不行昙楚,要對(duì)它的值進(jìn)行修改,我們的思想是這個(gè)樣子的诈嘿,看圖
我們需要慢慢的改變大圓的半徑堪旧,就相當(dāng)于改變被我們拉出來(lái)的那個(gè)圓的 x 坐標(biāo)和 y 坐標(biāo),我們給它定一個(gè)時(shí)間段永淌,讓它們一起開始變化崎场,這個(gè)就得使用到屬性動(dòng)畫來(lái)處理了,我們把上部分的代碼做如下修改即可
case MotionEvent.ACTION_UP:
if (!isAttached) {
//被扯斷了
isShowed = false;
} else if (mSmallCircleRaduis >= mSmallHideRaduis) {//小圓的半徑如果大于顯示的半徑遂蛀,意味著沒有拖段線谭跨。松開手,彈回去
isShowed = true;//大圓要顯示
animatorSet = new AnimatorSet();
xAnimator = ObjectAnimator.ofFloat(mBigCircleX, mSmallCircleX);
xAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBigCircleX = (float) animation.getAnimatedValue();
int disCircle = calculateDisCircle(mSmallCircleX, mSmallCircleY, mBigCircleX, mBigCircleY);
mSmallCircleRaduis = mSmallDefRaduis - disCircle / mSmallHideRaduis;//在拖動(dòng)過(guò)程中李滴,小圓半徑一直在縮小
}
});
yAnimator = ObjectAnimator.ofFloat(mBigCircleY, mSmallCircleY);
yAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBigCircleY = (float) animation.getAnimatedValue();
invalidate();
}
});
animatorSet.playTogether(xAnimator, yAnimator);
animatorSet.setInterpolator(new OvershootInterpolator(3f));
animatorSet.setDuration(10000);
animatorSet.start();
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//動(dòng)畫結(jié)束時(shí)螃宙,隱藏小圓
mSmallCircleRaduis = 0;//每次手松開,小圓半徑規(guī) 0
}
});
}
break;
那么所坯,繪制那個(gè)粘性的貝塞爾曲線也要一直繪制了谆扎,不能松開就沒了吧,所以要把 onDraw 的里面的代碼改為如下:
@Override
protected void onDraw(Canvas canvas) {
mBezierPath = drawDragBezier();
//兩個(gè)圓還有聯(lián)系
if (mBezierPath != null) {
canvas.drawPath(mBezierPath, mPaint);
}
if (isAttached) {
canvas.drawCircle(mSmallCircleX, mSmallCircleY, mSmallCircleRaduis, mPaint);
}
//如果是顯示的
if (isShowed) {
canvas.drawCircle(mBigCircleX, mBigCircleY, BIG_CIRCLE_RADUIS, mPaint);
}
}
好了芹助,一起來(lái)看看效果吧堂湖。為了使效果更加明顯闲先,我特地把縮回來(lái)的動(dòng)畫改為 10S,足夠你看清楚了吧
我給它加了一個(gè)插值器无蜂,回來(lái)的時(shí)候有一個(gè)反彈的效果伺糠!彈彈彈,彈走魚尾紋斥季。训桶。。
不過(guò)呢酣倾,還有一個(gè)地方需要優(yōu)化的舵揭,就是拖斷掉的時(shí)候,再松開會(huì)有一個(gè)消失的效果躁锡,我就搞的簡(jiǎn)單一點(diǎn)午绳,讓它慢慢的消失就好了。不過(guò)也可以學(xué)那個(gè)爆炸效果稚铣,會(huì)比較炫酷一點(diǎn)箱叁,我找了一下那個(gè)爆炸的圖片,懶得圖改成透明顏色了惕医,需要的自己去查一查幀動(dòng)畫就好了耕漱。
下面是放快的效果
最后的完整代碼
package nd.no.xww.qqmessagedragview;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.OvershootInterpolator;
/**
* @author xww
* @desciption : 仿 QQ 消息拖拽消失的效果(大圓:不會(huì)消失,且大小一致抬伺。小圓:與大圓的距離協(xié)調(diào)改變大忻弧)
* @date 2019/8/2
* @time 8:54
* @博主:威威喵
*/
public class QQMessageDragView extends View {
private Paint mPaint;
//大圓
private float mBigCircleX;
private float mBigCircleY;
private float mBigCircleRaduis = 50;
//小圓
private float mSmallCircleX;
private float mSmallCircleY;
private int mSmallDefRaduis = 40;
private int mSmallHideRaduis = 15;//扯斷的距離
private int mSmallCircleRaduis = mSmallDefRaduis;
private Bitmap mMessageBitmap;
private boolean isAttached;//代表兩個(gè)關(guān)聯(lián)
private boolean isFirst = true;//顯示大圓
private void init() {
mPaint = new Paint();
mPaint.setDither(true);
mPaint.setAntiAlias(true);
mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
mPaint.setTextSize(30f);
mMessageBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.message);
mMessageBitmap = Bitmap.createScaledBitmap(mMessageBitmap, 150, 150, false);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ? MeasureSpec.getSize(widthMeasureSpec) : 200
, MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY ? MeasureSpec.getSize(heightMeasureSpec) : 200);
}
public QQMessageDragView(Context context) {
this(context, null);
}
public QQMessageDragView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public QQMessageDragView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void onDraw(Canvas canvas) {
mBezierPath = drawDragBezier();
//兩個(gè)圓還有聯(lián)系
if (mBezierPath != null) {
canvas.drawPath(mBezierPath, mPaint);
}
if (isAttached) {
canvas.drawCircle(mSmallCircleX, mSmallCircleY, mSmallCircleRaduis, mPaint);
}
//如果第一次,不繪制圓
if (isFirst) {
return;
}
canvas.drawCircle(mBigCircleX, mBigCircleY, mBigCircleRaduis, mPaint);
}
private float raduis;
AnimatorSet animatorSet;
ValueAnimator xAnimator;
ValueAnimator yAnimator;
@Override
public boolean onTouchEvent(MotionEvent event) {
float downX = event.getX();
float downY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 兩個(gè)圓關(guān)聯(lián)了
mBigCircleRaduis = 50; // 大圓的初始值
isFirst = false;
isAttached = true;
mSmallCircleRaduis = mSmallDefRaduis;
mSmallCircleX = mBigCircleX = downX;
mSmallCircleY = mBigCircleY = downY;
break;
case MotionEvent.ACTION_MOVE:
mBigCircleX = event.getX();
mBigCircleY = event.getY();
int disCircle = calculateDisCircle(mSmallCircleX, mSmallCircleY, mBigCircleX, mBigCircleY);
mSmallCircleRaduis = mSmallDefRaduis - disCircle / mSmallHideRaduis;//在拖動(dòng)過(guò)程中峡钓,小圓半徑一直在縮小
if (mSmallCircleRaduis < mSmallHideRaduis) {//小圓的半徑如果太小了妓笙,不顯示了。
isAttached = false;//表示兩個(gè)圓沒有關(guān)聯(lián)了能岩,意味這線被拖斷了
}
break;
case MotionEvent.ACTION_UP:
if (!isAttached) { // 被扯斷了寞宫,兩圓沒有聯(lián)系了
ValueAnimator raduisAnimator = ObjectAnimator.ofFloat(mBigCircleRaduis, 0);
raduisAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBigCircleRaduis = (float) animation.getAnimatedValue();
invalidate();
}
});
raduisAnimator.setDuration(500);
raduisAnimator.start();
} else if (mSmallCircleRaduis >= mSmallHideRaduis) {//小圓的半徑如果大于顯示的半徑,意味著沒有拖段線拉鹃。松開手辈赋,彈回去
animatorSet = new AnimatorSet();
xAnimator = ObjectAnimator.ofFloat(mBigCircleX, mSmallCircleX);
xAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBigCircleX = (float) animation.getAnimatedValue();
int disCircle = calculateDisCircle(mSmallCircleX, mSmallCircleY, mBigCircleX, mBigCircleY);
mSmallCircleRaduis = mSmallDefRaduis - disCircle / mSmallHideRaduis;//在拖動(dòng)過(guò)程中,小圓半徑一直在縮小
}
});
yAnimator = ObjectAnimator.ofFloat(mBigCircleY, mSmallCircleY);
yAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBigCircleY = (float) animation.getAnimatedValue();
invalidate();
}
});
animatorSet.playTogether(xAnimator, yAnimator);
animatorSet.setInterpolator(new OvershootInterpolator(2.5f));
animatorSet.setDuration(500);
animatorSet.start();
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//動(dòng)畫結(jié)束時(shí)膏燕,隱藏小圓
mSmallCircleRaduis = 0;//每次手松開钥屈,小圓半徑規(guī) 0
}
});
}
break;
}
invalidate();
return true;
}
// 兩點(diǎn)之間的距離公式 √(x2-x1)2+(y2-y1)2
private int calculateDisCircle(float mSmallCircleX, float mSmallCircleY, float mBigCircleX, float mBigCircleY) {
return (int) Math.sqrt(Math.pow((mSmallCircleX - mBigCircleX), 2) + Math.pow((mSmallCircleY - mBigCircleY), 2));
}
private float p1X;
private float p1Y;
private float p2X;
private float p2Y;
private float p3X;
private float p3Y;
private float p4X;
private float p4Y;
//控制點(diǎn)
private float controlX;
private float controlY;
private float dx, dy;
private double angleA;
private double tanA;
private Path bezierPath;
private Path mBezierPath;
/**
* 貝塞爾 p1 p2 p3 p4 四個(gè)點(diǎn)坐標(biāo)的計(jì)算
*
* @return
*/
private Path drawDragBezier() {
if (mSmallCircleRaduis < mSmallHideRaduis || !isAttached) {
return null;
}
dx = mBigCircleX - mSmallCircleX;
dy = mBigCircleY - mSmallCircleY;
tanA = dy / dx;
angleA = Math.atan(tanA);
//控制點(diǎn)的計(jì)算
controlX = (mSmallCircleX + mBigCircleX) / 2;
controlY = (mSmallCircleY + mBigCircleY) / 2;
p1X = (float) (mSmallCircleX + Math.sin(angleA) * mSmallCircleRaduis);
p1Y = (float) (mSmallCircleY - Math.cos(angleA) * mSmallCircleRaduis);
p2X = (float) (mBigCircleX + Math.sin(angleA) * mBigCircleRaduis);
p2Y = (float) (mBigCircleY - Math.cos(angleA) * mBigCircleRaduis);
p3X = (float) (mBigCircleX - Math.sin(angleA) * mBigCircleRaduis);
p3Y = (float) (mBigCircleY + Math.cos(angleA) * mBigCircleRaduis);
p4X = (float) (mSmallCircleX - Math.sin(angleA) * mSmallCircleRaduis);
p4Y = (float) (mSmallCircleY + Math.cos(angleA) * mSmallCircleRaduis);
//繪制路徑
bezierPath = new Path();
bezierPath.moveTo(p1X, p1Y);
bezierPath.quadTo(controlX, controlY, p2X, p2Y);
bezierPath.lineTo(p3X, p3Y);
bezierPath.quadTo(controlX, controlY, p4X, p4Y);
bezierPath.close();
return bezierPath;
}
}
最后呢,給出本效果的全部代碼坝辫,期間由于隔了幾天再來(lái)繼續(xù)寫這個(gè)效果篷就,代碼的關(guān)鍵處也補(bǔ)了一點(diǎn)點(diǎn)注釋。哈哈近忙,隔了幾天沒去瞧一眼竭业,差點(diǎn)給我整懵逼了智润,還好,還好未辆。