android自定義view之模擬qq消息拖拽刪除效果
這個(gè)模擬功能的實(shí)現(xiàn)主要依靠了PATH和二階貝塞爾曲線(xiàn)。首先上一張圖來(lái)簡(jiǎn)單看一下:

這個(gè)模擬功能有以下幾個(gè)特點(diǎn):
- 在開(kāi)始的時(shí)候點(diǎn)擊圓以外的區(qū)域不會(huì)觸發(fā)拖動(dòng)事件
- 點(diǎn)擊圓的時(shí)候可以拖拽,此時(shí)會(huì)有一個(gè)拉伸效果,連接大圓和小圓
- 拉伸到一定距離(自己設(shè)定)以后兩個(gè)圓會(huì)斷開(kāi),此時(shí)即使再拖拽進(jìn)距離之內(nèi)的時(shí)候也不會(huì)再產(chǎn)生已經(jīng)斷開(kāi)的連接
- 在距離之內(nèi)松手的時(shí)候會(huì)回彈會(huì)原位置,并伴有一個(gè)彈跳動(dòng)畫(huà)
介紹了這么多,看過(guò)我前邊文章的朋友應(yīng)該會(huì)有一個(gè)基本思路。
暴露接口
這個(gè)模擬功能共分為三部分,一個(gè)是那個(gè)小圓,固定的位置,一個(gè)是那個(gè)大圓,可以移動(dòng),還有一部分就是中間的連接部分,會(huì)跟隨大圓一起延伸。
首先看一下都有哪些接口可以調(diào)用:
public void setMinR(float minR) {
this.minR = minR;
}
public void setMaxR(float maxR) {
this.maxR = maxR;
}
public void setBrokeDistance(float distance) {
this.brokeDistance = distance;
}
第一個(gè)setMinR是設(shè)置小圓的半徑,第二個(gè)setMaxR是設(shè)置大圓的半徑,第三個(gè)setBrokeDistance是設(shè)置斷開(kāi)的距離,也就是小圓和大圓的圓心之間的最大連接距離。
初始化
public Buble(Context context) {
super(context);
init();
}
public Buble(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
簡(jiǎn)單的看一下初始化方法。
private void init() {
paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.GREEN);
paint.setAntiAlias(true);
}
其實(shí)只有一個(gè)畫(huà)筆,這里可以為各個(gè)區(qū)域分別設(shè)置畫(huà)筆。
繪制圖形
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawOriginalCircle(canvas);
if (!canBroke) {
drawMoveCircle(canvas);
drawBCurve(canvas);
}
}
這三個(gè)方法相對(duì)簡(jiǎn)單,drawOriginalCircle是畫(huà)中心的小圓,然后canBroke是一個(gè)開(kāi)關(guān),控制是否執(zhí)行畫(huà)移動(dòng)的圓和畫(huà)弧線(xiàn)。
private void drawOriginalCircle(Canvas canvas) {
canvas.drawCircle(getWidth() / 2, getHeight() / 2, minR, paint);
}
private void drawMoveCircle(Canvas canvas) {
canvas.drawCircle(moveX, moveY, maxR, paint);
}
private void drawBCurve(Canvas canvas) {
canvas.drawPath(path, paint);
}
注意,moveX, moveY和path都是變化的,所以在他們的值發(fā)生改變以后千萬(wàn)不要忘記invalidate。
path的連接
關(guān)于path的文章網(wǎng)上一大堆。
此處的難點(diǎn)主要是大圓和小圓之間的連接。用一張圖簡(jiǎn)單表示一下:

基本就是這個(gè)樣子,path的路徑就是那個(gè)黑色的類(lèi)似于漏斗一樣的東西。此處要計(jì)算的角度需要用到三角函數(shù)關(guān)系式,簡(jiǎn)單表示一下:

圖中標(biāo)出的兩個(gè)角度是相等的
double angle = Math.atan((offsetX - minCircleX) / (offsetY - minCircleY));
求出這個(gè)角度(offsetX是移動(dòng)圓心的坐標(biāo))。
這樣就可以算出四個(gè)點(diǎn)的坐標(biāo)了。
private void setPath(float offsetX, float offsetY) {
float minCircleX = (float) getWidth() / 2;
float minCircleY = (float) getHeight() / 2;
double angle = Math.atan((offsetX - minCircleX) / (offsetY - minCircleY));
float x1 = (float) (minCircleX + Math.cos(angle) * minR);
float y1 = (float) (minCircleY - Math.sin(angle) * minR);
float x2 = (float) (offsetX + Math.cos(angle) * maxR);
float y2 = (float) (offsetY - Math.sin(angle) * maxR);
float x3 = (float) (offsetX - Math.cos(angle) * maxR);
float y3 = (float) (offsetY + Math.sin(angle) * maxR);
float x4 = (float) (minCircleX - Math.cos(angle) * minR);
float y4 = (float) (minCircleY + Math.sin(angle) * minR);
float centerX = minCircleX + (offsetX - minCircleX) / 2;
float centerY = minCircleY + (offsetY - minCircleY) / 2;
path.reset();
path.moveTo(minCircleX, minCircleY);
path.lineTo(x1, y1);
path.quadTo(centerX, centerY, x2, y2);
path.lineTo(x3, y3);
path.quadTo(centerX, centerY, x4, y4);
path.lineTo(minCircleX, minCircleY);
path.close();
}
注意quadTo的四個(gè)參數(shù)的意義,前兩個(gè)是你的錨點(diǎn)的坐標(biāo),后兩個(gè)是你要移動(dòng)到那個(gè)點(diǎn)的位置的坐標(biāo)。
觸摸事件
這個(gè)直接上代碼來(lái)實(shí)現(xiàn)思路吧,沒(méi)什么好講的。
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
this.canBroke = false;
moveX = event.getX();
moveY = event.getY();
touchArea = !setCanBroke(moveX, moveY, maxR);
break;
case MotionEvent.ACTION_MOVE:
if (touchArea) {
moveX = event.getX();
moveY = event.getY();
if (setCanBroke(moveX, moveY, brokeDistance)) {
touchArea = false;
this.canBroke = true;
} else {
setPath(moveX, moveY);
}
invalidate();
}
break;
case MotionEvent.ACTION_UP:
Log.d("aaa", "actionUp" + touchArea);
if (touchArea) {
resetCircle(event.getX(), event.getY());
}
break;
}
return true;
這里主要說(shuō)明一下這個(gè)setCanBroke:
private boolean setCanBroke(float offsetX, float offsetY, float brokeDistance) {
float minCircleX = (float) getWidth() / 2;
float minCircleY = (float) getHeight() / 2;
return (offsetX - minCircleX) * (offsetX - minCircleX) +
(offsetY - minCircleY) * (offsetY - minCircleY) > brokeDistance * brokeDistance;
}
這個(gè)表示是否超出了最大移動(dòng)距離,超出則返回真,未超出則返回假。同時(shí)在touchArea的設(shè)定中它也用用到了,主要是判斷點(diǎn)擊區(qū)域是否在圓圈上。
最后還要講一下這個(gè)resetCicle,這個(gè)是一個(gè)屬性動(dòng)畫(huà)來(lái)控制返回原點(diǎn)的彈性動(dòng)畫(huà):
private void resetCircle(float x, float y) {
valueAnimatorX = ValueAnimator.ofFloat(x, (float) getWidth() / 2);
valueAnimatorY = ValueAnimator.ofFloat(y, (float) getHeight() / 2);
valueAnimatorX.removeAllUpdateListeners();
valueAnimatorY.removeAllUpdateListeners();
valueAnimatorX.setInterpolator(new BounceInterpolator());
valueAnimatorY.setInterpolator(new BounceInterpolator());
valueAnimatorX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
tempX = (float) animation.getAnimatedValue();
moveX = tempX;
}
});
valueAnimatorY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
tempY = (float) animation.getAnimatedValue();
moveY = tempY;
setPath(tempX, tempY);
postInvalidate();
}
});
set.playTogether(valueAnimatorX, valueAnimatorY);
set.start();
}
其中的插值器是BounceInterpolator,類(lèi)似于小球彈跳的動(dòng)畫(huà),在我前邊的文章中有介紹。
最后來(lái)看一下不會(huì)斷開(kāi)的效果,相當(dāng)有意思:

關(guān)于自定義view的文章會(huì)暫時(shí)到這里,下一步準(zhǔn)備更新自定義viewgroup的文章。相對(duì)于自定義view會(huì)稍微簡(jiǎn)單一點(diǎn)。
demo下載地址:PathApplication_jb51.rar
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
淺談Android開(kāi)發(fā)中ListView控件性能的一些優(yōu)化方法
這篇文章主要介紹了Android開(kāi)發(fā)中ListView控件性能的一些優(yōu)化方法,需要的朋友可以參考下2016-01-01
Android實(shí)現(xiàn)GridView中ImageView動(dòng)態(tài)變換的方法
這篇文章主要介紹了Android實(shí)現(xiàn)GridView中ImageView動(dòng)態(tài)變換的方法,以實(shí)例形式較為詳細(xì)的分析了GridView中ImageView動(dòng)態(tài)變換的頁(yè)面布局及功能實(shí)現(xiàn)相關(guān)技巧,需要的朋友可以參考下2015-10-10
???????Android?H5通用容器架構(gòu)設(shè)計(jì)詳解
這篇文章主要介紹了???????Android?H5通用容器架構(gòu)設(shè)計(jì)詳解,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09
android實(shí)現(xiàn)滑動(dòng)標(biāo)簽頁(yè)效果的代碼解析
這篇文章主要介紹了android實(shí)現(xiàn)滑動(dòng)標(biāo)簽頁(yè)效果,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
Android手把手教大家制作APP首頁(yè)(下拉刷新、自動(dòng)加載)
這篇文章主要為大家詳細(xì)介紹了Android手把手教大家制作APP首頁(yè),實(shí)現(xiàn)下拉刷新、自動(dòng)加載功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01
Android中Uri和Path之間的轉(zhuǎn)換的示例代碼
本篇文章主要介紹了Android中Uri和Path之間的轉(zhuǎn)換的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04

