【开源项目解析】QQ“一键下班”功能实现解析——学习Path及贝塞尔曲线的基本使用

早在很久很久以前,QQ就实现了“一键下班”功能。何为“一键下班”?当你QQ有信息时,下部会有信息数量提示红点,点击拖动之后,就会出现“一键下班”效果。本文将结合github上关于此功能的一个简单实现,介绍这个功能的基本实现思路。

项目地址

https://github.com/chenupt/BezierDemo

最终实现效果

实现原理解析

我个人感觉,这个效果实现的很漂亮啊!那么咱们就来看看实现原理是什么~

注:下面内容请参照项目源码观看。

其实如果从代码来看,实现的过程并不复杂,重点需要掌握的就是

  • path的用法
  • 贝塞尔曲线的使用。

这个项目的核心就是BezierView,继承自FrameLayout,拖动的时候,相当于覆盖在屏幕上一样。在init()方法中主要进行了以下操作

<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">private void init(){
        path = new Path()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>

        paint = new Paint()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        paint<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setAntiAlias</span>(true)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        paint<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setStyle</span>(Paint<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Style</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.FILL</span>_AND_STROKE)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        paint<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setStrokeWidth</span>(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        paint<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setColor</span>(Color<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.RED</span>)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>

        LayoutParams params = new LayoutParams(ViewGroup<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.LayoutParams</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.WRAP</span>_CONTENT, ViewGroup<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.LayoutParams</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.WRAP</span>_CONTENT)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        exploredImageView = new ImageView(getContext())<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        exploredImageView<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setLayoutParams</span>(params)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        exploredImageView<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setImageResource</span>(R<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.drawable</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.tip</span>_anim)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        exploredImageView<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setVisibility</span>(View<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.INVISIBLE</span>)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>

        tipImageView = new ImageView(getContext())<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        tipImageView<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setLayoutParams</span>(params)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        tipImageView<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setImageResource</span>(R<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.drawable</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.skin</span>_tips_newmessage_ninetynine)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>

        addView(tipImageView)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        addView(exploredImageView)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li></ul>

初始化了Path和Paint对象,然后动态生成了两个ImageView

  • exploredImageView 主要用来实现爆炸效果,默认不可见
  • tipImageView 手指进行拖动时的红色图标

exploredImageView设置的图片资源是一个AnimationDrawable,下面是res中的声明,控制每张图片的播放顺序和持续时间,这也很好理解

<code class="hljs xml has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-pi" style="color: rgb(0, 102, 102); box-sizing: border-box;"><?xml version="1.0" encoding="utf-8"?></span>
<span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">animation-list
</span>    <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">xmlns:android</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"http://schemas.android.com/apk/res/android"</span>
    <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:oneshot</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"true"</span>></span>
    <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">item</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:drawable</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"@drawable/idp"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:duration</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"300"</span>/></span>
    <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">item</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:drawable</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"@drawable/idq"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:duration</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"300"</span>/></span>
    <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">item</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:drawable</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"@drawable/idr"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:duration</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"300"</span>/></span>
    <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">item</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:drawable</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"@drawable/ids"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:duration</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"300"</span>/></span>
    <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">item</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:drawable</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"@drawable/idt"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:duration</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"300"</span>/></span>
    <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">item</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:drawable</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"@android:color/transparent"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:duration</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"300"</span>/></span>
<span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"></<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">animation-list</span>></span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li></ul>

我们在学习这种自定义控件的时候,可以按照View的绘制过程,对代码进行重点的查看,比如说,我们可以从 
下面这个顺序来对这个项目进行学习。

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

因为这个项目没有重写onMeasure(),所以我们直接从onLayout看看做了什么

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"> <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">protected</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onLayout</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> changed, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> left, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> top, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> right, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> bottom) {
        exploredImageView.setX(startX - exploredImageView.getWidth()/<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>);
        exploredImageView.setY(startY - exploredImageView.getHeight()/<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>);
        tipImageView.setX(startX - tipImageView.getWidth()/<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>);
        tipImageView.setY(startY - tipImageView.getHeight()/<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>);
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.onLayout(changed, left, top, right, bottom);
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>

代码还是非常还理解的,无非就是初始化了ImageView的位置,在这里出现了两个变量,startX和startY,这两个变量控制的是红点的初始化坐标,在整个过程中不会发生改变。

那么onDraw()呢?

<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">@Override
    protected void onDraw(Canvas canvas){
        if(isAnimStart || !isTouch){
            canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.drawColor</span>(Color<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.TRANSPARENT</span>, PorterDuff<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Mode</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.OVERLAY</span>)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        }else{
            calculate()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
            canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.drawColor</span>(Color<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.TRANSPARENT</span>, PorterDuff<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Mode</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.OVERLAY</span>)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
            canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.drawPath</span>(path, paint)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
            canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.drawCircle</span>(startX, startY, radius, paint)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
            canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.drawCircle</span>(<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">x</span>, <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">y</span>, radius, paint)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        }
        super<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.onDraw</span>(canvas)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li></ul>

onDraw()里面的操作也并不复杂,如果正在执行动画或者是没有在触摸模式,就画一个透明的颜色,否则,就开始画真正的界面了。calculate()这个方法是一个重点,从命名来看应该是计算了一些坐标值,然后开始画了两个圆,这两个圆的坐标,一个是(startX,startY),另一个是(x,y),颜色和半径都是相同的,这个是为了简化计算,所以将两个圆的半径设置成相同的啦。

我们先继续看一下在onTouchEvent()里面进行了什么操作

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">@Override
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> boolean <span class="hljs-title" style="box-sizing: border-box;">onTouchEvent</span>(MotionEvent <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>) {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>.getAction() == MotionEvent.ACTION_DOWN){
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 判断触摸点是否在tipImageView中</span>
            Rect rect = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> Rect();
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>[] location = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>];
            tipImageView.getDrawingRect(rect);
            tipImageView.getLocationOnScreen(location);
            rect.left = location[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>];
            rect.top = location[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>];
            rect.right = rect.right + location[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>];
            rect.bottom = rect.bottom + location[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>];
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (rect.contains((<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>.getRawX(), (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>.getRawY())){
                isTouch = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>;
            }
        }<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>.getAction() == MotionEvent.ACTION_UP || <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>.getAction() == MotionEvent.ACTION_CANCEL){
            isTouch = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>;
            tipImageView.setX(startX - tipImageView.getWidth()/<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>);
            tipImageView.setY(startY - tipImageView.getHeight()/<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>);
        }
        invalidate();
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span>(isAnimStart){
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> super.onTouchEvent(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>);
        }
        anchorX =  (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>.getX() + startX)/<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>;
        anchorY =  (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>.getY() + startY)/<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>;
        x =  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>.getX();
        y =  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">event</span>.getY();
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>;
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li></ul>

首先在按下的时候,取得了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么~

所以

<code class="hljs glsl has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> offsetX = (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>) (radius*Math.<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">sin</span>(Math.<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">atan</span>((y - startY) / (x - startX))));
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> offsetY = (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>) (radius*Math.<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">cos</span>(Math.<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">atan</span>((y - startY) / (x - startX))));</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

那么。,,现在再来看下面的代码,你还说你看不懂吗?

<code class="hljs glsl has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">private <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> calculate(){
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">distance</span> = (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>) Math.<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">sqrt</span>(Math.<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">pow</span>(y-startY, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>) + Math.<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">pow</span>(x-startX, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>));
        radius = -<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">distance</span>/<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">15</span>+DEFAULT_RADIUS;

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span>(radius < <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">9</span>){
            isAnimStart = <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">true</span>;

            exploredImageView.setVisibility(View.VISIBLE);
            exploredImageView.setImageResource(R.drawable.tip_anim);
            ((AnimationDrawable) exploredImageView.getDrawable()).stop();
            ((AnimationDrawable) exploredImageView.getDrawable()).start();

            tipImageView.setVisibility(View.GONE);
        }

        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 根据角度算出四边形的四个点</span>
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> offsetX = (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>) (radius*Math.<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">sin</span>(Math.<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">atan</span>((y - startY) / (x - startX))));
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> offsetY = (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>) (radius*Math.<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">cos</span>(Math.<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">atan</span>((y - startY) / (x - startX))));

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> x1 = startX - offsetX;
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> y1 = startY + offsetY;

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> x2 = x - offsetX;
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> y2 = y + offsetY;

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> x3 = x + offsetX;
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> y3 = y - offsetY;

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> x4 = startX + offsetX;
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> 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);

        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 更改图标的位置</span>
        tipImageView.setX(x - tipImageView.getWidth()/<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>);
        tipImageView.setY(y - tipImageView.getHeight()/<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>);
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li></ul>

算出4个点的坐标,并且知道锚点位置,用path连起来就Ok啦

肚子饿了,这一篇就到这里了,下去吃饭饭

相关项目及文章

相关项目


尊重原创,转载请注明:From 凯子哥(http://blog.csdn.net/zhaokaiqiang1992) 侵权必究!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值