自定義View之qq氣泡拖拽消失效果


先看效果圖(本篇實現(xiàn)氣泡拖拽消失,沒有實現(xiàn)回彈和消失時爆炸效果)


氣泡拖拽消失

觀察效果圖,可以發(fā)現(xiàn)其中有兩個圓(固定圓和拖拽圓)加上中間拖拽的貝塞爾效果,其中固定圓的半徑隨著拉伸不斷變小,小到一定值時消失,手指松開,如果固定圓還存在則回彈,不存在則拖拽圓也消失,本篇沒實現(xiàn)的是回彈時的貝塞爾效果,已經(jīng)拖拽圓消失時爆炸的效果,如果有機會實現(xiàn)了會再進行講解。

先來初始化代碼,我們定義為BubbleView類

public class BubbleView extends View {
 private int mFixCircleRadius = 20;//固定圓的半徑,其半徑會隨著拉伸變小,我們設(shè)定最小是6,小于6的時候固定圓消失
    private int mDragCircleRadius = 20;//拖拽時顯示的園
    private PointF mFixCirclePoint;//固定圓圓心,本文只是演示,具體使用時要自己計算view的大小,再設(shè)置合適的圓心和半徑
    private PointF mDragCirclePoint;//拖拽圓的圓心,隨著拖拽不斷變化
    private Paint paint;//畫筆
    private float distance;//兩個圓心的距離
    private boolean isDrag = false;//判斷手指按下時是否落在固定圓的區(qū)域
    private boolean isFixCircleShow = true;//固定圓是否顯示
    private boolean isUp = false;//手指是否彈起,彈起時如果固定圓顯示這時候要回彈回來,如果固定圓消失了,此時應該執(zhí)行拖拽圓爆炸消失效果,本文演示只做消失效果

    public BubbleView(Context context) {
        this(context, null);
    }

    public BubbleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BubbleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    //初始化畫筆,圓心
    private void init() {
        mFixCirclePoint = new PointF(300, 300);
        mDragCirclePoint = new PointF(300, 300);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setColor(getResources().getColor(R.color.red));
    }
}

接著我們監(jiān)聽onTouchEvent事件,在ACTION_DOWN的時候判斷手指是否落在了固定圓的區(qū)域,如果沒有不響應本次拖拽事件,接著在ACTION_MOVE中計算拉伸的距離,并不斷的改變固定圓的大小(拉伸距離越大半徑越?。┮约袄靾A的圓心(手指移動到的點),在ACTION_UP中我們恢復初始值,代碼如下:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        float downX = event.getX();
        float downY = event.getY();
        isUp = false;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                isDrag(downX, downY);//判斷是否在固定圓區(qū)域,不在不顯示拖拽效果
                break;
            case MotionEvent.ACTION_MOVE:
                if (!isDrag) return true;
                //判斷是否出現(xiàn)拉伸效果
                if (Math.abs(event.getX() - mFixCirclePoint.x )> mFixCircleRadius || Math.abs(event.getY() - mFixCirclePoint.y) > mFixCircleRadius) {
                    mDragCirclePoint.x = event.getX();//不斷改變拖拽圓的圓心
                    mDragCirclePoint.y = event.getY();
                    float dx = mFixCirclePoint.x - mDragCirclePoint.x;
                    float dy = mFixCirclePoint.y - mDragCirclePoint.y;
                    distance = (float) Math.hypot(Math.abs(dx), Math.abs(dy));//計算拖拽的距離
                    postInvalidate();
                }
                break;
            case MotionEvent.ACTION_UP:
                isDrag = false;
                isUp = true;
                mFixCircleRadius = 20;
                mDragCirclePoint.x = event.getX();
                mDragCirclePoint.y = event.getY();
                postInvalidate();
                break;
        }

        return true;
    }

    //判斷手指是否落在了固定的小圓內(nèi)
    private void isDrag(float x, float y) {
        if (x >= mFixCirclePoint.x - mFixCircleRadius-20 && x <= mFixCirclePoint.x + mFixCircleRadius+20 && y >= mFixCirclePoint.y - mFixCircleRadius-20 && y <= mFixCirclePoint.y + mFixCircleRadius+20)
            isDrag = true;
        else isDrag = false;
    }

接下來就是繪制,繪制過程中除了兩個圓,最主要的就是中間的拉伸效果,拉伸效果實際上是一個貝塞爾曲線,如下圖


本圖來自 http://blog.csdn.net/z240336124/article/details/76409397

最主要的就是計算p0,p1,p2,p3的點,其中我們知道c0,c1的位置,然后根據(jù)三角函數(shù)就可以計算出這4個點,然后p0到p1是一個二階貝塞爾曲線,p2到p3是一個貝塞爾曲線,控制點我們選為c0,c1中間的位置,代碼如下:

 //繪制拉伸的效果
    private void drawBezer(Canvas canvas) {
        Path path = new Path();
        //計算4個控制點
        float dx = mFixCirclePoint.x - mDragCirclePoint.x;
        float dy = mFixCirclePoint.y - mDragCirclePoint.y;
        if (dx == 0) {
            dx = 0.001f;
        }
        float tan = dy / dx;
        // 獲取角a度值
        float arcTanA = (float) Math.atan(tan);

        // 依次計算 p0 , p1 , p2 , p3 點的位置
        float P0X = (float) (mFixCirclePoint.x + mFixCircleRadius * Math.sin(arcTanA));
        float P0Y = (float) (mFixCirclePoint.y - mFixCircleRadius * Math.cos(arcTanA));

        float P1X = (float) (mDragCirclePoint.x + mDragCircleRadius * Math.sin(arcTanA));
        float P1Y = (float) (mDragCirclePoint.y - mDragCircleRadius * Math.cos(arcTanA));

        float P2X = (float) (mDragCirclePoint.x - mDragCircleRadius * Math.sin(arcTanA));
        float P2Y = (float) (mDragCirclePoint.y + mDragCircleRadius * Math.cos(arcTanA));

        float P3X = (float) (mFixCirclePoint.x - mFixCircleRadius * Math.sin(arcTanA));
        float P3Y = (float) (mFixCirclePoint.y + mFixCircleRadius * Math.cos(arcTanA));
        PointF controlPoint = new PointF(mFixCirclePoint.x + (mDragCirclePoint.x - mFixCirclePoint.x) / 2, (mFixCirclePoint.y + (mDragCirclePoint.y - mFixCirclePoint.y) / 2));
        // 整合貝塞爾曲線路徑
        path.moveTo(P0X, P0Y);
        path.quadTo(controlPoint.x, controlPoint.y, P1X, P1Y);
        path.lineTo(P2X, P2Y);
        path.quadTo(controlPoint.x, controlPoint.y, P3X, P3Y);
        path.close();
        canvas.drawPath(path, paint);
    }

最后是整個的繪制過程代碼:

  @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (!isUp) {
            //手指沒有彈起時
            mFixCircleRadius = (int) (20 - distance / 14);
            //初始化時只畫固定圓
            if (!isDrag)
                canvas.drawCircle(mFixCirclePoint.x, mFixCirclePoint.y, mFixCircleRadius, paint);
            else {
                //拖拽時不斷減小固定圓半徑,如果小于6時,固定圓消失
                if (mFixCircleRadius <= 6) {
                    isFixCircleShow = false;
                }
                if (mFixCircleRadius > 6 && isFixCircleShow) {
                    canvas.drawCircle(mFixCirclePoint.x, mFixCirclePoint.y, mFixCircleRadius, paint);
                    drawBezer(canvas);//繪制拖拽的效果
                }
                canvas.drawCircle(mDragCirclePoint.x, mDragCirclePoint.y, mDragCircleRadius, paint);
            }
        } else {
            //手指彈起時,如果固定圓沒有消失,顯示回彈效果
            if (isFixCircleShow)
                canvas.drawCircle(mFixCirclePoint.x, mFixCirclePoint.y, mFixCircleRadius, paint);
        }
    }

最后貼一下本篇的完整代碼

/**
 * Created by liuyong
 * Data: 2017/8/9
 * Github:https://github.com/MrAllRight
 * qq未讀消息拖拽消失
 */

public class BubbleView extends View {
    private int mFixCircleRadius = 20;//固定圓的半徑,其半徑會隨著拉伸變小,我們設(shè)定最小是6,小于6的時候固定圓消失
    private int mDragCircleRadius = 20;//拖拽時顯示的園
    private PointF mFixCirclePoint;//固定圓圓心,本文只是演示,具體使用時要自己計算view的大小,再設(shè)置合適的圓心和半徑
    private PointF mDragCirclePoint;//拖拽圓的圓心,隨著拖拽不斷變化
    private Paint paint;//畫筆
    private float distance;//兩個圓心的距離
    private boolean isDrag = false;//判斷手指按下時是否落在固定圓的區(qū)域
    private boolean isFixCircleShow = true;//固定圓是否顯示
    private boolean isUp = false;//手指是否彈起,彈起時如果固定圓顯示這時候要回彈回來,如果固定圓消失了,此時應該執(zhí)行拖拽圓爆炸消失效果,本文演示只做消失效果

    public BubbleView(Context context) {
        this(context, null);
    }

    public BubbleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BubbleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    //初始化畫筆,圓心
    private void init() {
        mFixCirclePoint = new PointF(300, 300);
        mDragCirclePoint = new PointF(300, 300);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setColor(getResources().getColor(R.color.red));
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float downX = event.getX();
        float downY = event.getY();
        isUp = false;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                isDrag(downX, downY);//判斷是否在固定圓區(qū)域,不在不顯示拖拽效果
                break;
            case MotionEvent.ACTION_MOVE:
                if (!isDrag) return true;
                //判斷是否出現(xiàn)拉伸效果
                if (Math.abs(event.getX() - mFixCirclePoint.x )> mFixCircleRadius || Math.abs(event.getY() - mFixCirclePoint.y) > mFixCircleRadius) {
                    mDragCirclePoint.x = event.getX();//不斷改變拖拽圓的圓心
                    mDragCirclePoint.y = event.getY();
                    float dx = mFixCirclePoint.x - mDragCirclePoint.x;
                    float dy = mFixCirclePoint.y - mDragCirclePoint.y;
                    distance = (float) Math.hypot(Math.abs(dx), Math.abs(dy));//計算拖拽的距離
                    postInvalidate();
                }
                break;
            case MotionEvent.ACTION_UP:
                isDrag = false;
                isUp = true;
                mFixCircleRadius = 20;
                mDragCirclePoint.x = event.getX();
                mDragCirclePoint.y = event.getY();
                postInvalidate();
                break;
        }

        return true;
    }

    //判斷手指是否落在了固定的小圓內(nèi)
    private void isDrag(float x, float y) {
        if (x >= mFixCirclePoint.x - mFixCircleRadius-20 && x <= mFixCirclePoint.x + mFixCircleRadius+20 && y >= mFixCirclePoint.y - mFixCircleRadius-20 && y <= mFixCirclePoint.y + mFixCircleRadius+20)
            isDrag = true;
        else isDrag = false;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (!isUp) {
            //手指沒有彈起時
            mFixCircleRadius = (int) (20 - distance / 14);
            //初始化時只畫固定圓
            if (!isDrag)
                canvas.drawCircle(mFixCirclePoint.x, mFixCirclePoint.y, mFixCircleRadius, paint);
            else {
                //拖拽時不斷減小固定圓半徑,如果小于6時,固定圓消失
                if (mFixCircleRadius <= 6) {
                    isFixCircleShow = false;
                }
                if (mFixCircleRadius > 6 && isFixCircleShow) {
                    canvas.drawCircle(mFixCirclePoint.x, mFixCirclePoint.y, mFixCircleRadius, paint);
                    drawBezer(canvas);//繪制拖拽的效果
                }
                canvas.drawCircle(mDragCirclePoint.x, mDragCirclePoint.y, mDragCircleRadius, paint);
            }
        } else {
            //手指彈起時,如果固定圓沒有消失,顯示回彈效果
            if (isFixCircleShow)
                canvas.drawCircle(mFixCirclePoint.x, mFixCirclePoint.y, mFixCircleRadius, paint);
        }
    }
   //繪制拉伸的效果
    private void drawBezer(Canvas canvas) {
        Path path = new Path();
        //計算4個控制點
        float dx = mFixCirclePoint.x - mDragCirclePoint.x;
        float dy = mFixCirclePoint.y - mDragCirclePoint.y;
        if (dx == 0) {
            dx = 0.001f;
        }
        float tan = dy / dx;
        // 獲取角a度值
        float arcTanA = (float) Math.atan(tan);

        // 依次計算 p0 , p1 , p2 , p3 點的位置
        float P0X = (float) (mFixCirclePoint.x + mFixCircleRadius * Math.sin(arcTanA));
        float P0Y = (float) (mFixCirclePoint.y - mFixCircleRadius * Math.cos(arcTanA));

        float P1X = (float) (mDragCirclePoint.x + mDragCircleRadius * Math.sin(arcTanA));
        float P1Y = (float) (mDragCirclePoint.y - mDragCircleRadius * Math.cos(arcTanA));

        float P2X = (float) (mDragCirclePoint.x - mDragCircleRadius * Math.sin(arcTanA));
        float P2Y = (float) (mDragCirclePoint.y + mDragCircleRadius * Math.cos(arcTanA));

        float P3X = (float) (mFixCirclePoint.x - mFixCircleRadius * Math.sin(arcTanA));
        float P3Y = (float) (mFixCirclePoint.y + mFixCircleRadius * Math.cos(arcTanA));
        PointF controlPoint = new PointF(mFixCirclePoint.x + (mDragCirclePoint.x - mFixCirclePoint.x) / 2, (mFixCirclePoint.y + (mDragCirclePoint.y - mFixCirclePoint.y) / 2));
        // 整合貝塞爾曲線路徑
        path.moveTo(P0X, P0Y);
        path.quadTo(controlPoint.x, controlPoint.y, P1X, P1Y);
        path.lineTo(P2X, P2Y);
        path.quadTo(controlPoint.x, controlPoint.y, P3X, P3Y);
        path.close();
        canvas.drawPath(path, paint);
    }
}

也可到github上下載整個貝塞爾曲線的代碼,其中有水波紋,直播點贊,還有本篇qq氣泡拖拽效果,地址:https://github.com/MrAllRight/BezierView

寫完發(fā)現(xiàn)這個還是參考紅橙Darren的文章,大家可以去他的簡書看一下,里面有自定義view的系列,講解的都不錯,而且還有視頻講解。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,937評論 25 709
  • 光棍節(jié)快到了,提前祝愿廣大的單身猿猴,早日脫單,盡快找到另一半。 一直覺得 QQ 的小紅點非常具有創(chuàng)新,新穎。要是...
    文淑閱讀 1,124評論 4 11
  • 原文地址 這個模擬功能的實現(xiàn)主要依靠了PATH和二階貝塞爾曲線。首先上一張圖來簡單看一下: 這個模擬功能有以下幾個...
    sakasa閱讀 597評論 0 1
  • 四周都是模糊的,空間里的聲音也聽不太清,雙耳悶堵得慌,像突然海拔升高而產(chǎn)生的高原反應。 我卷著褲腳站在泥地里,頭頂...
    毛阿花閱讀 659評論 8 6
  • 1. 小姨比我大23歲,從我記事起,我就知道小姨打小就是個名人,出名的聰明,出名的學習好,而且是在那個年代趴著睡覺...
    不離能斷閱讀 983評論 0 1

友情鏈接更多精彩內(nèi)容