先来看一张动态图

昨天跟着视频学了如何自定义View并做成仿360悬浮球与加速球的样式

可以看出来,做成的效果有:

点击按钮后退出Activity,呈现一个圆形的悬浮球,可以随意拖动并会自动依靠到屏幕一侧,且拖动时会变成一张图片

当点击悬浮球时,悬浮球隐藏,底部出现一个加速球,双击加速球时,呈现水量逐渐增高且波动幅度较小的效果,单击时波浪上下波动且幅度渐小

点击屏幕不包含底部加速球的部位,加速球会隐藏,悬浮球重新出现

要做出这么一个效果,需要两个自定义View与一个自定义ViewGroup

首先,需要先设计悬浮球View——FloatBall

简单起见,为FloatBall指定一个默认宽度和高度——150像素

然后在onDraw(Canvas canvas)方法中,判断FloatBall是否正在被拖动isDrag,如果是,则绘制一张默认图片bitmap,否则则根据绘图函数绘制圆形与居中文本

/**

* Created by ZY on 2016/8/10.

* 悬浮球

*/

public class FloatBall extends View {

public int width = 150;

public int height = 150;

//默认显示的文本

private String text = "50%";

//是否在拖动

private boolean isDrag;

private Paint ballPaint;

private Paint textPaint;

private Bitmap bitmap;

public FloatBall(Context context) {

super(context);

init();

}

public FloatBall(Context context, AttributeSet attrs) {

super(context, attrs);

init();

}

public FloatBall(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

init();

}

public void init() {

ballPaint = new Paint();

ballPaint.setColor(Color.GRAY);

ballPaint.setAntiAlias(true);

textPaint = new Paint();

textPaint.setTextSize(25);

textPaint.setColor(Color.WHITE);

textPaint.setAntiAlias(true);

textPaint.setFakeBoldText(true);

Bitmap src = BitmapFactory.decodeResource(getResources(), R.drawable.ninja);

//将图片裁剪到指定大小

bitmap = Bitmap.createScaledBitmap(src, width, height, true);

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

setMeasuredDimension(width, height);

}

@Override

protected void onDraw(Canvas canvas) {

if (!isDrag) {

canvas.drawCircle(width / 2, height / 2, width / 2, ballPaint);

float textWidth = textPaint.measureText(text);

Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();

float dy = -(fontMetrics.descent + fontMetrics.ascent) / 2;

canvas.drawText(text, width / 2 - textWidth / 2, height / 2 + dy, textPaint);

} else {

//正在被拖动时则显示指定图片

canvas.drawBitmap(bitmap, 0, 0, null);

}

}

//设置当前移动状态

public void setDragState(boolean isDrag) {

this.isDrag = isDrag;

invalidate();

}

}

因为FloatBall是不存在于Activity中而在屏幕单独显示的,所以需要用WindowManager来添加View并显示

新建一个类,命名为ViewManager,用来总的管理View的显示与删除

私有化构造函数并采用单例模式

private static ViewManager manager;

//私有化构造函数

private ViewManager(Context context) {

this.context = context;

init();

}

//获取ViewManager实例

public static ViewManager getInstance(Context context) {

if (manager == null) {

manager = new ViewManager(context);

}

return manager;

}

ViewManager包含有显示与隐藏悬浮球与加速球的函数

//显示浮动小球

public void showFloatBall() {

if (floatBallParams == null) {

floatBallParams = new LayoutParams();

floatBallParams.width = floatBall.width;

floatBallParams.height = floatBall.height - getStatusHeight();

floatBallParams.gravity = Gravity.TOP | Gravity.LEFT;

floatBallParams.type = LayoutParams.TYPE_TOAST;

floatBallParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCH_MODAL;

floatBallParams.format = PixelFormat.RGBA_8888;

}

windowManager.addView(floatBall, floatBallParams);

}

//显示底部菜单

private void showFloatMenu() {

if (floatMenuParams == null) {

floatMenuParams = new LayoutParams();

floatMenuParams.width = getScreenWidth();

floatMenuParams.height = getScreenHeight() - getStatusHeight();

floatMenuParams.gravity = Gravity.BOTTOM;

floatMenuParams.type = LayoutParams.TYPE_TOAST;

floatMenuParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCH_MODAL;

floatMenuParams.format = PixelFormat.RGBA_8888;

}

windowManager.addView(floatMenu, floatMenuParams);

}

//隐藏底部菜单

public void hideFloatMenu() {

if (floatMenu != null) {

windowManager.removeView(floatMenu);

}

}

将悬浮球置于Service中开启,这样悬浮球就不那么容易被系统去除了

在onCreate()方法中调用showFloatBall()

public class StartFloatBallService extends Service {

public StartFloatBallService() {

}

@Override

public IBinder onBind(Intent intent) {

// TODO: Return the communication channel to the service.

throw new UnsupportedOperationException("Not yet implemented");

}

@Override

public void onCreate() {

ViewManager manager = ViewManager.getInstance(this);

manager.showFloatBall();

super.onCreate();

}

}

此时,只要为MainActivity添加一个按钮,并设定当点击按钮后开启Service,此时即可看到屏幕显示了一个悬浮球

public void startService(View view) {

Intent intent = new Intent(this, StartFloatBallService.class);

startService(intent);

finish();

}

不过此时悬浮球还不支持拖动与点击,还需要为其添加OnTouchListener与OnClickListener

View.OnTouchListener touchListener = new View.OnTouchListener() {

float startX;

float startY;

float tempX;

float tempY;

@Override

public boolean onTouch(View v, MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

startX = event.getRawX();

startY = event.getRawY();

tempX = event.getRawX();

tempY = event.getRawY();

break;

case MotionEvent.ACTION_MOVE:

float x = event.getRawX() - startX;

float y = event.getRawY() - startY;

//计算偏移量,刷新视图

floatBallParams.x += x;

floatBallParams.y += y;

floatBall.setDragState(true);

windowManager.updateViewLayout(floatBall, floatBallParams);

startX = event.getRawX();

startY = event.getRawY();

break;

case MotionEvent.ACTION_UP:

//判断松手时View的横坐标是靠近屏幕哪一侧,将View移动到依靠屏幕

float endX = event.getRawX();

float endY = event.getRawY();

if (endX < getScreenWidth() / 2) {

endX = 0;

} else {

endX = getScreenWidth() - floatBall.width;

}

floatBallParams.x = (int) endX;

floatBall.setDragState(false);

windowManager.updateViewLayout(floatBall, floatBallParams);

//如果初始落点与松手落点的坐标差值超过6个像素,则拦截该点击事件

//否则继续传递,将事件交给OnClickListener函数处理

if (Math.abs(endX - tempX) > 6 && Math.abs(endY - tempY) > 6) {

return true;

}

break;

}

return false;

}

};

OnClickListener clickListener = new OnClickListener() {

@Override

public void onClick(View v) {

windowManager.removeView(floatBall);

showFloatMenu();

floatMenu.startAnimation();

}

};

floatBall.setOnTouchListener(touchListener);

floatBall.setOnClickListener(clickListener);

加速球ProgressBall的设计较为复杂,需要用到贝塞尔曲线来呈现波浪效果,且单击双击的效果也需要分开呈现

同样是让ProgressBall继承于View

进度值的意义在于限制水面最终上升到的高度,即根据目标进度值与最大进度值的比例来决定水面高度

波浪总的起伏次数Count用于在单击加速球时,水面上下波动的次数

//view的宽度

private int width = 200;

//view的高度

private int height = 200;

//最大进度值

private final int maxProgress = 100;

//当前进度值

private int currentProgress = 0;

//目标进度值

private final int targetProgress = 70;

//是否为单击

private boolean isSingleTop;

//设定波浪总的起伏次数

private final int Count = 20;

//当前起伏次数

private int currentCount;

//初始振幅大小

private final int startAmplitude = 15;

//波浪周期性出现的次数

private final int cycleCount = width / (startAmplitude * 4) + 1;

初始化画笔与监听函数

private void init() {

//初始化小球画笔

ballPaint = new Paint();

ballPaint.setAntiAlias(true);

ballPaint.setColor(Color.argb(0xff, 0x3a, 0x8c, 0x6c));

//初始化(波浪)进度条画笔

progressPaint = new Paint();

progressPaint.setAntiAlias(true);

progressPaint.setColor(Color.argb(0xff, 0x4e, 0xc9, 0x63));

progressPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

//初始化文字画笔

textPaint = new Paint();

textPaint.setAntiAlias(true);

textPaint.setColor(Color.WHITE);

textPaint.setTextSize(25);

handler = new Handler();

path = new Path();

bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

bitmapCanvas = new Canvas(bitmap);

//手势监听

//重点在于将单击和双击操作分隔开

SimpleOnGestureListener listener = new SimpleOnGestureListener() {

//双击

@Override

public boolean onDoubleTap(MotionEvent e) {

//当前波浪起伏次数为零,说明“单击效果”没有影响到现在

if (currentCount == 0) {

//当前进度为零或者已达到目标进度值,说明“双击效果”没有影响到现在,此时可以允许进行双击操作

if (currentProgress == 0 || currentProgress == targetProgress) {

currentProgress = 0;

isSingleTop = false;

startDoubleTapAnimation();

}

}

return super.onDoubleTap(e);

}

//单击

@Override

public boolean onSingleTapConfirmed(MotionEvent e) {

//当前进度值等于目标进度值,且当前波动次数为零,则允许进行单击操作

if (currentProgress == targetProgress && currentCount == 0) {

isSingleTop = true;

startSingleTapAnimation();

}

return super.onSingleTapConfirmed(e);

}

};

gestureDetector = new GestureDetector(context, listener);

setOnTouchListener(new OnTouchListener() {

@Override

public boolean onTouch(View v, MotionEvent event) {

return gestureDetector.onTouchEvent(event);

}

});

//接受点击操作

setClickable(true);

}

单击或双击后的渐变效果是利用Handler的postDelayed(Runnable r, long delayMillis)方法来实现的,可以设定一个延时时间去执行Runnable ,然后在Runnable 中再次调用自身

class DoubleTapRunnable implements Runnable {

@Override

public void run() {

if (currentProgress < targetProgress) {

invalidate();

handler.postDelayed(doubleTapRunnable, 50);

currentProgress++;

} else {

handler.removeCallbacks(doubleTapRunnable);

}

}

}

//开启双击动作动画

public void startDoubleTapAnimation() {

handler.postDelayed(doubleTapRunnable, 50);

}

class SingleTapRunnable implements Runnable {

@Override

public void run() {

if (currentCount < Count) {

invalidate();

currentCount++;

handler.postDelayed(singleTapRunnable, 100);

} else {

handler.removeCallbacks(singleTapRunnable);

currentCount = 0;

}

}

}

//开启单击动作动画

public void startSingleTapAnimation() {

handler.postDelayed(singleTapRunnable, 100);

}

onDraw(Canvas canvas)的重点在于根据比例值来计算水面高度

@Override

protected void onDraw(Canvas canvas) {

//绘制圆形

bitmapCanvas.drawCircle(width / 2, height / 2, width / 2, ballPaint);

path.reset();

//高度随当前进度值的变化而变化

float y = (1 - (float) currentProgress / maxProgress) * height;

//属性PorterDuff.Mode.SRC_IN代表了progressPaint只显示与下层层叠的部分,

//所以以下四点虽然连起来是个矩形,可呈现出来的依然是圆形

//右上角

path.moveTo(width, y);

//右下角

path.lineTo(width, height);

//左下角

path.lineTo(0, height);

//左上角

path.lineTo(0, y);

//绘制顶部波浪

if (!isSingleTop) {

//是双击

//根据当前进度大小调整振幅大小,有逐渐减小的趋势

float tempAmplitude = (1 - (float) currentProgress / targetProgress) * startAmplitude;

for (int i = 0; i < cycleCount; i++) {

path.rQuadTo(startAmplitude, tempAmplitude, 2 * startAmplitude, 0);

path.rQuadTo(startAmplitude, -tempAmplitude, 2 * startAmplitude, 0);

}

} else {

//是单击

//根据当前次数调整振幅大小,有逐渐减小的趋势

float tempAmplitude = (1 - (float) currentCount / Count) * startAmplitude;

//因为想要形成波浪上下起伏的效果,所以根据currentCount的奇偶性来变化贝塞尔曲线转折点位置

if (currentCount % 2 == 0) {

for (int i = 0; i < cycleCount; i++) {

path.rQuadTo(startAmplitude, tempAmplitude, 2 * startAmplitude, 0);

path.rQuadTo(startAmplitude, -tempAmplitude, 2 * startAmplitude, 0);

}

} else {

for (int i = 0; i < cycleCount; i++) {

path.rQuadTo(startAmplitude, -tempAmplitude, 2 * startAmplitude, 0);

path.rQuadTo(startAmplitude, tempAmplitude, 2 * startAmplitude, 0);

}

}

}

path.close();

bitmapCanvas.drawPath(path, progressPaint);

String text = (int) (((float) currentProgress / maxProgress) * 100) + "%";

float textWidth = textPaint.measureText(text);

Paint.FontMetrics metrics = textPaint.getFontMetrics();

float baseLine = height / 2 - (metrics.ascent + metrics.descent);

bitmapCanvas.drawText(text, width / 2 - textWidth / 2, baseLine, textPaint);

canvas.drawBitmap(bitmap, 0, 0, null);

}

因为要呈现ProgressBall时不仅仅是其本身,或者还需要背景色或者文本之类的内容,所以可以将其置于ViewGroup中来显示

布局文件

android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/layout"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_alignParentBottom="true"

android:background="#556f7f8f"

android:clickable="true"

android:gravity="center"

android:orientation="vertical">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center"

android:layout_marginTop="10dp"

android:gravity="center"

android:text="叶应是叶\nhttp://blog.csdn.net/new_one_object" />

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_margin="20dp" />

FloatMenu就作为容纳ProgressBall的容器,并为其赋予从下往上滑动显示的动画效果

/**

* Created by ZY on 2016/8/10.

* 底部菜单栏

*/

public class FloatMenu extends LinearLayout {

private LinearLayout layout;

private TranslateAnimation animation;

public FloatMenu(final Context context) {

super(context);

View root = View.inflate(context, R.layout.float_menu, null);

layout = (LinearLayout) root.findViewById(R.id.layout);

animation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0,

Animation.RELATIVE_TO_SELF, 0,

Animation.RELATIVE_TO_SELF, 1.0f,

Animation.RELATIVE_TO_SELF, 0);

animation.setDuration(500);

animation.setFillAfter(true);

layout.setAnimation(animation);

root.setOnTouchListener(new OnTouchListener() {

@Override

public boolean onTouch(View v, MotionEvent event) {

ViewManager manager = ViewManager.getInstance(context);

manager.showFloatBall();

manager.hideFloatMenu();

return false;

}

});

addView(root);

}

public void startAnimation() {

animation.start();

}

}

android 悬浮球动画,Android 仿360悬浮球与加速球相关推荐

  1. android悬浮球代码,Android 仿360悬浮球与加速球

    先来看一张动态图 昨天跟着视频学了如何自定义View并做成仿360悬浮球与加速球的样式 可以看出来,做成的效果有: 点击按钮后退出Activity,呈现一个圆形的悬浮球,可以随意拖动并会自动依靠到屏幕 ...

  2. android 仿360浮动,Android仿360悬浮小球自定义view实现示例

    Android仿360悬浮小球自定义view实现示例 效果图如下: 实现当前这种类似的效果 和360小球 悬浮桌面差不错类似.这种效果是如何实现的呢.废话不多说 ,直接上代码. 1.新建工程,添加悬浮 ...

  3. android 全局浮动球,Android仿360悬浮小球自定义view实现

    图片.png 图片.png 实现当前这种类似的效果 (360小球 悬浮桌面差不错类似).第一次接触到的童鞋就像我一样懵逼(研究过得童鞋,就知道,实现这种悬浮窗体,是需要添加悬浮窗 设置权限的,我会在下 ...

  4. 【Android 学习】实现仿360悬浮窗

    本篇博客转自郭霖的博客http://blog.csdn.net/guolin_blog/article/details/8689140 360手机卫士我相信大家都知道,好多人手机上都会装这一款软件,那 ...

  5. android 清理缓存动画,Android仿微信清理内存图表动画(解决surfaceView屏幕闪烁问题)demo实例详解...

    最近接了一个项目其中有功能要实现一个清理内存,要求和微信的效果一样.于是想到用surfaceView而不是继承view.下面小编给大家解析下实现思路. surfaceView是为了解决频繁绘制动画产生 ...

  6. android 自定义园动画,Android 自定View实现仿QQ运动步数圆弧及动画效果

    在之前的Android超精准计步器开发-Dylan计步中的首页用到了一个自定义控件,和QQ运动的界面有点类似,还有动画效果,下面就来讲一下这个View是如何绘制的. 1.先看效果图 2.效果图分析 功 ...

  7. android 吐泡泡动画,android仿摩拜贴纸碰撞|气泡碰撞

    转载请注明出处 准备 气泡碰撞最重要的就是边缘检测,气泡的运动涉及到重力,方向,重心,线速度,角速度,,等等一系列因素,想要在android 用view描述现实世界中的气泡实在是难度很大.网上查找资料 ...

  8. android 文字跳动动画,Android 仿余额宝数字跳动动画效果完整代码

    Android 仿余额宝数字跳动动画效果完整代码 发布时间:2020-08-24 06:55:30 来源:脚本之家 阅读:120 作者:曾淘 一:想都不用想的,有图有真相,看着爽了,在看下面源码 二: ...

  9. android 缩放透明动画,Android之高仿QQ6.6.0侧滑效果(背景动画、透明+沉浸式状态栏、渐变效果)...

    根据需求实现类似QQ侧滑效果,之前看到过很多实现方式通过SlidingMenu,但是既然官方推出了自己的专属控件,那么使用DrawerLayout就是不二选择.且看下文. 一.先来看看官方文档解释 D ...

最新文章

  1. php如何包含html模板,php html模板中怎么使用类似include的功能?
  2. win7下面安装mongo步骤(开发环境为pycharm)
  3. 因果推理、正则化上榜:权威专家盘点过去50年最重要的统计学思想
  4. PXE安装CentOS
  5. java dump分析工具_java性能分析与常用工具
  6. selenium3浏览器驱动安装设置方法
  7. LeetCode 295. 数据流的中位数 Hard难度
  8. PLSQL9.0下载及配置oracle,PLSQL登录时常见问题解决
  9. 关于mingw编译Qt时无法编译opengl es2(ANGLE)版本的问题
  10. 日期getTime()方法以及JavaScript中的示例
  11. oracle中树形数据,ORACLE树形数据解决方法
  12. 描述cookie,sessionstroage,localstrage的区别
  13. linux中查找grep与find命令的使用
  14. win7计算机自动关机设置在哪里设置方法,win7怎么设置自动关机【详细步骤】
  15. DSP方案山景AP8224C2芯片可烧录适用USB声卡降噪麦克风
  16. m4a转换mp3格式怎么弄?
  17. excel查找在哪里_数据分析,如何学好Excel
  18. electron-vue起步
  19. 电子学会-全国青少年编程等级考试真题Scratch一级(2019年3月)
  20. BUUCTF-刷题记录-8

热门文章

  1. 数据结构与算法分析(第一周)
  2. 卡罗拉 (COROLLA) - 参数配置
  3. 私有云的优缺点_什么是公有云、私有云、混合云 ?各自的优缺点有哪些?
  4. java数据查询_Java中的大量数据查询
  5. uniapp倒计时按钮,点击发送短信触发倒计时
  6. 婚纱摄影APP软件开发详细
  7. 连接ipv6服务器未响应,IPv6过渡技术未响应
  8. 2021最近的新闻大事10条简短 今天的新闻大事10条汇总
  9. 阿里微服务质量保障系列(一):微服务知多少
  10. Python爬虫+selenium——爬取淘宝商品信息和数据分析