效果演示:
悬浮小球在移动时会换成一张图片,当松开时,会自动停靠在一侧,并且恢复原来的形式。
点击悬浮小球,会从屏幕底部滑入一个菜单栏。
双击加速球,会有水不断注入的动画效果,并且水面逐渐平静下来。
单击加速球,水面会震荡,最后恢复平静。

这个的实现是观看了慕课网视频后,在其基础上做了一些适当的修改,如:在android23以上,如何申请动态权限。因为这个实现需要用到一个危险权限,弹出窗口。有兴趣的可以前往:http://www.imooc.com/learn/693。

整体思路:
可以看下项目结构。在MainActivity开启了一个服务MyFloatService,这个服务获得了FloatViewManager管理类的实例,view包下有悬浮球的view以及加速球的view。而这个管理类则可控制这些view的显示,隐藏,位置。开启服务后,显示了悬浮球,通过对悬浮球进行Touch事件的监听,实现了拖动改变图片,自动停靠在一侧,单击出现菜单栏。在菜单栏的FloatMenuView类中,实现了对自身的Touch监听,双击出现水不断注入效果,单击水面震荡。单击加速球其他的地方则隐藏菜单栏。

源码已经开放在github上:https://github.com/My-Zzw/FloatView360Demo

在这篇文章中,简单提一下使用到的技巧,以及一些主要的思路,一些思想,一些需要注意的地方。

1.权限问题
当点击主界面的开启悬浮窗按钮时,开启MyFloatService服务。当服务创建时,进行判断,如果运行在大于等于6.0系统上,则跳转到应用信任界面,允许该应用所有权限。在这个项目中,需要用到 SYSTEM_ALERT_WINDOW 权限,记得要在manifest中注册。

@Overridepublic void onCreate() {FloatViewManager manager = FloatViewManager.getInstance(this);manager.showFloatCircleView();//弹出一个窗口,需要权限。>=23,需要动态申请。super.onCreate();}

2.在FloatViewManager中,是如何控制悬浮球以及菜单栏显示和隐藏的。
首先已经实例化了悬浮球View和菜单栏的View

 //实例化悬浮球ViewfloatCircleView = new FloatCircleView(context);//实例化菜单ViewfloatMenuView = new FloatMenuView(context);

已经实例化了View。那么View要显示在界面,就需要一些布局参数,比如view的宽和高,位置在哪里?所以需要一个 WindowManager.LayoutParams 对象。这样,要显示的view已经拿到,如何显示也拿到,那么就可以添加到界面显示了。显示到界面上让用户看到,需要实例化一个 WindowManager 的对象,通过其addView方法添加到窗口。例如显示悬浮窗。

//显示菜单栏private void showFloatMenuView() {WindowManager.LayoutParams params2 = new WindowManager.LayoutParams();params2.width = getScreenWidth();params2.height = getScreenHeight() - getStatusHeigt();//高度为全屏。params2.gravity = Gravity.BOTTOM | Gravity.LEFT;params2.x = 0;params2.y = 0;params2.type = WindowManager.LayoutParams.TYPE_PHONE;//布局参数的类型为手机类型。意味着 在所有页面的上面。params2.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;//不和其他应用抢焦点params2.format = PixelFormat.RGBA_8888;//设置透明度windowManager.addView(floatMenuView, params2);//通过manager添加视图到窗口。第一个参数为要添加的view,第二个为view的布局参数}

3.实现悬浮窗在点击移动时变成一张火箭的图片,松开时自动附着在其中一侧。
首先对悬浮窗注册一个事件监听器。类型为触摸事件。

//注册触摸事件监听器
floatCircleView.setOnTouchListener(circleViewOnTouchListener);

接着实现这个事件监听器的触发事件。当用户按下,移动,抬起时。
需要说明的是,当监听到用户在移动这个悬浮球时,调用了悬浮球对象的setDrawState方法通知到这个悬浮球在移动中,那么悬浮球对象就会调用invalidate进行重绘。在onDraw方法去drawBitmap。
当用户松开时,判断最后松开的悬浮窗x坐标如果大于或者小于屏幕的一半,修改悬浮窗的params.x布局参数的x坐标。最后调用updateViewLayout方法,更新悬浮窗。

 //监听的内部类private View.OnTouchListener circleViewOnTouchListener = new View.OnTouchListener() {@Overridepublic boolean onTouch(View view, MotionEvent motionEvent) {switch (motionEvent.getAction()) {case MotionEvent.ACTION_DOWN://当按下的时候,获取相对屏幕密度的xy坐标startX = motionEvent.getRawX();startY = motionEvent.getRawY();startX0 = motionEvent.getRawX();startY0 = motionEvent.getRawY();break;case MotionEvent.ACTION_MOVE://移动的时候,悬浮球跟着移动//获取移动中的坐标float x = motionEvent.getRawX();float y = motionEvent.getRawY();//偏移量float dx = x - startX;float dy = y - startY;//获取布局参数对象。重新设置悬浮球的xy位置params.x += dx;params.y += dy;//移动的过程中,改变样式。通知在移动。floatCircleView.setDrawState(true);//刷新界面。指定用新的 布局参数params 刷新 floatCircleView在界面的显示windowManager.updateViewLayout(floatCircleView,params);//起始位置变化为移动后的位置startX = x;startY = y;break;case MotionEvent.ACTION_UP://当抬起时,悬浮球附着在两旁float endX = motionEvent.getRawX();//获取最后的X坐标//进行判断.当在屏幕中间线右边或者左边的时,往两边靠拢if (endX > getScreenWidth()/2){params.x = getScreenWidth() - floatCircleView.width;} else {params.x = 0;}//当抬起时,通知移动状态停止。并且悬浮球会重新绘制自身样式floatCircleView.setDrawState(false);//刷新界面。刷新的是悬浮球floatCircleView在界面的布局参数。而悬浮球样式的改变在其内部。windowManager.updateViewLayout(floatCircleView,params);//解决可能有的 触摸事件和点击事件 的冲突.//如果移动后的X坐标 大于 起始的X坐标6个单位距离,则认为是触摸事件,要终止点击事件的执行if (Math.abs(endX - startX0) > 6){//取绝对值return true;//返回true的时候,则不会继续往下执行到 onClick 事件。} else {return  false;}default:break;}return false;}};

4.获取状态栏高度。
这里是采用了反射去获取。这样的好处是能获取到程序运行到具体的设备或者模拟器的状态栏高度。

//获取状态栏的高度public  int getStatusHeigt (){//通过反射的方法获取状态栏的高度try {Class<?> c = Class.forName("com.android.internal.R$dimen");//反射.class获取类Object o = c.newInstance();//实例化这个类,得到一个具体的对象Field field = c.getField("status_bar_height");//获取这个类的field(域)。这个域的对象类型是 这个类里面的一个属性int x = (Integer)field.get(o);//再从具体对象的一个属性的值return context.getResources().getDimensionPixelSize(x);//返回。值转换成px} catch (Exception e) {e.printStackTrace();return 0;}}

5.隐藏悬浮球或者菜单栏。
例如隐藏菜单栏,拿到窗口管理对象remove即可。

 //隐藏 菜单栏public void hideFloatMenuView() {windowManager.removeView(floatMenuView);}

6.悬浮球 FloatCircleView 的实现。

通过实际效果可以知道,其实现原理很简单。首先是如何绘制。
绘制:绘制有两种状态。一是不移动状态下的绘制,使用两只画笔,一个画笔绘制圆形,一个画笔绘制文本。另一个是移动状态下的绘制。移动状态下是将一个在drawable下的资源图片进行绘制。

//    绘制方法@Overrideprotected void onDraw(Canvas canvas) {//如果移动中,则显示图片,否则正常显示if (draw){canvas.drawBitmap(bitmap,0,0,null);} else {//绘制圆形canvas.drawCircle(width/2,height/2,width/2,circlePaint);//绘制文本float textWidth = textPaint.measureText(text);//用画笔去测量文本的宽度float x = width/2 - textWidth/2;//确定文本的x坐标Paint.FontMetrics metrics = textPaint.getFontMetrics();//获得画笔绘制下的文本规格
//      确定文本的y坐标.descent ascent,基准线下的高度,基准线下的高度。为什么不是除2,而是除4?为什么是+而不是-?
//      + 是因为文本的初始位置在圆的上方。除4 则可能是因为有默认的行距。以后凡是需要精确的数值,则可以将其都显示出来,再一个个去测试值。float y = height/2 + (metrics.descent - metrics.ascent)/4;canvas.drawText(text,x,y ,textPaint);}}

如何通知到悬浮球什么时候在移动?通过调用下面这个方法。这个方法会改变状态的标识,并且调用invalidate去重新绘制。

//   在移动中,则进行状态的改变public void setDrawState(boolean b) {draw = b;invalidate();//每当状态改变的时候,需要重新执行draw方法,进行重新绘制}

要保证移动时候,改变的图片的大小也要和悬浮球大小一致,则需要

//     初始化移动需要的bitmap。并且缩放到合适大小Bitmap src = BitmapFactory.decodeResource(getResources(), R.drawable.hj);bitmap = Bitmap.createScaledBitmap(src,width,height,true);

7.进度球 ProcessView 的实现。

进度球放置在了菜单栏里面。通过实际效果知道,需要三支画笔。绘制圆形,绘制水,绘制文本。

7.1 双击进度球动画效果的实现:水不断被注入,并且水面越来越平缓

图解是这样的:

一支画笔绘制了圆形,另外一支画笔在圆形上绘制了一个“矩形”,不过这个矩形有点特殊的是上面的边是个曲线,其他三边都是直线。
在绘制这个特殊的矩形,是用画笔通过Path路径去绘制,先用lineTo方法依次绘制三条直线的边,在绘制到这个曲线的时候再用rQuadTo方法绘制曲线,也叫贝塞尔曲线。
那么动画效果:水不断被注入,并且水面越来越平缓。是如何实现的呢?
水不断注入,其实就是这个特殊的矩形不断的重新绘制,绘制。并且每次绘制时,矩形的高都是不断增加的,在时间很短的情况下,感觉水面在不断的升高。
水面越来越平缓,则是每次在重新绘制的时候,贝塞尔曲线的振幅不断的在减小。
需要了解贝塞尔曲线可能才能明白在说什么,所以有不明白的可以去百度下Android贝塞尔曲线。

绘制的矩形是在覆盖在圆形上,那么矩形超出圆形的部分如何隐藏?
我们可以在初始化画笔时,设置绘制矩形的画笔的过度模式,也就是图像混合模式。当矩形画笔绘制的图像和其他图像混合时,设置其模式为重叠。也就是说,只显示重叠的部分。

//下面这句代码则是 设置了progressPaint画笔绘制的图像,只显示重叠部分progressPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

7.2 单击进度球动画效果的实现:水面不断震荡,最后平缓。

图解:

其实还是不断的去重新绘制矩形,不过矩形的高度不需要变化,但是贝塞尔曲线需要变化,每绘制一次贝塞尔曲线,那么在下一次则绘制相反的贝塞尔曲线,再下一次则恢复原来的贝塞尔曲线即可。
并且在每次重新绘制的时候,同样需要将振幅减小。

如何每次去计算振幅的减小 以及 如何判断哪次需要将贝塞尔曲线取反。读者可以查看代码结合注释。这里不详细介绍。

    //在这进行绘制.@Overrideprotected void onDraw(Canvas canvas) {//画圆bitmapCanvas.drawCircle(width/2,height/2,width/2,circlePaint);//绘制水波纹进度。基本思想是绘制一个矩形。不过矩形除了上面的边使用贝塞尔曲线外,其他都是直线。path.reset();//重置path的所有属性//起始点定义。重新规定路径的起始坐标,默认为(0,0)。moveTo()移动画笔。//这里将起始点的x移动到右边,y则是变化的。也就是矩形的右上角的点float y = (1 - (float)current_progress/max_progress) * height;path.moveTo(width,y);//第二个点定义。右下角,绘制直线。也就是右面的边path.lineTo(width,height);//第三个点定义。左下角,绘制直线。也就是下面的边path.lineTo(0,height);//第四个点定义。左上角,绘制直线。也就是左面的边path.lineTo(0,y);//绘制上面的边。也就是贝塞尔曲线。//根据进度球宽度,设定需要的贝塞尔曲线的周期。如:250的宽度,那么7个周期为40的即可。//绘制贝赛尔曲线需要起始点,控制点和结束点。rQuadTo方法只需要两个参数,起始点默认为 未闭合路径的最后一个点。//一个循环意味着一个周期的贝塞尔曲线if (!isSingleTab){//双击//双击效果:贝塞尔曲线逐渐变得平缓,最后成为直线的效果。//其实就是控制了贝赛尔曲线的振幅。也就是参数的控制点的y坐标。使得y坐标逐渐变成0。也就是rQuadTo中的第二个参数//在这里设定了振幅为10的话,也就是控制点的y坐标,随着当前进度不断增加到接近目标进度,那么百分比就会不断增加//这时用1减去他们的百分比,就会不断减小。用这结果去乘振幅。就能使得振幅不断减小.float d = (1 - ((float)current_progress / progress)) * 10;for (int i = 0; i < 7 ; i++){path.rQuadTo(10,d,20,0);path.rQuadTo(10,-d,20,0);}} else {//单击//单击效果:水不断震荡,最后便于平缓。//其实就是贝塞尔曲线每次周期不断取反,也就是高的变低,低的变高.//这里是水波纹波动50次,也就是50个周期.为什么要模2?//因为count是每次减1。如果模2的结果是0,那么为一个周期。再减1,模2的结果不是0.则为另外一个周期,就可以去实现相反的效果。//要使得贝赛尔曲线逐渐变得平缓,和上面写的一样道理。使得振幅不断减小.注意count是每次-1//第一解决贝塞尔曲线不断取反。第二解决贝塞尔曲线振幅也就是控制点的y坐标不断减小。//以后凡是遇到类似这种,需要多个来配合的,那么先解决一个,再解决下一个。float d = (float)count/50 * 10;if (count%2 == 0){for (int i = 0; i < 7 ; i++){path.rQuadTo(20,d,40,0);path.rQuadTo(20,-d,40,0);}} else {for (int i = 0; i < 7 ; i++){path.rQuadTo(20,-d,40,0);path.rQuadTo(20,d,40,0);}}}path.close();//路径闭合bitmapCanvas.drawPath(path,progressPaint);//绘制文本String text = (int)(((float)current_progress/max_progress) * 100) + "%";//获取文本宽度。便于规定绘制文本时候的x坐标float textWidth = textPaint.measureText(text);//获取文本规格。便于规定绘制文本时候的y坐标;Paint.FontMetrics metrics = textPaint.getFontMetrics();float baseLine = height/2 - (metrics.ascent + metrics.descent)/2;//开始绘制文本bitmapCanvas.drawText(text, width/2-textWidth/2,baseLine,textPaint);//利用自定义bitmap的画布绘制完毕后。再通过显示的画布,绘制这自定义的bitmapcanvas.drawBitmap(bitmap,0,0,null);}

7.3 如何不断去重新绘制?
可以采用Timer定时器去不断重绘。但是这里使用的是 Handle.postDelayed 方法。这里面有个小技巧。也就是postDelayed 方法去执行runable的代码时,如果条件没达到,则再次调用自身,如果条件达到则remove。比如双击动画:
被双击的时,先执行一次

 //开启双击动画private void startDoubleTapAnimation() {//利用handler执行一个延迟50毫秒的线程。//每次线程首先将当前进度++,并且判断。//如果当前进度没有达到指定进度,先重新绘制,然后再延迟50毫秒后执行同样的线程//如果达到了,则让当前进度为0,并且关闭。//本质的效果像开了个定时器,每隔50毫秒去执行,当达到一定条件停止执行handler.postDelayed(new DoubleTabRunable(),50);}

接着在执行的方法去做判断,也就是DoubleTabRunable

 //双击时,需要执行的线程。刷新数据,重绘界面。class DoubleTabRunable implements Runnable {@Overridepublic void run() {current_progress++;if (current_progress <= progress){invalidate();//重新绘制,调用onDraw方法。因为current_progress是变化的,所以重新绘制会使得进度条有变化。handler.postDelayed(this,50);//再次调用自己} else {current_progress = 0;handler.removeCallbacks(this);}}}

7.4 进度球的绘制,先绘制在了一个自定义的bitmap中,绘制完毕后,再讲这个bitmap绘制在界面中显示。

 //自己画图。创建一个空的bitmap,并且在这bitmap上传入一个画布才能进行绘制。bitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);bitmapCanvas = new Canvas(bitmap);

8.菜单栏中放置进度球。
FloatMenuView 继承了LinearLayout。所以这个类本质还是一个View。既然是View的话就会有样式。
之前的悬浮球FloatCircleView 和 进度球 ProcessView 都是通过画笔在OnDraw中去绘制样式。
在FloatMenuView ,则是事先将一个写好的xml格式的布局文件写好,通过View.inflate方法找到这个xml格式的布局文件后,再通过addView方法添加这个布局文件到FloatMenuView ,也就是将这个找到的布局文件和FloatMenuView 绑定。这样在其他地方实例化FloatMenuView 后,显示在窗口的话,样子就是xml布局文件的样式。

 View root = View.inflate(getContext(), R.layout.float_menu_view,null);//找到xml文件样式
... ...addView(root);//将xml样式文件添加到这个View。也就是绑定。

那么进度球是如何显示到了菜单栏中?很简单,在菜单栏的xml布局文件中,也就是刚刚说的FloatMenuView 绑定的这个xml布局文件。在里面将其实例化

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#33000000"><LinearLayout
        android:id="@+id/linearLayout"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="#F02F3942"android:layout_alignParentBottom="true"android:clickable="true"><!--左上角文本提示--><LinearLayout
            android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><ImageView
                android:layout_width="50dp"android:layout_height="50dp"android:layout_marginLeft="10dp"android:layout_gravity="center_vertical"android:src="@drawable/four"/><TextView
                android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="22sp"android:textColor="#1296db"android:text="四月加速球"android:layout_marginLeft="10dp"android:layout_gravity="center_vertical"/></LinearLayout><!--实例化自定义的进度球--><view.ProcessView
            android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_margin="10dp"/></LinearLayout></RelativeLayout>

8.1或者说是bug,或者说是技巧吧,在这里用到了。具体原因不是很清楚,以后有待查证。
当点击进度球(包括灰色区域)以外的地方,需要隐藏菜单栏(进度球是菜单栏的子控件)。但是点击事件监听的是整个父控件,也就是菜单栏。而点击进度球是有其自身动画效果需要实现的。
也就是子控件不需要因为父控件而给监听到。
这样解决:给进度球在xml文件或者通过代码的方式设置一个属性:clickable=”true”。
当给整个父控件设置了点击或者触摸的监听,如果不需要其子控件也给监听到(或者子控件的监听有额外的事件处理)时,给子控件设置属性:clickable=”true”。

最后,感谢你耐心的看完。水平有限,不足或者有误之处,请谅解!!!

源码已经开放在github上:https://github.com/My-Zzw/FloatView360Demo

360加速球效果实现相关推荐

  1. html5 加速球 效果,css 渐隐渐现、echarts 圆环图、百度地图覆盖物、echarts水球图(360加速球效果)...

    说一下知识点: 1.水球是echarts的插件echarts-liquidfill,官网下载-扩展下载中可以拿到,git传送阵https://github.com/ecomfe/echarts-liq ...

  2. android代码实现手机加速功能,Android自定义View实现内存清理加速球效果

    Android自定义View实现内存清理加速球效果 发布时间:2020-09-21 22:21:57 来源:脚本之家 阅读:105 作者:程序员的自我反思 前言 用过猎豹清理大师或者相类似的安全软件, ...

  3. android 自定义progressbar demo,Android自定义View――动态ProgressBar之模仿360加速球

    在之前一篇文章中我们讲解了三种ProgressBar的做法,详见-><Android 自定义View--自定义ProgressBar >.这一节中我们模仿360加速球制作一个动态Pr ...

  4. [MFC] WS_EX_LAYERED 实现透明异形窗口(酷狗歌词、360加速球、窗口边缘阴影)

    关键词:WS_EX_LAYERED. UpdateLayeredWindow PC应用不少都有透明的异形窗口  比如以下程序的效果: 酷狗音乐播放器的歌词窗口(窗口除了歌词内容 其他都是透明的) 36 ...

  5. vc++实现悬浮窗,迅雷360悬浮球效果

    1.SetWindowRgn创建圆角悬浮窗DlgFloat rgn.CreateRoundRectRgn(rect.left, rect.top, rect.Width(), rect.Height( ...

  6. 360加速球 android,Android加速球、360加速球

    先看效果图,这个加速球是动态的,并且当调用了myView.setRefresh(0.8F);方法后可以从当前值动态降到0再升到80%,期间可以看到颜色的变化. 源码: package com.ww.v ...

  7. Android自定义View实现炫酷的加速球效果

    效果展示: 原理解析: 首先构建一个圆形路径(中心点为控件中心),然后利用canvas.clipPath方法将画布裁剪为圆形. 利用贝赛尔曲线构造一条如上图所示的波浪线,波浪线分为三段(一起一伏为一段 ...

  8. Android绘图:360加速球

    自定义View 主函数 布局 自定义View public class MyPathView extends View {private Paint mPaint;private Paint mPai ...

  9. 利用canvas制作加速球波浪效果

    我在第一篇博客中提到运用canvas制作加速球效果,现在把整个代码分享给大家,希望对大家有帮助.(转载请标明出处) 先上效果图: 还存在一些问题: 1.想法是鼠标触碰到滑块才执行画波浪的效果,结果是直 ...

  10. C# 学习笔记(10)加速球

    C# 学习笔记(10)加速球 利用窗体透明和GIF透明背景,实现加速球类似效果(QQ宠物,老年人大概也知道) 本文参考C#仿PS异形启动界面设计https://www.bilibili.com/vid ...

最新文章

  1. html5插件教程,HTML5教程 | HTML5 time元素
  2. go语言基础到提高(5)-结构
  3. 【移动端debug-6】如何做一个App里的web调试小工具
  4. leetcode刷题可以用python吗_LeetCode刷题——第四天(python)
  5. SQL Server中常用全局变量介绍
  6. 6大理由告诉你为什么这次区块链大会必须参加
  7. oraccle 索引管理
  8. supersocke接收不到数据_豪横吗?易查分除了上传电子表格,复制粘贴也能上传数据啦!...
  9. iOS7以上: 实现如“日历”的 NavigationBar
  10. Steam游戏上线初期的总结与思考
  11. 文曲星猜数游戏c语言带结果,文曲星猜数游戏 C实现
  12. gc算法 java_Java的GC机制及算法
  13. 阿里云推出“通达云OA”办公系统 基于钉钉的移动OA应用...
  14. 电大计算机网考试题,电大计算机应用基础网考统考试题及答案
  15. PDF板式文档转OFD板式文档
  16. 井柏然自己的字体,手写语录合集
  17. 新松机器人BG总裁高峰_新松机器人总裁曲道奎博士出席高工机器人产业高峰论坛...
  18. WARNING:not built warning
  19. 主成分分析碎石图_用R软件包ade4做主成分分析图(PCA)
  20. 网络变压器 网络变压器基本线路及其设计目的和侧重点

热门文章

  1. 二分、冒泡、快速、插入排序
  2. 亿图图示专家Edraw Max v10.5.2 中文免费版(附安装教程)
  3. linux 服务状态命令,Linux 查看服务列表,查看服务状态
  4. 深度学习-图解反向传播算法
  5. ENVI Landsat8 裁剪 辐射校正 大气校正 CSU
  6. Glide4.0 centerCrop属性和圆角 冲突
  7. 安装eclipse汉化包后无法打开eclipse的解决方法
  8. IR2130与MOSFET驱动电路分析
  9. osx 字体 linux,Linux/MacOS下matplotlib能正常显示的中文字体选择
  10. 2020 年百度之星·程序设计大赛 - 初赛一