android自定義view之模擬qq消息拖拽刪除效果
這個模擬功能的實現(xiàn)主要依靠了PATH和二階貝塞爾曲線。首先上一張圖來簡單看一下:
這個模擬功能有以下幾個特點:
- 在開始的時候點擊圓以外的區(qū)域不會觸發(fā)拖動事件
- 點擊圓的時候可以拖拽,此時會有一個拉伸效果,連接大圓和小圓
- 拉伸到一定距離(自己設定)以后兩個圓會斷開,此時即使再拖拽進距離之內的時候也不會再產(chǎn)生已經(jīng)斷開的連接
- 在距離之內松手的時候會回彈會原位置,并伴有一個彈跳動畫
介紹了這么多,看過我前邊文章的朋友應該會有一個基本思路。
暴露接口
這個模擬功能共分為三部分,一個是那個小圓,固定的位置,一個是那個大圓,可以移動,還有一部分就是中間的連接部分,會跟隨大圓一起延伸。
首先看一下都有哪些接口可以調用:
public void setMinR(float minR) { this.minR = minR; } public void setMaxR(float maxR) { this.maxR = maxR; } public void setBrokeDistance(float distance) { this.brokeDistance = distance; }
第一個setMinR是設置小圓的半徑,第二個setMaxR是設置大圓的半徑,第三個setBrokeDistance是設置斷開的距離,也就是小圓和大圓的圓心之間的最大連接距離。
初始化
public Buble(Context context) { super(context); init(); } public Buble(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); }
簡單的看一下初始化方法。
private void init() { paint = new Paint(); paint.setStyle(Paint.Style.FILL); paint.setColor(Color.GREEN); paint.setAntiAlias(true); }
其實只有一個畫筆,這里可以為各個區(qū)域分別設置畫筆。
繪制圖形
protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawOriginalCircle(canvas); if (!canBroke) { drawMoveCircle(canvas); drawBCurve(canvas); } }
這三個方法相對簡單,drawOriginalCircle是畫中心的小圓,然后canBroke是一個開關,控制是否執(zhí)行畫移動的圓和畫弧線。
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ā)生改變以后千萬不要忘記invalidate。
path的連接
關于path的文章網(wǎng)上一大堆。
此處的難點主要是大圓和小圓之間的連接。用一張圖簡單表示一下:
基本就是這個樣子,path的路徑就是那個黑色的類似于漏斗一樣的東西。此處要計算的角度需要用到三角函數(shù)關系式,簡單表示一下:
圖中標出的兩個角度是相等的
double angle = Math.atan((offsetX - minCircleX) / (offsetY - minCircleY));
求出這個角度(offsetX是移動圓心的坐標)。
這樣就可以算出四個點的坐標了。
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的四個參數(shù)的意義,前兩個是你的錨點的坐標,后兩個是你要移動到那個點的位置的坐標。
觸摸事件
這個直接上代碼來實現(xiàn)思路吧,沒什么好講的。
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;
這里主要說明一下這個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; }
這個表示是否超出了最大移動距離,超出則返回真,未超出則返回假。同時在touchArea的設定中它也用用到了,主要是判斷點擊區(qū)域是否在圓圈上。
最后還要講一下這個resetCicle,這個是一個屬性動畫來控制返回原點的彈性動畫:
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,類似于小球彈跳的動畫,在我前邊的文章中有介紹。
最后來看一下不會斷開的效果,相當有意思:
關于自定義view的文章會暫時到這里,下一步準備更新自定義viewgroup的文章。相對于自定義view會稍微簡單一點。
demo下載地址:PathApplication_jb51.rar
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
Android中Fab(FloatingActionButton)實現(xiàn)上下滑動的漸變效果
這篇文章主要給大家介紹了Android中FloatingActionButton(簡稱FAB)是如何實現(xiàn)上下滑動的漸變效果,文中給出了詳細的示例代碼,相信對大家具有一定的參考價值,有需要的朋友們可以一起看看吧。2017-02-02Android利用CountDownTimer實現(xiàn)點擊獲取驗證碼倒計時效果
這篇文章主要為大家詳細介紹了Android利用CountDownTimer實現(xiàn)點擊獲取驗證碼倒計時效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-03-03Android開發(fā)之關閉和打開Speaker(揚聲器)的方法
這篇文章主要介紹了Android開發(fā)之關閉和打開Speaker(揚聲器)的方法,結合實例形式簡單分析了Android揚聲器的操作技巧,具有一定參考借鑒價值,需要的朋友可以參考下2016-03-03Android編程之播放器MediaPlayer實現(xiàn)均衡器效果示例
這篇文章主要介紹了Android編程之播放器MediaPlayer實現(xiàn)均衡器效果,結合具體實例形式分析了Android調用MediaPlayer相關API構造均衡器的具體步驟與相關功能實現(xiàn)方法,需要的朋友可以參考下2017-08-08