最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android Path繪制貝塞爾曲線實現(xiàn)QQ拖拽泡泡

 更新時間:2016年09月06日 17:57:39   投稿:lqh  
本文主要介紹Android Path繪制貝塞爾曲線,這里整理相關(guān)資料并運(yùn)用貝塞爾曲線實現(xiàn)QQ拖拽泡泡的示例,有興趣的小伙伴可以參考下

這兩天學(xué)習(xí)了使用Path繪制貝塞爾曲線相關(guān),然后自己動手做了一個類似QQ未讀消息可拖拽的小氣泡,效果圖如下:

最終效果圖
接下來一步一步的實現(xiàn)整個過程。

基本原理

其實就是使用Path繪制三點(diǎn)的二次方貝塞爾曲線來完成那個妖嬈的曲線的。然后根據(jù)觸摸點(diǎn)不斷繪制對應(yīng)的圓形,根據(jù)距離的改變改變原始固定圓形的半徑大小。最后就是松手后返回或者爆裂的實現(xiàn)。

Path介紹:

顧名思義,就是一個路徑的意思,Path里面有很多的方法,本次設(shè)計主要用到的相關(guān)方法有

  1. moveTo() 移動Path到一個指定的點(diǎn)
  2. quadTo() 繪制二次貝塞爾曲線,接收兩個點(diǎn),第一個是控制弧度的點(diǎn),第二個是終點(diǎn)。
  3. lineTo() 就是連線
  4. close() 閉合Path路徑,
  5. reset() 重置Path的相關(guān)設(shè)置

Path入門熱身:

path.reset();
 path.moveTo(200, 200);
 //第一個坐標(biāo)是對應(yīng)的控制的坐標(biāo),第二個坐標(biāo)是終點(diǎn)坐標(biāo)
 path.quadTo(400, 250, 600, 200);

 canvas.drawPath(path, paint);
 canvas.translate(0, 200);
 //調(diào)用close,就會首尾閉合連接
 path.close();
 canvas.drawPath(path, paint);

記得不要在onDraw方法中new Path或者 Paint喲!

Path

具體實現(xiàn)拆分:

其實整個過程就是繪制了兩個貝塞爾二次曲線的的閉合Path路徑,然后在上面添加兩個圓形。

閉合的Path 路徑實現(xiàn)從左上點(diǎn)畫二次貝塞爾曲線到左下點(diǎn),左下點(diǎn)連線到右下點(diǎn),右下點(diǎn)二次貝塞爾曲線到右上點(diǎn),最后閉合一下!!

相關(guān)坐標(biāo)的確定

這是這次里面的難點(diǎn)之一,因為涉及到了數(shù)學(xué)里面的一個sin,cos,tan等等,我其實也忘完了,然后又腦補(bǔ)了一下,廢話不多說,

為什么自己要親自去畫一下呢,因為畫了你才知道,在360旋轉(zhuǎn)的過程中,角標(biāo)體系是有兩套的,如果就使用一套來畫的話,就畫出現(xiàn)在旋轉(zhuǎn)的過程中曲線重疊在一起的情況!

問題已經(jīng)拋出來了,接下來直接看看代碼實現(xiàn)!

角度確定

根據(jù)貼出來的原理圖可以知道,我們可以使用起始圓心坐標(biāo)和拖拽的圓心坐標(biāo),根據(jù)反正切函數(shù)來得到具體的弧度。


int dy = Math.abs(CIRCLEY - startY);
int dx = Math.abs(CIRCLEX - startX);
 angle = Math.atan(dy * 1.0 / dx);

ok,這里的startX,Y就是移動過程中的坐標(biāo)。angle就是得到的對應(yīng)的弧度(角度)。

相關(guān)Path繪制

前面已經(jīng)提到在旋轉(zhuǎn)的過程中有兩套坐標(biāo)體系,一開始我也很糾結(jié)這個坐標(biāo)體系要怎么確定,后面又恍然大悟,其實相當(dāng)于就是一三象限正比例增長,二四象限,反比例增長。

flag = (startY - CIRCLEY  ) * (startX- CIRCLEX ) <= 0;
 //增加一個flag,用于判斷使用哪種坐標(biāo)體系。

最最重要的來了,繪制相關(guān)的Path路徑!

 

path.reset();
 if (flag) {
  //第一個點(diǎn)
 path.moveTo((float) (CIRCLEX - Math.sin(angle) * ORIGIN_RADIO), (float) (CIRCLEY - Math.cos(angle) * ORIGIN_RADIO));

 path.quadTo((float) ((startX + CIRCLEX) * 0.5), (float) ((startY + CIRCLEY) * 0.5), (float) (startX - Math.sin(angle) * DRAG_RADIO), (float) (startY - Math.cos(angle) * DRAG_RADIO));
path.lineTo((float) (startX + Math.sin(angle) * DRAG_RADIO), (float) (startY + Math.cos(angle) * DRAG_RADIO));

path.quadTo((float) ((startX + CIRCLEX) * 0.5), (float) ((startY + CIRCLEY) * 0.5), (float) (CIRCLEX + Math.sin(angle) * ORIGIN_RADIO), (float) (CIRCLEY + Math.cos(angle) * ORIGIN_RADIO));
path.close();
canvas.drawPath(path, paint);
 } else {
  //第一個點(diǎn)
  path.moveTo((float) (CIRCLEX - Math.sin(angle) * ORIGIN_RADIO), (float) (CIRCLEY + Math.cos(angle) * ORIGIN_RADIO));

  path.quadTo((float) ((startX + CIRCLEX) * 0.5), (float) ((startY + CIRCLEY) * 0.5), (float) (startX - Math.sin(angle) * DRAG_RADIO), (float) (startY + Math.cos(angle) * DRAG_RADIO));
  path.lineTo((float) (startX + Math.sin(angle) * DRAG_RADIO), (float) (startY - Math.cos(angle) * DRAG_RADIO));

  path.quadTo((float) ((startX + CIRCLEX) * 0.5), (float) ((startY + CIRCLEY) * 0.5), (float) (CIRCLEX + Math.sin(angle) * ORIGIN_RADIO), (float) (CIRCLEY - Math.cos(angle) * ORIGIN_RADIO));
  path.close();
  canvas.drawPath(path, paint);
 }

這里的代碼就是把圖片上相關(guān)的數(shù)學(xué)公式Java化而已!

到這里,其實主要的工作就完成的差不多了!

接下來,設(shè)置paint 為填充的效果,最后再畫兩個圓

paint.setStyle(Paint.Style.FILL)
 canvas.drawCircle(CIRCLEX, CIRCLEY, ORIGIN_RADIO, paint);//默認(rèn)的
 canvas.drawCircle(startX == 0 ? CIRCLEX : startX, startY == 0 ? CIRCLEY : startY, DRAG_RADIO, paint);//拖拽的

就可以繪制出想要的效果了!

這里不得不再說說onTouch的處理!

case MotionEvent.ACTION_DOWN://有事件先攔截再說?。?
   getParent().requestDisallowInterceptTouchEvent(true);
   CurrentState = STATE_IDLE;
   animSetXY.cancel();
   startX = (int) ev.getX();
   startY = (int) ev.getRawY();
   break;

處理一下事件分發(fā)的坑!

測量和布局

這樣基本過得去了,但是我們的布局什么的還沒有處理,math_parent是萬萬沒法使用到具體項目當(dāng)中去的!
測量的時候,如果發(fā)現(xiàn)不是精準(zhǔn)模式,那么都手動去計算出需要的寬度和高度。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

 int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
 int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
 if (modeWidth == MeasureSpec.UNSPECIFIED || modeWidth == MeasureSpec.AT_MOST) {
  widthMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_RADIO * 2, MeasureSpec.EXACTLY);
 }
 if (modeHeight == MeasureSpec.UNSPECIFIED || modeHeight == MeasureSpec.AT_MOST) {
  heightMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_RADIO * 2, MeasureSpec.EXACTLY);
 }
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

然后在布局變化時,獲取相關(guān)坐標(biāo),確定初始圓心坐標(biāo):

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
 super.onSizeChanged(w, h, oldw, oldh);
 CIRCLEX = (int) ((w) * 0.5 + 0.5);
 CIRCLEY = (int) ((h) * 0.5 + 0.5);
}

然后清單文件里面就可以這樣配置了:

<com.lovejjfg.circle.DragBubbleView
 android:id="@+id/dbv"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_gravity="center"/>

這樣之后,又會出現(xiàn)一個問題,那就是wrap_content 之后,這個View能繪制的區(qū)域只有自身那么大了,拖拽了都看不見了!這個坑怎么辦呢,其實很簡單,父布局加上android:clipChildren="false" 的屬性!
這個坑也算是解決了?。?/p>

相關(guān)狀態(tài)的確定

我們是不希望它可以無限的拖拽的,就是有一個拖拽的最遠(yuǎn)距離,還有就是放手后的返回,爆裂。那么對應(yīng)的,這里需要確定幾種狀態(tài):

private final static int STATE_IDLE = 1;//靜止的狀態(tài)
 private final static int STATE_DRAG_NORMAL = 2;//正在拖拽的狀態(tài)
 private final static int STATE_DRAG_BREAK = 3;//斷裂后的拖拽狀態(tài)
 private final static int STATE_UP_BREAK = 4;//放手后的爆裂的狀態(tài)
 private final static int STATE_UP_BACK = 5;//放手后的沒有斷裂的返回的狀態(tài)
 private final static int STATE_UP_DRAG_BREAK_BACK = 6;//拖拽斷裂又返回的狀態(tài)
 private int CurrentState = STATE_IDLE;

private int MIN_RADIO = (int) (ORIGIN_RADIO * 0.4);//最小半徑
 private int MAXDISTANCE = (int) (MIN_RADIO * 13);//最遠(yuǎn)的拖拽距離

確定好這些之后,在move的時候,就要去做相關(guān)判斷了:

case MotionEvent.ACTION_MOVE://移動的時候
   startX = (int) ev.getX();
   startY = (int) ev.getY();

   updatePath();
   invalidate();
   break;

private void updatePath() {
 int dy = Math.abs(CIRCLEY - startY);
 int dx = Math.abs(CIRCLEX - startX);

 double dis = Math.sqrt(dy * dy + dx * dx);
 if (dis <= MAXDISTANCE) {//增加的情況,原始半徑減小
  if (CurrentState == STATE_DRAG_BREAK || CurrentState == STATE_UP_DRAG_BREAK_BACK) {
   CurrentState = STATE_UP_DRAG_BREAK_BACK;
  } else {
   CurrentState = STATE_DRAG_NORMAL;
  }
  ORIGIN_RADIO = (int) (DEFAULT_RADIO - (dis / MAXDISTANCE) * (DEFAULT_RADIO - MIN_RADIO));
  Log.e(TAG, "distance: " + (int) ((1 - dis / MAXDISTANCE) * MIN_RADIO));
  Log.i(TAG, "distance: " + ORIGIN_RADIO);
 } else {
  CurrentState = STATE_DRAG_BREAK;
 }
//  distance = dis;
 flag = (startY - CIRCLEY) * (startX - CIRCLEX) <= 0;
 Log.i("TAG", "updatePath: " + flag);
 angle = Math.atan(dy * 1.0 / dx);
}

updatePath() 的方法之前已經(jīng)看過部分了,這次的就是完整的。
這里做的事就是根據(jù)拖拽的距離更改相關(guān)的狀態(tài),并根據(jù)百分比來修改原始圓形的半徑大小。還有就是之前介紹的確定相關(guān)的弧度!

最后放手的時候:

case MotionEvent.ACTION_UP:
   if (CurrentState == STATE_DRAG_NORMAL) {
    CurrentState = STATE_UP_BACK;
    valueX.setIntValues(startX, CIRCLEX);
    valueY.setIntValues(startY, CIRCLEY);
    animSetXY.start();
   } else if (CurrentState == STATE_DRAG_BREAK) {
    CurrentState = STATE_UP_BREAK;
    invalidate();
   } else {
    CurrentState = STATE_UP_DRAG_BREAK_BACK;
    valueX.setIntValues(startX, CIRCLEX);
    valueY.setIntValues(startY, CIRCLEY);
    animSetXY.start();
   }
   break;

自動返回這里使用到的 ValueAnimator,

animSetXY = new AnimatorSet();

 valueX = ValueAnimator.ofInt(startX, CIRCLEX);
 valueY = ValueAnimator.ofInt(startY, CIRCLEY);
 animSetXY.playTogether(valueX, valueY);
 valueX.setDuration(500);
 valueY.setDuration(500);
 valueX.setInterpolator(new OvershootInterpolator());
 valueY.setInterpolator(new OvershootInterpolator());
 valueX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  @Override
  public void onAnimationUpdate(ValueAnimator animation) {
   startX = (int) animation.getAnimatedValue();
   Log.e(TAG, "onAnimationUpdate-startX: " + startX);
   invalidate();
  }

 });
 valueY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  @Override
  public void onAnimationUpdate(ValueAnimator animation) {
   startY = (int) animation.getAnimatedValue();
   Log.e(TAG, "onAnimationUpdate-startY: " + startY);
   invalidate();

  }
 });

最后在看看完整的onDraw方法吧!

@Override
protected void onDraw(Canvas canvas) {
 switch (CurrentState) {
  case STATE_IDLE://空閑狀態(tài),就畫默認(rèn)的圓
   if (showCircle) {
    canvas.drawCircle(CIRCLEX, CIRCLEY, ORIGIN_RADIO, paint);//默認(rèn)的
   }
   break;
  case STATE_UP_BACK://執(zhí)行返回的動畫
  case STATE_DRAG_NORMAL://拖拽狀態(tài) 畫貝塞爾曲線和兩個圓
   path.reset();
   if (flag) {
    //第一個點(diǎn)
    path.moveTo((float) (CIRCLEX - Math.sin(angle) * ORIGIN_RADIO), (float) (CIRCLEY - Math.cos(angle) * ORIGIN_RADIO));

    path.quadTo((float) ((startX + CIRCLEX) * 0.5), (float) ((startY + CIRCLEY) * 0.5), (float) (startX - Math.sin(angle) * DRAG_RADIO), (float) (startY - Math.cos(angle) * DRAG_RADIO));
    path.lineTo((float) (startX + Math.sin(angle) * DRAG_RADIO), (float) (startY + Math.cos(angle) * DRAG_RADIO));

    path.quadTo((float) ((startX + CIRCLEX) * 0.5), (float) ((startY + CIRCLEY) * 0.5), (float) (CIRCLEX + Math.sin(angle) * ORIGIN_RADIO), (float) (CIRCLEY + Math.cos(angle) * ORIGIN_RADIO));
    path.close();
    canvas.drawPath(path, paint);
   } else {
    //第一個點(diǎn)
    path.moveTo((float) (CIRCLEX - Math.sin(angle) * ORIGIN_RADIO), (float) (CIRCLEY + Math.cos(angle) * ORIGIN_RADIO));

    path.quadTo((float) ((startX + CIRCLEX) * 0.5), (float) ((startY + CIRCLEY) * 0.5), (float) (startX - Math.sin(angle) * DRAG_RADIO), (float) (startY + Math.cos(angle) * DRAG_RADIO));
    path.lineTo((float) (startX + Math.sin(angle) * DRAG_RADIO), (float) (startY - Math.cos(angle) * DRAG_RADIO));

    path.quadTo((float) ((startX + CIRCLEX) * 0.5), (float) ((startY + CIRCLEY) * 0.5), (float) (CIRCLEX + Math.sin(angle) * ORIGIN_RADIO), (float) (CIRCLEY - Math.cos(angle) * ORIGIN_RADIO));
    path.close();
    canvas.drawPath(path, paint);
   }
   if (showCircle) {
    canvas.drawCircle(CIRCLEX, CIRCLEY, ORIGIN_RADIO, paint);//默認(rèn)的
    canvas.drawCircle(startX == 0 ? CIRCLEX : startX, startY == 0 ? CIRCLEY : startY, DRAG_RADIO, paint);//拖拽的
   }
   break;

  case STATE_DRAG_BREAK://拖拽到了上限,畫拖拽的圓:
  case STATE_UP_DRAG_BREAK_BACK:
   if (showCircle) {
    canvas.drawCircle(startX == 0 ? CIRCLEX : startX, startY == 0 ? CIRCLEY : startY, DRAG_RADIO, paint);//拖拽的
   }
   break;

  case STATE_UP_BREAK://畫出爆裂的效果
   canvas.drawCircle(startX - 25, startY - 25, 10, circlePaint);
   canvas.drawCircle(startX + 25, startY + 25, 10, circlePaint);
   canvas.drawCircle(startX, startY - 25, 10, circlePaint);
   canvas.drawCircle(startX, startY, 18, circlePaint);
   canvas.drawCircle(startX - 25, startY, 10, circlePaint);
   break;

 }


}

到這里,成品就出來了!!

總結(jié):

1、確定默認(rèn)圓形的坐標(biāo);
2、根據(jù)move的情況,實時獲取最新的坐標(biāo),根據(jù)移動的距離(確定出角度),更新相關(guān)的狀態(tài),畫出相關(guān)的Path路徑。超出上限,不再畫Path路徑。
3、松手時,根據(jù)相關(guān)的狀態(tài),要么帶Path路徑執(zhí)行動畫返回,要么不帶Path路徑直接返回,要么直接爆裂!

以上就是用Android Path 繪制貝塞爾曲線的示例,后續(xù)繼續(xù)補(bǔ)充相關(guān)文章,謝謝大家對本站的支持!

相關(guān)文章

  • Android 實現(xiàn)單線程輪循機(jī)制批量下載圖片

    Android 實現(xiàn)單線程輪循機(jī)制批量下載圖片

    這篇文章主要介紹了Android 單線程輪循機(jī)制批量下載圖片的相關(guān)資料,這里對實現(xiàn)步驟做了詳細(xì)介紹,需要的朋友可以參考下
    2017-07-07
  • Android天氣預(yù)報之基于HttpGet對象解析天氣數(shù)據(jù)的方法

    Android天氣預(yù)報之基于HttpGet對象解析天氣數(shù)據(jù)的方法

    這篇文章主要介紹了Android天氣預(yù)報之基于HttpGet對象解析天氣數(shù)據(jù)的方法,非常實用的功能,需要的朋友可以參考下
    2014-08-08
  • Android自定義View實現(xiàn)五子棋游戲

    Android自定義View實現(xiàn)五子棋游戲

    這篇文章主要為大家詳細(xì)介紹了Android自定義View實現(xiàn)五子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-11-11
  • Android8.1 通過黑名單屏蔽系統(tǒng)短信和來電功能

    Android8.1 通過黑名單屏蔽系統(tǒng)短信和來電功能

    最近小編接到一個新的需求,需要將8.1 設(shè)備的來電功能和短信功能都屏蔽掉,特殊產(chǎn)品就是特殊定制。接下來通過本文給大家介紹Android8.1 通過黑名單屏蔽系統(tǒng)短信和來電功能,需要的朋友參考下吧
    2019-05-05
  • Android 應(yīng)用的安裝過程詳解

    Android 應(yīng)用的安裝過程詳解

    這篇文章主要介紹了Android 應(yīng)用的安裝過程詳解的相關(guān)資料,對應(yīng)Android應(yīng)用的安裝,我想大家應(yīng)該了解下的,需要的朋友可以參考下
    2016-11-11
  • Android 斷點(diǎn)續(xù)傳原理以及實現(xiàn)

    Android 斷點(diǎn)續(xù)傳原理以及實現(xiàn)

    這篇文章主要介紹了Android 斷點(diǎn)續(xù)傳原理以及實現(xiàn)的相關(guān)資料,這里對斷點(diǎn)續(xù)傳原理進(jìn)行了詳細(xì)介紹,需要的朋友可以參考下
    2016-12-12
  • Android仿微信雷達(dá)掃描效果的實現(xiàn)方法

    Android仿微信雷達(dá)掃描效果的實現(xiàn)方法

    最近看了一個視頻講了一種微信雷達(dá)掃描的實現(xiàn)方案,借鑒了一下,自己也寫一個玩玩,所以下面這篇文章主要給大家介紹了利用Android模仿微信雷達(dá)掃描效果的實現(xiàn)方法,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-06-06
  • Android開關(guān)控件Switch的使用案例

    Android開關(guān)控件Switch的使用案例

    今天小編就為大家分享一篇關(guān)于Android開關(guān)控件Switch的使用案例,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-03-03
  • 解析Android開發(fā)中多點(diǎn)觸摸的實現(xiàn)方法

    解析Android開發(fā)中多點(diǎn)觸摸的實現(xiàn)方法

    多點(diǎn)觸摸(MultiTouch),指的是允許計算機(jī)用戶同時通過多個手指來控制圖形界面的一種技術(shù)。與多點(diǎn)觸摸技術(shù)相對應(yīng)的就是單點(diǎn)觸摸,單點(diǎn)觸摸的設(shè)備已經(jīng)有很多年了,小尺寸的有觸摸式的手機(jī),大尺寸的最常見的就是銀行里的ATM機(jī)和排隊查詢機(jī)等等
    2013-05-05
  • Android異步加載數(shù)據(jù)和圖片的保存思路詳解

    Android異步加載數(shù)據(jù)和圖片的保存思路詳解

    這篇文章主要介紹了Android異步加載數(shù)據(jù)和圖片的保存思路詳解的相關(guān)資料,需要的朋友可以參考下
    2016-04-04

最新評論

临澧县| 九龙县| 叶城县| 外汇| 横山县| 城固县| 台山市| 长治县| 丹巴县| 苗栗县| 含山县| 临潭县| 咸丰县| 彰化县| 娄烦县| 泰来县| 潢川县| 上林县| 深州市| 广德县| 蒙山县| 永仁县| 四子王旗| 昭平县| 华阴市| 四平市| 葫芦岛市| 南阳市| 内黄县| 宁德市| 阳信县| 固镇县| 成武县| 辽阳市| 北川| 乡城县| 安乡县| 临颍县| 鄂伦春自治旗| 景泰县| 九龙城区|