Android自定义View实现QQ消息气泡

本文实例为大家分享了Android自定义View实现QQ消息气泡的具体代码,供大家参考,具体内容如下

效果图:

原理:

控件源码:

public class DragView extends View {

    private int defaultZoomSize = 8;
    //初始化圆的大小
    private int initRadius;
    //圆1的圆心位置
    private PointF center1;
    private PointF center2;

    private PointF point1;
    private PointF point2;
    private PointF point3;
    private PointF point4;

    private int mWidth;
    private int mHeight;

    private float realZoomSize;
    private float currentRadius;
    private float minRadiusScale = 1 / 2f;

    private Paint paint;
    private Path path;
    private Bitmap bitmap;

    @DragStatus
    private int mDragStatus;

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

    public DragView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DragView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(4);
        paint.setAntiAlias(true);

        path = new Path();
        center1 = new PointF();
        center2 = new PointF();
        point1 = new PointF();
        point2 = new PointF();
        point3 = new PointF();
        point4 = new PointF();

        bitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.icon_pot);
        initRadius = Math.min(bitmap.getWidth(), bitmap.getHeight()) / 2;
        Log.e("zhen", "解析bitmap: " + bitmap.getWidth() + " * " + bitmap.getHeight() + " * " + initRadius);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
        center1.set(mWidth / 2, mHeight / 2);
        Log.d("zhen", "圆心位置:x" + center1.x + " y: " + center1.y);
    }

    private boolean isSelected = false;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (Math.sqrt(Math.pow(x - center1.x, 2) + Math.pow(y - center1.y, 2)) < initRadius
                        && mDragStatus == DragStatus.NORMAL) {
                    inAnimation = false;
                    isSelected = true;
                    Log.e("zhen", "选中状态");
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (isSelected) {
//                    Log.d("zhen", "拖动距离: " + dragDistance);
                    if (mDragStatus != DragStatus.DRAG_BACK && mDragStatus != DragStatus.DRAG_TO) {
                        mDragStatus = DragStatus.DRAG_MOVE;
                        center2.set(x, y);
                        float dragDistance = (float) (Math.sqrt(Math.pow(center2.x - center1.x, 2)
                                + Math.pow(center2.y - center1.y, 2)));
                        //多少倍圆的大小
                        realZoomSize = dragDistance / initRadius;
                        invalidate();
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (isSelected) {
                    if (realZoomSize <= defaultZoomSize) {
                        //回弹,改变center2.x, center2.y直到等于center1.x, center1.y
                        doAnimation(DragStatus.DRAG_BACK, center2, center1);
                    }
                }
                isSelected = false;
                break;
        }
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //圆的半径改变
        currentRadius = initRadius * (1 + (minRadiusScale - 1) / defaultZoomSize * realZoomSize);
        if (realZoomSize > defaultZoomSize) {
            //圆缩小为一半,去往目的地,就应该消失了
            doAnimation(DragStatus.DRAG_TO, center1, center2);
        }
        //中间矩形
//        paint.setColor(Color.BLACK);
        float angle = (float) Math.atan((center2.y - center1.y) / (center2.x - center1.x));

        float sinValue;
        float cosValue;
        float controlX;
        float controlY;
        sinValue = (float) Math.abs((currentRadius * Math.sin(angle)));
        cosValue = (float) Math.abs((currentRadius * Math.cos(angle)));
        controlX = (center1.x + center2.x) / 2;
        controlY = (center1.y + center2.y) / 2;
        point1.set(center1.x - sinValue, center1.y - cosValue);
        point2.set(center1.x + sinValue, center1.y + cosValue);
        point3.set(center2.x - sinValue, center2.y - cosValue);
        point4.set(center2.x + sinValue, center2.y + cosValue);

        path.reset();
        switch (mDragStatus) {
            case DragStatus.NORMAL:
                currentRadius = initRadius;
                //原始图片
                canvas.drawBitmap(bitmap, center1.x - initRadius, center1.y - initRadius, paint);
                //起始位置的圆
//                paint.setColor(Color.RED);
//                canvas.drawCircle(center1.x, center1.y, currentRadius, paint);
                break;
            case DragStatus.DRAG_MOVE:
                //拖动过程中
                path.moveTo(point1.x, point1.y);
                path.lineTo(point2.x, point2.y);
                path.quadTo(controlX, controlY, point4.x, point4.y);
                path.lineTo(point3.x, point3.y);
                path.quadTo(controlX, controlY, point1.x, point1.y);
                canvas.drawPath(path, paint);
                //起始位置的圆
                paint.setColor(Color.RED);
                canvas.drawCircle(center1.x, center1.y, currentRadius, paint);
                //结束位置的圆
//                paint.setColor(Color.BLUE);
//                canvas.drawCircle(center2.x, center2.y, currentRadius, paint);
                //原始图片
                canvas.drawBitmap(bitmap, center2.x - initRadius, center2.y - initRadius, paint);
                break;
            case DragStatus.DRAG_BACK:
                //改变center2.x, center2.y直到等于center1.x, center1.y
                path.reset();
                path.moveTo(point1.x, point1.y);
                path.quadTo(center2.x, center2.y, point2.x, point2.y);
                canvas.drawPath(path, paint);
                //起始位置的圆
//                paint.setColor(Color.RED);
//                canvas.drawCircle(center1.x, center1.y, currentRadius, paint);
                //原始图片
                canvas.drawBitmap(bitmap, center1.x - initRadius, center1.y - initRadius, paint);
                break;
            case DragStatus.DRAG_TO:
                //改变center1.x, center1.y,直到等于center2.x, center2.y
                path.reset();
                path.moveTo(point3.x, point3.y);
                path.quadTo(center1.x, center1.y, point4.x, point4.y);
                canvas.drawPath(path, paint);
//                //起始位置的圆
//                paint.setColor(Color.RED);
//                canvas.drawCircle(center1.x, center1.y, currentRadius, paint);
//                //结束位置的圆
//                paint.setColor(Color.BLUE);
//                canvas.drawCircle(center2.x, center2.y, currentRadius, paint);
                //原始图片
                canvas.drawBitmap(bitmap, center2.x - initRadius, center2.y - initRadius, paint);
                break;
        }
//        Log.d("zhen", "dragStatus: " + mDragStatus + " 圆1:" + center1 + " 圆2:" + center2 + " 半径: " + currentRadius);
//        Log.w("zhen", "dragStatus: " + mDragStatus + " point3:" + point3 + " point4" + point4 + " sinValue " + sinValue + " cosValue " + cosValue);
        Log.w("zhen", "dragStatus: " + mDragStatus + " 圆1:" + center1 + " 圆2:" + center2 + " 半径: " + currentRadius);

    }

    int i = 0;
    private boolean inAnimation = false;

    private void doAnimation(int dragStatus, final PointF startPoint, final PointF endPoint) {
        if (inAnimation) return;
        inAnimation = true;
        final int step = 10;
        final float stepx = (endPoint.x - startPoint.x) / step;
        final float stepy = (endPoint.y - startPoint.y) / step;
        i = 1;
        mDragStatus = dragStatus;
        Log.d("zhen", "dragStatus: " + mDragStatus + " startPoint:" + startPoint
                + " endPoint:" + endPoint + " stepx: " + stepy + " stepx: " + stepy);
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (i <= step) {
                    startPoint.x += stepx;
                    startPoint.y += stepy;
                    postInvalidate();
                    i++;
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                mDragStatus = DragStatus.NORMAL;
                invalidate();

                Log.e("zhen", "恢复为可拖动状态");
            }
        }).start();
    }

    @IntDef({DragStatus.DRAG_MOVE, DragStatus.DRAG_TO, DragStatus.DRAG_BACK})
    public @interface DragStatus {
        int NORMAL = 0;
        //拖动中
        int DRAG_MOVE = 1;
        //
        int DRAG_TO = 2;
        //回弹
        int DRAG_BACK = 3;
    }

}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android贝塞尔曲线初步学习第二课 仿QQ未读消息气泡拖拽黏连效果

    上一节初步了解了Android端的贝塞尔曲线,这一节就举个栗子练习一下,仿QQ未读消息气泡,是最经典的练习贝塞尔曲线的东东,效果如下 附上github源码地址:https://github.com/MonkeyMushroom/DragBubbleView 欢迎star~ 大体思路就是画两个圆,一个黏连小球固定在一个点上,一个气泡小球跟随手指的滑动改变坐标.随着两个圆间距越来越大,黏连小球半径越来越小.当间距小于一定值,松开手指气泡小球会恢复原来位置:当间距超过一定值之后,黏连小球消失,气泡小球

  • Android使用贝塞尔曲线仿QQ聊天消息气泡拖拽效果

    本文实例为大家分享了Android仿QQ聊天消息气泡拖拽效果展示的具体代码,供大家参考,具体内容如下 先画圆,都会吧.代码如下: public class Bezier extends View { private final Paint mGesturePaint = new Paint(); private final Path mPath = new Path(); private float mX1 = 100, mY1 = 150; private float mX2 = 300, m

  • Android未读消息拖动气泡示例代码详解(附源码)

    前言 拖动清除未读消息可以说在很多应用中都很常见,也被用户广泛接受.本文是一个可以供参考的Demo,希望能有帮助. 提示:以下是本篇文章正文内容,下面案例可供参考 最终效果图及思路 实现关键: 气泡中间的两条边,分别是以ab,cd为数据点,G为控制点的贝塞尔曲线. 步骤: 绘制圆背景以及文本:连接情况绘制贝塞尔曲线:另外端点绘制一个圆 关键代码 1.定义,初始化等 状态:静止.连接.分离.消失 在onSizeChanged中初始化状态,固定气泡以及可动气泡的圆心 代码如下(示例): @Overr

  • Android自定义View实现QQ消息气泡

    本文实例为大家分享了Android自定义View实现QQ消息气泡的具体代码,供大家参考,具体内容如下 效果图: 原理: 控件源码: public class DragView extends View {     private int defaultZoomSize = 8;     //初始化圆的大小     private int initRadius;     //圆1的圆心位置     private PointF center1;     private PointF center2

  • Android自定义View 仿QQ侧滑菜单的实现代码

    先看看QQ的侧滑效果 分析一下 先上原理图(不知道能否表达的清楚 ==) -首先这里使用了 Android 的HorizontalScrollView 水平滑动布局作为容器,当然我们需要继承它自定义一个侧滑视图 - 这个容器里面有一个父布局(一般用LinerLayout,本demo用的是),这个父布局里面有且只有两个子控件(布局),初始状态菜单页的位置在Y轴上存在偏移这样可以就可以形成主页叠在菜单页的上方的视觉效果:然后在滑动的过程程中 逐渐修正偏移,最后菜单页和主页并排排列.原理搞清了实现起来

  • Android自定义view仿QQ的Tab按钮动画效果(示例代码)

    话不多说 先上效果图 实现其实很简单,先用两张图 一张是背景的图,一张是笑脸的图片,笑脸的图片是白色,可能看不出来.实现思路:主要是再触摸view的时候同时移动这两个图片,但是移动的距离不一样,造成的错位感,代码很简单: import android.content.Context import android.graphics.* import android.util.AttributeSet import android.view.MotionEvent import android.vi

  • Android自定义View仿QQ健康界面

    最近一直在学习自定义View相关的知识,今天给大家带来的是QQ健康界面的实现.先看效果图: 可以设置数字颜色,字体颜色,运动步数,运动排名,运动平均步数,虚线下方的蓝色指示条的长度会随着平均步数改变而进行变化.整体效果还是和QQ运动健康界面很像的. 自定义View四部曲,一起来看看怎么实现的. 1.自定义view的属性: <?xml version="1.0" encoding="utf-8"?> <resources> //自定义属性名,定

  • Android自定义View仿QQ运动步数效果

    本文实例为大家分享了Android QQ运动步数的具体代码,供大家参考,具体内容如下 今天我们实现下面这样的效果: 首先自定义属性: <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="MyQQStep"> <attr name="out_color" format="colo

  • Android自定义View实现QQ运动积分转盘抽奖功能

    因为偶尔关注QQ运动, 看到QQ运动的积分抽奖界面比较有意思,所以就尝试用自定义View实现了下,原本想通过开发者选项查看下界面的一些信息,后来发现积分抽奖界面是在WebView中展示的,应该是在H5页面中用js代码实现的,暂时不去管它了. 这里的自定义View针对的是继承自View的情况,你可以将Canvas想象为画板, Paint为画笔,自定义View的过程和在画板上用画笔作画其实类似,想象在画板上作画的过程,你要画一个多大图形(对应View的测量 onMeasure方法),你要画什么样的图

  • Android自定义View仿QQ等级天数进度

    最近一直都在看自定义View这一块.差不多一个星期了吧.这个星期坚持每天更新博客,感觉自己的技术也有点突破,对自定义View的计算也有了更深的认识. 今天看到手机一个成长天数进度的控件,觉得挺有意思的,于是想自己也写一个.效果如下: 由图可以知道,这里面有很多个元素,首先是背景的矩形区域,其次就是两个环形,然后三个Text文本.其实不复杂,我们一点一点的去实现. 首先呢,画矩形背景.这里用到一个RectF的类,这个类包含一个矩形的四个单精度浮点坐标.矩形通过上下左右4个边的坐标来表示一个矩形.这

  • Android自定义View实现QQ音乐中圆形旋转碟子

    QQ音乐中圆形旋转碟子 思路分析: 1.在onMeasure中测量整个View的宽和高后,设置宽高 2.获取我们res的图片资源后,在ondraw方法中进行绘制圆形图片 3.通过Handler发送Runnable来启动旋转线程(如果只想做圆形头像的话,这步可以去掉) 4.在布局中使用我们的View 效果图: 贴出我们的变量信息: //view的宽和高 int mHeight = 0; int mWidth = 0; //圆形图片 Bitmap bitmap = null; //圆形图片的真实半径

  • Android自定义View模仿QQ讨论组头像效果

    首先来看看我们模仿的效果图,相信对于使用过QQ的人来说都不陌生,效果图如下: 在以前的一个项目中,需要实现类似QQ讨论组头像的控件,只是头像数量和布局有一小点不一样:一是最头像数是4个,二是头像数是2个时的布局是横着排的.其实当时GitHub上就有类似的开源控件,只是那个控件在每一次绘制View的时候都会新创建一些Bitmap对象,这肯定是不可取的,而且那个控件头像输入的是Bitmap对象,不满足需求.所以只能自己实现一个了.实现的时候也没有过多的考虑,传入头像Drawable对象,根据数量排列

  • Android自定义View实现气泡动画

    本文实例为大家分享了Android自定义View实现气泡动画的具体代码,供大家参考,具体内容如下 一.前言 最近有需求制作一个水壶的气泡动画,首先在网上查找了一番,找到了一个文章:Android实现气泡动画 测试了一下发现,如果把它作为子视图的话,会出现小球溢出边界的情况.所以简单的修改了一下. 二.代码 1. 随机移动的气泡 Ball类 /** * @author jiang yuhang * @date 2021-04-18 19:57 */ class Ball { // 半径 @kotl

随机推荐