android 悬浮球动画,Android 仿360悬浮球与加速球
先来看一张动态图
昨天跟着视频学了如何自定义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悬浮球与加速球相关推荐
- android悬浮球代码,Android 仿360悬浮球与加速球
先来看一张动态图 昨天跟着视频学了如何自定义View并做成仿360悬浮球与加速球的样式 可以看出来,做成的效果有: 点击按钮后退出Activity,呈现一个圆形的悬浮球,可以随意拖动并会自动依靠到屏幕 ...
- android 仿360浮动,Android仿360悬浮小球自定义view实现示例
Android仿360悬浮小球自定义view实现示例 效果图如下: 实现当前这种类似的效果 和360小球 悬浮桌面差不错类似.这种效果是如何实现的呢.废话不多说 ,直接上代码. 1.新建工程,添加悬浮 ...
- android 全局浮动球,Android仿360悬浮小球自定义view实现
图片.png 图片.png 实现当前这种类似的效果 (360小球 悬浮桌面差不错类似).第一次接触到的童鞋就像我一样懵逼(研究过得童鞋,就知道,实现这种悬浮窗体,是需要添加悬浮窗 设置权限的,我会在下 ...
- 【Android 学习】实现仿360悬浮窗
本篇博客转自郭霖的博客http://blog.csdn.net/guolin_blog/article/details/8689140 360手机卫士我相信大家都知道,好多人手机上都会装这一款软件,那 ...
- android 清理缓存动画,Android仿微信清理内存图表动画(解决surfaceView屏幕闪烁问题)demo实例详解...
最近接了一个项目其中有功能要实现一个清理内存,要求和微信的效果一样.于是想到用surfaceView而不是继承view.下面小编给大家解析下实现思路. surfaceView是为了解决频繁绘制动画产生 ...
- android 自定义园动画,Android 自定View实现仿QQ运动步数圆弧及动画效果
在之前的Android超精准计步器开发-Dylan计步中的首页用到了一个自定义控件,和QQ运动的界面有点类似,还有动画效果,下面就来讲一下这个View是如何绘制的. 1.先看效果图 2.效果图分析 功 ...
- android 吐泡泡动画,android仿摩拜贴纸碰撞|气泡碰撞
转载请注明出处 准备 气泡碰撞最重要的就是边缘检测,气泡的运动涉及到重力,方向,重心,线速度,角速度,,等等一系列因素,想要在android 用view描述现实世界中的气泡实在是难度很大.网上查找资料 ...
- android 文字跳动动画,Android 仿余额宝数字跳动动画效果完整代码
Android 仿余额宝数字跳动动画效果完整代码 发布时间:2020-08-24 06:55:30 来源:脚本之家 阅读:120 作者:曾淘 一:想都不用想的,有图有真相,看着爽了,在看下面源码 二: ...
- android 缩放透明动画,Android之高仿QQ6.6.0侧滑效果(背景动画、透明+沉浸式状态栏、渐变效果)...
根据需求实现类似QQ侧滑效果,之前看到过很多实现方式通过SlidingMenu,但是既然官方推出了自己的专属控件,那么使用DrawerLayout就是不二选择.且看下文. 一.先来看看官方文档解释 D ...
最新文章
- php如何包含html模板,php html模板中怎么使用类似include的功能?
- win7下面安装mongo步骤(开发环境为pycharm)
- 因果推理、正则化上榜:权威专家盘点过去50年最重要的统计学思想
- PXE安装CentOS
- java dump分析工具_java性能分析与常用工具
- selenium3浏览器驱动安装设置方法
- LeetCode 295. 数据流的中位数 Hard难度
- PLSQL9.0下载及配置oracle,PLSQL登录时常见问题解决
- 关于mingw编译Qt时无法编译opengl es2(ANGLE)版本的问题
- 日期getTime()方法以及JavaScript中的示例
- oracle中树形数据,ORACLE树形数据解决方法
- 描述cookie,sessionstroage,localstrage的区别
- linux中查找grep与find命令的使用
- win7计算机自动关机设置在哪里设置方法,win7怎么设置自动关机【详细步骤】
- DSP方案山景AP8224C2芯片可烧录适用USB声卡降噪麦克风
- m4a转换mp3格式怎么弄?
- excel查找在哪里_数据分析,如何学好Excel
- electron-vue起步
- 电子学会-全国青少年编程等级考试真题Scratch一级(2019年3月)
- BUUCTF-刷题记录-8