先来看一张动态图

昨天跟着视频学了如何自定义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 悬浮球

    闲来无事,搞一波悬浮球,此球: 无需权限 主要代码只有一个类,简简单单放进自己的工程 悬浮球可以用来干啥: 打开侧滑界面 打开一排小按钮 打开客服等等 功能: 显示红点(接收到信息等场景) 关闭红点( ...

  6. android自动悬浮窗代码,Android_Android实现桌面悬浮窗、蒙板效果实例代码,现在很多安全类的软件,比如3 - phpStudy...

    Android实现桌面悬浮窗.蒙板效果实例代码 现在很多安全类的软件,比如360手机助手,百度手机助手等等,都有一个悬浮窗,可以飘浮在桌面上,方便用户使用一些常用的操作. 今天这篇文章,就是介绍如何实 ...

  7. android 进度条 代码,Android 进度条使用详解及示例代码

    在这里,总结一下loading进度条的使用简单总结一下. 一.说起进度条,必须说说条形进度条,经常都会使用到嘛,特别是下载文件进度等等,还有像腾讯QQ安装进度条一样,有个进度总给人良好的用户体验. 先 ...

  8. android小球移动代码,Android自定义圆形View实现小球跟随手指移动效果

    本文实例为大家分享了Android实现小球跟随手指移动效果的具体代码,供大家参考,具体内容如下 一. 需求功能 手指在屏幕上滑动,红色的小球始终跟随手指移动. 实现的思路: 1)自定义View,在on ...

  9. android调频收音机代码,android 收音机 FM 驱动 hal层 框架层以及应用层代码

    [实例简介] android 收音机 FM 驱动 hal层 框架层以及应用层代码 方法一 不需要framework部分 1.fm放到 \hardware\rk2x 2.FmRadio 放到 packa ...

最新文章

  1. iOS-仿膜拜贴纸滚动(物理仿真)
  2. 动力节点Java培训告诉你Java线程的多功能用法
  3. 2017.10.9 JVM入门学习
  4. SpringBoot学习之@Configuration注解和@Bean注解
  5. wxWidgets:wxDatePickerCtrl类用法
  6. mpeg4视频中,I帧、p帧、B帧的判定
  7. IOS设计模式第二篇之单例设计模式
  8. 【原创】搭建spark环境二
  9. 吐血推荐泛绿色编程工具: EditPlus 已于 2010-07-14 升级到 v3.12(602)
  10. ADS笔记 | 史密斯圆进行阻抗匹配,并用ADS仿真结果
  11. 粪斗这杆大旗下,注定只是少部分人的盛宴
  12. 关于travis scott的网名_女生时尚好听的qq网名
  13. 2022 lineCTF WEB复现WriteUp
  14. (20201015 Solved)docker-compose创建网络ERROR: Pool overlaps with other one on this address space
  15. LTE学习-信道估计(LS算法)
  16. OpenSSL SAN 证书
  17. Visual Studio2012 编译 gtest 遇到 error C2977: ‘std::tuple‘ : too many template argum...
  18. MQTT协议详解(完整版)
  19. python scapy模块_关于scapy模块
  20. GCC源码分析(十六) — gimple转RTL(pass_expand)(下)

热门文章

  1. mysql建表语句非空约束默认_Navicat mysql 建表字段 默认值 空白、NULL 、empty string的区别...
  2. 中国大学mooc 南京大学慕课 --探索数据的奥秘--代码复现
  3. 机器学习-有监督学习-分类算法:k-近邻(KNN)算法【多分类】
  4. 东华oj-进阶题第87题-挤牛奶
  5. 【LC周赛241】1865. 找出和为指定值的下标对
  6. bigemap地图下载器矢量数据下载
  7. Python 深度学习--学习笔记(十五)
  8. 数据采集---自制上位机界面接收单片机uart数据
  9. “追随者”京东方,为何难有高估值?
  10. php语法难看,PHP语法之令人困惑的strtotime