Android自定义View仿QQ消息拖拽气泡实现
来源:互联网 发布:域名访问tomcat项目 编辑:程序博客网 时间:2024/05/14 07:54
Android自定义View仿QQ消息拖拽气泡实现
很多小的效果看上去很酷炫,其实操作起来很简单,就是要注意细节以及分析状态,然后去绘制,这里会实现qq消息拖拽气泡的实现。
- 画图分析
- 源代码
- 效果展示
画图分析
在画图之前,看一下原效果图,分析实现步骤
由图可知,它这里拖拽画的是图片,为了更接近原理以及自定义可扩展,我们将拖拽的99+也当成一个圆。如下图:
小圆是在原始位置,大圆是在当前手指的位置画出来的,我们这里的坐标系是根据平移画布得到的,代码里会标注;
两个圆的圆心分别是o、 p,圆半径分别为rbig、rsmall,两圆心的链接线为op,链接bc经过圆心垂直于op,链接ad垂直于op,设op的中心点为g,设p点的坐标为(x,y),那么贝塞尔曲线agb和dgc就可以很好的画出(如果贝塞尔曲线不了解的可以看我的其它博客)。那么主要就是求a、b、c、d四个点的坐标,由于p点坐标就是手势的坐标可以很好的求出;
解决a、b、c、d四个点的坐标
由图可知
1.∠epo=∠bpf(这不需要我推理把。。。。)那么∠poe=∠pbf;同理oa与y轴方向的夹角也可以求出(我这里没有标出点,您可以在草稿纸上标出,来求一下)
那么
2.sin∠poe=pe/op; cos∠poe=oe/op;
3.pe=0-y(这里都用起始坐标减去末尾坐标,以确定改点在哪个坐标系)oe=0-x
由以上结论可以求出ABCD四点的坐标
A :(0-rsmall*sin,0+rsmall*cos)
B :(x-rbig*sin,y+rbig*cos)
C :(x+rbig*sin,y-rbig*cos)
D :(0+rsmall*sin,0-rsmall*cos)
分析状态
四种状态:正常状态,拖拽状态,超出状态,恢复状态
NORMAL,DRAG,BEYOUND,COMEBACK
这里我们就提下拖拽状态的path:还是要让他成为一个封闭的路径,所以要满足方向闭合(以前博客有分析,我这里画一下,也就是Path.Direction)
如上图箭头所示,所以我在代码中设置了不同位置的坐标点来决定绘制的方向,已达到绘制出来的path是封闭图形
源代码
代码很简单,具体封装接口和回掉都没写,看你的操作了,代码有注释,很好理解,有什么不好的地方可以指出来,我会改正。 注意,如果想在控件之外展示,需要在其父控件写 android:clipChildren=”false”
public class BubbleView extends View { private int mWidth,mHeight,paintextSize=20; //相关坐标 private float[] startSmall=new float[2],endBig=new float[2],startBig=new float[2], endSmall=new float[2],controlXY=new float[2],activeXY=new float[2],lastXY=new float[2]; //相关路径 private Path pathSmallC,pathBigC,pathStick,pathLine,pathText; //画笔 private Paint paintNormal,paintText; //矩阵 private Matrix matrix; //区域的裁定 private Region regionClip,regionBubble; //一些参数 private float radioSmall=10f,radioBig=25f,sin=2,cos=2,len=0,maxLen=200f,varySet=1; private TYPE type=TYPE.NORMAL; private ValueAnimator animatorBack,animatorGone; private ValueAnimator.AnimatorUpdateListener uplistner; //测量 private PathMeasure measure; enum TYPE{ //四种状态:正常状态,拖拽状态,超出状态,恢复状态 NORMAL,DRAG,BEYOUND,COMEBACK } public BubbleView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initPaint(); initValuAnimator(); } /** * 初始化画笔 */ private void initPaint() { paintNormal=new Paint(); paintNormal.setStyle(Paint.Style.FILL); paintNormal.setAntiAlias(true); paintNormal.setColor(Color.RED); paintText=new Paint(); paintText.setTextSize(paintextSize); paintText.setAntiAlias(true); paintText.setColor(Color.WHITE); paintText.setStyle(Paint.Style.FILL); paintText.setTextAlign(Paint.Align.CENTER); matrix=new Matrix(); measure=new PathMeasure(); } /** * 初始化路径 */ private void initPath() { if(type==TYPE.NORMAL){ activeXY[0]=0; activeXY[1]=0; } pathSmallC=new Path(); pathSmallC.addCircle(0,0,radioSmall, Path.Direction.CW); pathLine=new Path(); pathLine.lineTo(activeXY[0],activeXY[1]); if(type==TYPE.COMEBACK){ measure.setPath(pathLine,false); measure.getPosTan(measure.getLength()*varySet,activeXY,null); } pathBigC=new Path(); pathText=new Path(); if(type==TYPE.BEYOUND){ pathBigC.addCircle(activeXY[0],activeXY[1],radioBig*varySet, Path.Direction.CW); pathText.moveTo(activeXY[0]-radioBig,activeXY[1]+4*varySet); pathText.lineTo(activeXY[0]+radioBig,activeXY[1]+4*varySet); paintText.setTextSize(paintextSize*varySet); }else { pathBigC.addCircle(activeXY[0],activeXY[1],radioBig, Path.Direction.CW); pathText.moveTo(activeXY[0]-radioBig,activeXY[1]+4); pathText.lineTo(activeXY[0]+radioBig,activeXY[1]+4); paintText.setTextSize(paintextSize); } regionBubble=new Region(); regionBubble.setPath(pathBigC,regionClip); pathStick=new Path(); controlXY[0]=activeXY[0]/2; controlXY[1]=activeXY[1]/2; sin=(0-activeXY[1])/len; cos=(0-activeXY[0])/len; startSmall[0]=0-radioSmall*sin; startSmall[1]=0+radioSmall*cos; endBig[0]=activeXY[0]-radioBig*sin; endBig[1]=activeXY[1]+radioBig*cos; startBig[0]=activeXY[0]+radioBig*sin; startBig[1]=activeXY[1]-radioBig*cos; endSmall[0]=0+radioSmall*sin; endSmall[1]=0-radioSmall*cos; pathStick.moveTo(startSmall[0],startSmall[1]); pathStick.quadTo(controlXY[0],controlXY[1],endBig[0],endBig[1]); pathStick.lineTo(startBig[0],startBig[1]); pathStick.quadTo(controlXY[0],controlXY[1],endSmall[0],endSmall[1]); pathStick.close(); pathStick.setFillType(Path.FillType.WINDING); } /** * 初始化动画 */ private void initValuAnimator() { uplistner = new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { varySet= (float) valueAnimator.getAnimatedValue(); invalidate(); } }; Animator.AnimatorListener listener=new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { } @Override public void onAnimationEnd(Animator animator) { type=TYPE.NORMAL; varySet=1; } @Override public void onAnimationCancel(Animator animator) { } @Override public void onAnimationRepeat(Animator animator) { } }; animatorBack=ValueAnimator.ofFloat(1.0000f,0.0000f); animatorBack.setInterpolator(new LinearInterpolator()); animatorBack.setRepeatCount(0); animatorBack.setDuration(1000); animatorBack.addUpdateListener(uplistner); animatorBack.addListener(listener); animatorGone=ValueAnimator.ofFloat(1.0000f,0.0000f); animatorGone.setInterpolator(new LinearInterpolator()); animatorGone.setRepeatCount(0); animatorGone.setDuration(1000); animatorGone.addUpdateListener(uplistner); animatorGone.addListener(listener); } @Override public boolean dispatchTouchEvent(MotionEvent event) { getParent().requestDisallowInterceptTouchEvent(true); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: if(type==TYPE.NORMAL) { activeXY[0] = event.getRawX(); activeXY[1] = event.getRawY(); matrix.mapPoints(activeXY); if (regionBubble.contains((int) activeXY[0], (int) activeXY[1])) { type = TYPE.DRAG; lastXY[0]=activeXY[0]; lastXY[1]=activeXY[1]; } } break; case MotionEvent.ACTION_MOVE: activeXY[0]= event.getRawX(); activeXY[1]=event.getRawY(); matrix.mapPoints(activeXY); if(type==TYPE.DRAG){ len= (float) Math.hypot(activeXY[0],activeXY[1]); if(len<=maxLen){ lastXY[0]=activeXY[0]; lastXY[1]=activeXY[1]; }else if(len>maxLen){ type=TYPE.BEYOUND; } } invalidate(); break; case MotionEvent.ACTION_UP: if(type==TYPE.DRAG){ activeXY[0]=lastXY[0]; activeXY[1]=lastXY[1]; //开始动画 type=TYPE.COMEBACK; animatorBack.start(); }else if(type==TYPE.BEYOUND){ //开始动画 animatorGone.start(); } break; default: break; } return true; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth=w; mHeight=h; regionClip=new Region(-mWidth,-mHeight,mWidth,mHeight); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //平移画布 canvas.translate(mWidth/2,mHeight/2); matrix.reset(); if(matrix.isIdentity()){ canvas.getMatrix().invert(matrix); } initPath(); switch (type){ case NORMAL: drawBigC(canvas); break; case DRAG: drawStick(canvas); drawSmallC(canvas); drawBigC(canvas); break; case BEYOUND: drawBigC(canvas); break; case COMEBACK: drawStick(canvas); drawSmallC(canvas); drawBigC(canvas); break; default:break; } drawText(canvas); } /** * 画小圆 * @param canvas */ private void drawSmallC(Canvas canvas) { canvas.drawPath(pathSmallC,paintNormal); } /** * 画拖拽 * @param canvas */ private void drawStick(Canvas canvas) { canvas.drawPath(pathStick,paintNormal); } /** * 画大圆 * @param canvas */ private void drawBigC(Canvas canvas) { canvas.drawPath(pathBigC,paintNormal); } /** * 画文字 * @param canvas */ private void drawText(Canvas canvas){canvas.drawTextOnPath("1",pathText,0,0,paintText);}}
效果展示
还没优化,有什么建议或者看法,可以留言,抠脚来的,不喜勿喷
- Android自定义View仿QQ消息拖拽气泡实现
- 仿QQ消息气泡拖拽效果
- android仿QQ消息列表拖拽气泡效果源码读后感(1)
- android自定义View 之仿QQ消息头像
- Android自定义View之仿QQ侧滑菜单实现
- [Swift]iOS仿QQ消息气泡拉拽动画
- 微信小程序之『仿 QQ 消息气泡拖拽消失』
- 自定义View之仿QQ消息滑动删除
- Java Swing实现的仿QQ气泡消息聊天窗口效果
- Android UI设计: 分享一个仿QQ聊天消息提示可以拖拉气泡
- Android开源BezierView:仿QQ未读消息99+条的红色气泡
- Android开源BezierView:仿QQ未读消息99+条的红色气泡
- Android开源BezierView:仿QQ未读消息99+条的红色气泡
- Android UI设计: 分享一个仿QQ聊天消息提示可以拖拉气泡
- 实现自定义view(2):仿Android QQ多屏幕显示ListView的效果
- android自定义View实现图片上传进度显示(仿手机QQ上传效果)
- android自定义View实现图片上传进度显示(仿手机QQ上传效果)
- android自定义View实现图片上传进度显示(仿手机QQ上传效果)
- 设计模式之单例模式
- 我的第一篇博客
- JAVA 生成随机密码工具
- 前端html第三方登录集合,微信,微博,QQ
- Android学习笔记之Android应用核心Intent
- Android自定义View仿QQ消息拖拽气泡实现
- zuul简介(一)
- c语言中,double精度数
- 有关cache命中率的问题
- 如何用思维导图提高阅读效率?
- css的学习和应用
- diskimage-builder镜像制作工具的安装
- Java进阶(十八)数组(上)-认识Java数组
- 地球坐标转换(度分秒转度),火星坐标,百度坐标转换 c++,js ,c# 算法通用