Android*QQ小红点功能

发布时间 - 2026-01-11 01:57:52    点击率:

先给大家展示下效果图:

代码已上传至Github:*QQ小红点,如对您有帮助,欢迎star~感谢

绘制贝塞尔曲线:

主要是当在一定范围内拖拽时算出固定圆和拖拽圆的外切直线以及对应的切点,就可以通过path.quadTo()来绘制二阶贝塞尔曲线了~

整体思路:

1、当小红点静止时,什么都不做,只需要给自定义小红点QQBezierView(extends TextView)添加一个.9文件当背景即可

2、当滑动时,通过getRootView()获得顶根View,然后new一个DragView ( extends View ) 来绘制各种状态时的小红点,并且通过getRootView().addView()的方式把DragView 加进去,这样DragView 就可以实现全屏滑动了

实现过程:

自定义QQBezierView ( extends TextView ) 并复写onTouchEvent来处理各种情况,代码如下:

@Override
public boolean onTouchEvent(MotionEvent event) {
  //获得根View
  View rootView = getRootView();
  //获得触摸位置在全屏所在位置
  float mRawX = event.getRawX();
  float mRawY = event.getRawY();
  switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
      //请求父View不拦截
      getParent().requestDisallowInterceptTouchEvent(true);
      //获得当前View在屏幕上的位置
      int[] cLocation = new int[2];
      getLocationOnScreen(cLocation);
      if (rootView instanceof ViewGroup) {
        //初始化拖拽时显示的View
        dragView = new DragView(getContext());
        //设置固定圆的圆心坐标
        dragView.setStickyPoint(cLocation[0] + mWidth / 2, cLocation[1] + mHeight / 2, mRawX, mRawY);
        //获得缓存的bitmap,滑动时直接通过drawBitmap绘制出来
        setDrawingCacheEnabled(true);
        Bitmap bitmap = getDrawingCache();
        if (bitmap != null) {
          dragView.setCacheBitmap(bitmap);
          //将DragView添加到RootView中,这样就可以全屏滑动了
          ((ViewGroup) rootView).addView(dragView);
          setVisibility(INVISIBLE);
        }
      }
      break;
    case MotionEvent.ACTION_MOVE:
      //请求父View不拦截
      getParent().requestDisallowInterceptTouchEvent(true);
      if (dragView != null) {
        //更新DragView的位置
        dragView.setDragViewLocation(mRawX, mRawY);
      }
      break;
    case MotionEvent.ACTION_UP:
      getParent().requestDisallowInterceptTouchEvent(false);
      if (dragView != null) {
        //手抬起时来判断各种情况
        dragView.setDragUp();
      }
      break;
  }
  return true;
}

上面代码注释已经很详细了,总结一下就是通过内部拦截法来请求父View是否拦截分发事件,并通过event.getRawX()和event.getRawY()来不断更新DragView的位置,那么DragView都做了哪些事呢,接下来就看一下DragView,DragView是QQBezierView 的一个内部View类:

 private int mState;//当前红点的状态
 private static final int STATE_INIT = 0;//默认静止状态
 private static final int STATE_DRAG = 1;//拖拽状态
 private static final int STATE_MOVE = 2;//移动状态
 private static final int STATE_DISMISS = 3;//消失状态

首先声明了小红点的四种状态,静止状态,拖拽状态,移动状态和消失状态。

在QQBezierView 的onTouchEvent的DOWN事件中调用了setStickyPoint()方法:

/**
 * 设置固定圆的圆心和半径
 * @param stickyX 固定圆的X坐标
 * @param stickyY 固定圆的Y坐标
 */
 public void setStickyPoint(float stickyX, float stickyY, float touchX, float touchY) {
   //分别设置固定圆和拖拽圆的坐标
   stickyPointF.set(stickyX, stickyY);
   dragPointF.set(touchX, touchY);
   //通过两个圆点算出圆心距,也是拖拽的距离
   dragDistance = MathUtil.getTwoPointDistance(dragPointF, stickyPointF);
   if (dragDistance <= maxDistance) {
     //如果拖拽距离小于规定最大距离,则固定的圆应该越来越小,这样看着才符合实际
     stickRadius = (int) (defaultRadius - dragDistance / 10) < 10 ? 10 : (int) (defaultRadius - dragDistance / 10);
     mState = STATE_DRAG;
   } else {
     mState = STATE_INIT;
   }
 }

接着,在QQBezierView 的onTouchEvent的MOVE事件中调用了setDragViewLocation()方法:

 /**
 * 设置拖拽的坐标位置
 *
 * @param touchX 拖拽时的X坐标
 * @param touchY 拖拽时的Y坐标
 */
 public void setDragViewLocation(float touchX, float touchY) {
   dragPointF.set(touchX, touchY);
   //随时更改圆心距
   dragDistance = MathUtil.getTwoPointDistance(dragPointF, stickyPointF);
   if (mState == STATE_DRAG) {
    if (isInsideRange()) {
       stickRadius = (int) (defaultRadius - dragDistance / 10) < 10 ? 10 : (int) (defaultRadius - dragDistance / 10);
     } else {
       mState = STATE_MOVE;
       if (onDragListener != null) {
         onDragListener.onMove();
       }
     }
   }
   invalidate();
 }

最后在QQBezierView 的onTouchEvent的UP事件中调用了setDragUp()方法:

public void setDragUp() {
  if (mState == STATE_DRAG && isInsideRange()) {
    //拖拽状态且在范围之内
    startResetAnimator();
   } else if (mState == STATE_MOVE) {
     if (isInsideRange()) {
      //在范围之内 需要RESET
      startResetAnimator();
    } else {
      //在范围之外 消失动画
      mState = STATE_DISMISS;
      startExplodeAnim();
    }
  }
}

最后来看下DragView的onDraw方法,拖拽时的贝塞尔曲线以及拖拽滑动时的状态都是通过onDraw实现的:

@Override
 protected void onDraw(Canvas canvas) {
   if (isInsideRange() && mState == STATE_DRAG) {
     mPaint.setColor(Color.RED);
     //绘制固定的小圆
     canvas.drawCircle(stickyPointF.x, stickyPointF.y, stickRadius, mPaint);
     //首先获得两圆心之间的斜率
     Float linK = MathUtil.getLineSlope(dragPointF, stickyPointF);
     //然后通过两个圆心和半径、斜率来获得外切线的切点
     PointF[] stickyPoints = MathUtil.getIntersectionPoints(stickyPointF, stickRadius, linK);
     dragRadius = (int) Math.min(mWidth, mHeight) / 2;
     PointF[] dragPoints = MathUtil.getIntersectionPoints(dragPointF, dragRadius, linK);
     mPaint.setColor(Color.RED);
     //二阶贝塞尔曲线的控制点取得两圆心的中点
     controlPoint = MathUtil.getMiddlePoint(dragPointF, stickyPointF);
     //绘制贝塞尔曲线
     mPath.reset();
     mPath.moveTo(stickyPoints[0].x, stickyPoints[0].y);
     mPath.quadTo(controlPoint.x, controlPoint.y, dragPoints[0].x, dragPoints[0].y);
     mPath.lineTo(dragPoints[1].x, dragPoints[1].y);
     mPath.quadTo(controlPoint.x, controlPoint.y, stickyPoints[1].x, stickyPoints[1].y);
     mPath.lineTo(stickyPoints[0].x, stickyPoints[0].y);
     canvas.drawPath(mPath, mPaint);
   }
   if (mCacheBitmap != null && mState != STATE_DISMISS) {
     //绘制缓存的Bitmap
     canvas.drawBitmap(mCacheBitmap, dragPointF.x - mWidth / 2,
            dragPointF.y - mHeight / 2, mPaint);
   }
   if (mState == STATE_DISMISS && explodeIndex < explode_res.length) {
     //绘制小红点消失时的爆炸动画
     canvas.drawBitmap(bitmaps[explodeIndex], dragPointF.x - mWidth / 2, dragPointF.y - mHeight / 2, mPaint);
   }
 }

PS:最开始使用的是 Android:clipChildren=”false” 这个属性,如果父View只是一个普通的ViewGroup(如LinearLayout、RelativeLayout等),此时在父View中设置android:clipChildren=”false”后,子View就可以超出自己的范围,在ViewGroup中也可以滑动了,此时也没问题;但是当是RecycleView时,只要ItemView设置了background属性,滑动时的DragView就会显示在background的下面了,如有知其原因的还望不吝赐教~

最后再贴下源码下载地址:Android*QQ小红点

以上所述是小编给大家介绍的Android*QQ小红点功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!


# android 仿 qq 小红点  # 拖拽  # 小红点  # 塞尔  # 就可以  # 全屏  # 自定义  # 小圆  # 小编  # 范围之内  # 事件中  # 自己的  # 的是  # 都是  # 看着  # 就会  # 不吝赐教  # 也没  # 在此  # 下载地址  # 如有 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251811 】 【 AI营销90571


相关推荐: ,怎么在广州志愿者网站注册?  香港服务器如何优化才能显著提升网站加载速度?  在Oracle关闭情况下如何修改spfile的参数  🚀拖拽式CMS建站能否实现高效与个性化并存?  如何制作新型网站程序文件,新型止水鱼鳞网要拆除吗?  Python面向对象测试方法_mock解析【教程】  node.js报错:Cannot find module &#39;ejs&#39;的解决办法  悟空识字如何进行跟读录音_悟空识字开启麦克风权限与录音  如何快速搭建高效简练网站?  韩国服务器如何优化跨境访问实现高效连接?  Laravel Octane如何提升性能_使用Laravel Octane加速你的应用  lovemo网页版地址 lovemo官网手机登录  Laravel如何优化应用性能?(缓存和优化命令)  ChatGPT怎么生成Excel公式_ChatGPT公式生成方法【指南】  如何获取PHP WAP自助建站系统源码?  宙斯浏览器文件分类查看教程 快速筛选视频文档与图片方法  Laravel怎么处理异常_Laravel自定义异常处理与错误页面教程  如何在万网利用已有域名快速建站?  Windows10如何删除恢复分区_Win10 Diskpart命令强制删除分区  开心动漫网站制作软件下载,十分开心动画为何停播?  个人网站制作流程图片大全,个人网站如何注销?  北京网站制作的公司有哪些,北京白云观官方网站?  Laravel 419 page expired怎么解决_Laravel CSRF令牌过期处理  Android中Textview和图片同行显示(文字超出用省略号,图片自动靠右边)  浏览器如何快速切换搜索引擎_在地址栏使用不同搜索引擎【搜索】  Laravel观察者模式如何使用_Laravel Model Observer配置  Claude怎样写约束型提示词_Claude约束提示词写法【教程】  Laravel Facade的原理是什么_深入理解Laravel门面及其工作机制  如何在建站主机中优化服务器配置?  大连企业网站制作公司,大连2025企业社保缴费网上缴费流程?  公司网站制作价格怎么算,公司办个官网需要多少钱?  西安专业网站制作公司有哪些,陕西省建行官方网站?  Laravel如何记录自定义日志?(Log频道配置)  Laravel如何实现本地化和多语言支持?(i18n教程)  WordPress 子目录安装中正确处理脚本路径的完整指南  js实现获取鼠标当前的位置  香港服务器租用每月最低只需15元?  如何在阿里云ECS服务器部署织梦CMS网站?  高端云建站费用究竟需要多少预算?  Laravel如何发送系统通知_Laravel Notifications实现多渠道消息通知  北京网站制作公司哪家好一点,北京租房网站有哪些?  免费的流程图制作网站有哪些,2025年教师初级职称申报网上流程?  JavaScript 输出显示内容(document.write、alert、innerHTML、console.log)  Laravel与Inertia.js怎么结合_使用Laravel和Inertia构建现代单页应用  如何在阿里云高效完成企业建站全流程?  图册素材网站设计制作软件,图册的导出方式有几种?  Claude怎样写结构化提示词_Claude结构化提示词写法【教程】  HTML透明颜色代码在Angular里怎么设置_Angular透明颜色使用指南【详解】  千问怎样用提示词获取健康建议_千问健康类提示词注意事项【指南】  大连 网站制作,大连天途有线官网?