Android仿qq消息拖拽效果
本文实例为大家分享了android仿qq消息拖拽效果展示的具体代码,供大家参考,具体内容如下
这是一个仿qq消息拖拽效果,view和拖拽实现了分离,textview、button、imageview等都可以实现相应的拖拽效果;在触发的地方调用
messagebubbleview.attach(findviewbyid(r.id.text_view), new messagebubbleview.bubbledisappearlistener() { @override public void dismiss(view view) { toast.maketext(mainactivity.this,"消失了",toast.length_long).show(); } });
就可以了,第一个参数需要传入一个view,第二个参数需要出入bubbledisappearlistener的实现类进行消失监听回调;在attach();方法中也给传入的view设置了触摸监听事件;
/** * 绑定可以拖拽的控件 * * @param view * @param disappearlistener */ public static void attach(view view, bubbledisappearlistener disappearlistener) { if (view == null) { return; } view.setontouchlistener(new bubblemessagetouchlistener(view, view.getcontext(),disappearlistener)); }
bubblemessagetouchlistener类的话是用来处理触摸监听的类,先去看messagebubbleview类,先去实现自定义view的效果,再去处理相应的触摸事件;
public class messagebubbleview extends view { //两个圆的圆心 private pointf mfixactionpoint; private pointf mdragpoint; //拖拽圆的半径 private int mdragradius = 15; //画笔 private paint mpaint; //固定圆的半径 private int mfixactionradius; //固定圆半径的初始值 private int mfixactionradiusmax = 12; //最小值 private int mfixactionradiusmin = 3; private bitmap mdragbitmap; public messagebubbleview(context context) { this(context, null); } public messagebubbleview(context context, attributeset attrs) { this(context, attrs, 0); } public messagebubbleview(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); mdragradius = dip2px(mdragradius); mfixactionradiusmax = dip2px(mfixactionradiusmax); mfixactionradiusmin = dip2px(mfixactionradiusmin); mpaint = new paint(); mpaint.setcolor(color.red); mpaint.setantialias(true); mpaint.setdither(true); } private int dip2px(int dip) { return (int) typedvalue.applydimension(typedvalue.complex_unit_dip, dip, getresources().getdisplaymetrics()); } }
首先是一些参数的定义及画笔的初始化,接下来就要在ondraw()方法中进行绘制,这里会涉及到两个圆的绘制,一个是固定圆,还有一个是拖拽圆,对于拖拽圆来说,确定x,y坐标及圆的半径就可以进行绘制了,相对来说简单些,对于固定圆来说,一开始有一个初始值,半径是随着距离的增大而减小,小到一定程度就消失;
@override protected void ondraw(canvas canvas) { if (mdragpoint == null || mfixactionpoint == null) { return; } //画两个圆 //绘制拖拽圆 canvas.drawcircle(mdragpoint.x, mdragpoint.y, mdragradius, mpaint); //绘制固定圆 有一个初始大小,而且半径是随着距离的增大而减小,小到一定程度就消失 path bezeierpath = getbezeierpath(); if (bezeierpath != null) { canvas.drawcircle(mfixactionpoint.x, mfixactionpoint.y, mfixactionradius, mpaint); //绘制贝塞尔曲线 canvas.drawpath(bezeierpath, mpaint); } if (mdragbitmap != null) { //绘制图片 位置也是手指一动的位置 中心位置才是手指拖动的位置 canvas.drawbitmap(mdragbitmap, mdragpoint.x - mdragbitmap.getwidth() / 2, mdragpoint.y - mdragbitmap.getheight() / 2, null); } }
绘制了拖拽圆和固定圆后,就需要将两个圆连接起来,连接两个圆的路径的绘制就需要使用三阶贝塞尔曲线来实现;
看过去,需要求p0、p1、p2、p3,这几个点的左边,对于c0、c1的坐标,拖拽圆和固定圆的半径都是知道的,可以先求出c0到c1的距离,对于p0、p1、p2、p3坐标可以通过三角函数求得,再利用path路径进行绘制;
/** * 获取贝塞尔的路径 * * @return */ public path getbezeierpath() { //计算两个点的距离 double distance = getdistance(mdragpoint, mfixactionpoint); mfixactionradius = (int) (mfixactionradiusmax - distance / 14); if (mfixactionradius < mfixactionradiusmin) { //超过一定距离不需要绘制贝塞尔曲线和圆 return null; } path path = new path(); //求斜率 float dy = (mdragpoint.y - mfixactionpoint.y); float dx = (mdragpoint.x - mfixactionpoint.x); float tana = dy / dx; //求角a double arctana = math.atan(tana); //p0 float p0x = (float) (mfixactionpoint.x + mfixactionradius * math.sin(arctana)); float p0y = (float) (mfixactionpoint.y - mfixactionradius * math.cos(arctana)); //p1 float p1x = (float) (mdragpoint.x + mdragradius * math.sin(arctana)); float p1y = (float) (mdragpoint.y - mdragradius * math.cos(arctana)); //p2 float p2x = (float) (mdragpoint.x - mdragradius * math.sin(arctana)); float p2y = (float) (mdragpoint.y + mdragradius * math.cos(arctana)); //p3 float p3x = (float) (mfixactionpoint.x - mfixactionradius * math.sin(arctana)); float p3y = (float) (mfixactionpoint.y + mfixactionradius * math.cos(arctana)); //拼装贝塞尔曲线 path.moveto(p0x, p0y); //两个点,第一个是控制点,第二个是p1的位置 pointf controlpoint = getcontrolpoint(); //绘制第一条 path.quadto(controlpoint.x, controlpoint.y, p1x, p1y); //绘制第二条 path.lineto(p2x, p2y); path.quadto(controlpoint.x, controlpoint.y, p3x, p3y); //闭合 path.close(); return path; } public pointf getcontrolpoint() { //控制点选取的为圆心的中心点 pointf controlpoint = new pointf(); controlpoint.x = (mdragpoint.x + mfixactionpoint.x) / 2; controlpoint.y = (mdragpoint.y + mfixactionpoint.y) / 2; return controlpoint; }
接下来就是处理手势触摸了,手势触摸主要是在bubblemessagetouchlistener类中的ontouch()方法中进行处理;
@override public boolean ontouch(view v, motionevent event) { switch (event.getaction()) { case motionevent.action_down: //在windowmanager上面搞一个view, mwindowmanager.addview(mmessagebubbleview, mparams); //初始化贝塞尔view的点 //需要获取屏幕的位置 不是相对于父布局的位置 还需要减掉状态栏的高度 //将页面做为全屏的可以将其拖拽到状态栏上面 //保证固定圆的中心在view的中心 int[] location = new int[2]; mstateview.getlocationonscreen(location); bitmap bitmapbyview = getbitmapbyview(mstateview); mmessagebubbleview.initpoint(location[0] + mstateview.getwidth() / 2, location[1] + mstateview.getheight() / 2 - bubbleutils.getstatusbarheight(mcontext)); //给消息拖拽设置一个bitmap mmessagebubbleview.setdragbitmap(bitmapbyview); //首先将自己隐藏 mstateview.setvisibility(view.invisible); break; case motionevent.action_move: mmessagebubbleview.updatadragpoint(event.getrawx(), event.getrawy()); break; case motionevent.action_up: //拖动如果贝塞尔曲线没有消失就回弹 //拖动如果贝塞尔曲线消失就爆炸 mmessagebubbleview.handleactionup(); break; } return true; }
在按下拖拽的时候,为了能让view能拖拽到手机屏幕上的任意一点,是在该view添加到了windowmanager上,
public bubblemessagetouchlistener(view mstateview, context context,messagebubbleview.bubbledisappearlistener disappearlistener) { this.mstateview = mstateview; this.mcontext = context; this.disappearlistener=disappearlistener; mwindowmanager = (windowmanager) context.getsystemservice(context.window_service); mmessagebubbleview = new messagebubbleview(context); //设置监听 mmessagebubbleview.setmessagebubblelistener(this); mparams = new windowmanager.layoutparams(); //设置背景透明 mparams.format = pixelformat.translucent; mbombframe = new framelayout(mcontext); mbombimageview = new imageview(mcontext); mbombimageview.setlayoutparams(new framelayout.layoutparams(viewgroup.layoutparams.wrap_content, viewgroup.layoutparams.wrap_content)); mbombframe.addview(mbombimageview); }
在按下的时候需要初始化坐标点及设置相应的背景;
/** * 初始化位置 * * @param downx * @param downy */ public void initpoint(float downx, float downy) { mfixactionpoint = new pointf(downx, downy); mdragpoint = new pointf(downx, downy); } /** * @param bitmap */ public void setdragbitmap(bitmap bitmap) { this.mdragbitmap = bitmap; }
对于action_move手势移动来说,只需要去不断更新移动的坐标就可以了;
/** * 更新当前拖拽点的位置 * * @param movex * @param movey */ public void updatadragpoint(float movex, float movey) { mdragpoint.x = movex; mdragpoint.y = movey; //不断绘制 invalidate(); }
对于action_up手势松开的话,处理就要麻烦些,这里需要判断拖拽的距离,如果拖拽的距离在规定的距离内就反弹,如果超过规定的距离就消失,并伴随相应的动画效果;
/** * 处理手指松开 */ public void handleactionup() { if (mfixactionradius > mfixactionradiusmin) { //拖动如果贝塞尔曲线没有消失就回弹 //valueanimator 值变化的动画 从0-->1的变化 valueanimator animator = objectanimator.offloat(1); animator.setduration(250); final pointf start = new pointf(mdragpoint.x, mdragpoint.y); final pointf end = new pointf(mfixactionpoint.x, mfixactionpoint.y); animator.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { float animatedvalue = (float) animation.getanimatedvalue(); // int percent = (int) animatedvalue; pointf pointf = bubbleutils.getpointbypercent(start, end, animatedvalue); //更新当前拖拽点 updatadragpoint(pointf.x, pointf.y); } }); animator.setinterpolator(new overshootinterpolator(5f)); animator.start(); //通知touchlistener移除当前view然后显示静态的view animator.addlistener(new animatorlisteneradapter() { @override public void onanimationend(animator animation) { super.onanimationend(animation); if(mlistener!=null){ mlistener.restore(); } } }); } else { //拖动如果贝塞尔曲线消失就爆炸 if(mlistener!=null){ mlistener.dimiss(mdragpoint); } } }
而在messagebubblelistener接口监听中需要对void restore();和void dimiss(pointf pointf);进行相应的监听处理,在拖拽距离在规定距离内的话就会去回调restore()方法;
@override public void restore() { //把消息的view移除 mwindowmanager.removeview(mmessagebubbleview); //将原来的view显示 mstateview.setvisibility(view.visible); }
如果拖拽的距离大于规定的距离就会去回调void dimiss(pointf pointf);方法:
@override public void dimiss(pointf pointf) { //要去执行爆炸动画 帧动画 //原来的view肯定要移除 mwindowmanager.removeview(mmessagebubbleview); //要在windowmanager添加一个爆炸动画 mwindowmanager.addview(mbombframe, mparams); //设置背景 mbombimageview.setbackgroundresource(r.drawable.anim_bubble_pop); animationdrawable drawable = (animationdrawable) mbombimageview.getbackground(); //设置位置 mbombimageview.setx(pointf.x-drawable.getintrinsicwidth()/2); mbombimageview.sety(pointf.y-drawable.getintrinsicheight()/2); //开启动画 drawable.start(); //执行完毕后要移除掉mbombframe mbombimageview.postdelayed(new runnable() { @override public void run() { //移除 mwindowmanager.removeview(mbombframe); //通知该view消失了 if(disappearlistener!=null){ disappearlistener.dismiss(mmessagebubbleview); } } }, getanimationdrawabletime(drawable)); }
在拖拽消失后的那个消失动画是使用帧动画来实现的;
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="true" > <item android:drawable="@drawable/pop1" android:duration="100"/> <item android:drawable="@drawable/pop2" android:duration="100"/> <item android:drawable="@drawable/pop3" android:duration="100"/> <item android:drawable="@drawable/pop4" android:duration="100"/> <item android:drawable="@drawable/pop5" android:duration="100"/> </animation-list>
这样子效果就差不多ok了。
源码地址:仿qq消息拖拽效果
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
下一篇: Android贝塞尔曲线实现消息拖拽消失
推荐阅读
-
Android自定义View仿QQ运动步数效果
-
Android自定义View实现拖拽效果
-
Android仿QQ复制昵称效果的实现方法
-
Android 之BottomsheetDialogFragment仿抖音评论底部弹出对话框效果(实例代码)
-
Android 之BottomsheetDialogFragment仿抖音评论底部弹出对话框效果(实例代码)
-
Android仿微信键盘切换效果
-
Android仿qq分组管理的第三方库
-
Android仿淘宝切换商品列表布局效果的示例代码
-
android仿iphone主题效果的主菜单
-
Android 仿京东商城底部布局的选择效果(Selector 选择器的实现)