Android UI 篇- 手势月亮动画

一、应用场景

1.1、先上效果图

2020-05-03 01_00_28.gif

一个有创意的亮度动画,通过手势上下滑动控制手机屏幕亮度,动画从太阳(天亮了)变成月亮(夜黑了),非常漂亮屏幕亮度动画!

二、流程分析

2.1、先上渐变图分析:

渐变图1

首先需要画一个圆

需要画出 A,B 两点之间的向圆心弯曲的弧度,和 A,B 本身的圆弧,然后用 PorterDuff.Mode.DST_OUT,去掉和圆重叠的部分,可以得出月亮。那么向圆心弯曲弧度需要用贝塞尔曲线画出,把 A,B 两点作为贝塞尔曲线的定点,并取A,B两点形成的线段的中垂线上的点C 作为控制点,画出月亮的缺角的弧度,如草图下 :

1588491589781.jpg

变量之间的关系:OC ⊥ AB,A,B两点是贝塞尔曲线定点,C点是贝塞尔曲线的控制点。OC = K * AB ,K 是一个经验值目前可以设置为0.43f(那么C点可以被解出来),这样的塞尔曲线的弧度比较美观,拟合圆的弧度。

问题分解为要求出 A,B两点的位置,和 C点的位置,A,B两点一直在变化,A点跑得比较快,B点跑得比较慢,需要模拟出两点的位置(在progress ∈[0,1]的条件下的位置)。A,B两点的运动轨迹范围如草图下:

1588493267013.jpg

A,B 两点同时出发,A->A',B->B'.(逆时针)

A,B 两点可使用 PathMeasure.getPosTan(float distance, float pos[], float tan[]) 函数接口得出两点的位置,distance 代表的是 A 或者 B 点距离起点的距离(这个距离可以通过progress算出),pos[] 代表传入一个非空数组,函数执行完毕后,A或者B 点的坐标会复赋值到 pos[]中。tan[] 代表 A或者B 点的当前的导数,也就当前点切线的斜率(目前本动画无需用上)。分析完毕,代码撸起来。

备注:另外一种思路实现通过两个圆去 DST_OUT,得出第一个圆剩余的部分也是月亮,但是第二个圆的圆心轨迹函数,和半径变化轨迹函数,想要画出漂亮的月亮,两个运动轨迹没有规律可言,也很难被求出,只能适用于画静态的月亮,画不了动态的月亮。

三、代码实现

3.1、画圆形

通过复写 onSizeChanged ,画出圆。

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

// 太阳/月亮 到光晕的间隔是两倍光晕的宽度

int margin = mHaloHeight + mHaloWidth * 2;

* //实际上太阳/月亮 具体宽高

mLayer.set(margin, margin, w - margin, h - margin);

mCirclePath.reset();

//取宽高中 最短的最为太阳的半径

circleR = w > h ? (h - 2 * margin) / 2.0f : (w - 2 * margin) / 2.0f;

//顺时钟画圆,圆的起始位置在右侧中间

mCirclePath.addCircle(mLayer.centerX(), mLayer.centerY(), circleR, Path.Direction.CW);

//把画好圆的 path 添加到 PathMeasure,待会可以被 getPosTan 使用

mMeasure.setPath(mCirclePath, false);

}

3.2、求通过progress求A、B定点

这里画个图先讲解一下 getPosTan 的用法,因为下面开始要求 A,B 定点坐标。如下图,圆的起始位置在右侧中间,通过 getPosTan 接口 传入 0.25 * len,可以得到下图中 B 点的位置,len = mMeasure.getLength();

1588496794943.jpg

A,B定点如何求得,我们要做到通过输入 progress ,求出 distance ,然后通过 distance 求出定点。(distance 就是A/B点距离原点的距离)

首先我们通过上面getPosTan 的用法,知道如何求指定点的坐标,那么A,B两点可以通过progress求出,我们通过下面草图规定一下名词称呼:

1588498848037.jpg

先看 A 点运动轨迹,progress 进度 0->1 变化时。A->A' (第四象限->第一象限),也就是位置从 0.1 -> 0 (0 和 1 重叠),1 -> 0.9. 嗯。。如何用公式把这个映射给表达出来,输入 progress ,输出 distance 位置 。只能通过分段函数去表示,progress 0->0.5,输出distance 0.1 ->0;progress 0.5->1 ,输出distance 1->0.9. 见下图,通过两点公式可以求得:

1588500041091.jpg

/**

* 分段函数

*

* @param progress

* @param point

*/

private void getBeginPoint(float progress, float[] point) {

if (progress <= 0.5) {

//A 定点 在0.5 progress 之前都是在第四

mMeasure.getPosTan(mMeasure.getLength() * (-0.2f * progress + 0.1f), point, null);

} else {

mMeasure.getPosTan(mMeasure.getLength() * (-0.2f * progress + 1.1f), point, null);

}

}

依样画葫芦,我们来就 B 点运动轨迹的坐标,progress 进度 0->1变化时。B->B' (第四象限->第三象限),也就是位置从 0.1 -> 0 (0 和 1 重叠),1 -> 0.3. 通过分段函数去表示,progress 0->0.1 (B点速度较快),输出distance 0.1 ->0;当 progress 0.1->1 ,输出distance 1->0.3. 差不多这样子,通过两点公式可以求得:

1588591051538.jpg

/**

* 分段函数

*

* @param progress

* @param point

*/

private void getSecondPoint(float progress, float[] point) {

if (progress <= 0.1) {

mMeasure.getPosTan(mMeasure.getLength() * (-1.0f * progress + 0.1f), point, null);

} else {

mMeasure.getPosTan(mMeasure.getLength() * (-7.0f / 9.0f * progress + 9.7f / 9f), point, null);

}

}

3.2、求控制点 C

1588606424855.jpg

根据上图分析,控制点C 是AB 线段的中垂线上的一点;

CO = 0.43 *AB ( O为垂足);

O点横坐标,减去OD 就是C点横坐标 OD = cos α * OC ;

OC = 0.43 *AB;

cosα = 1 / Math.sqrt(1 + tanα * tanα)(三角函数);

tanα = 中垂线 l 的斜率 K;

定理:l1垂直l2,那么k1*k2 = -1;那么中垂线的斜率可以通过AB 斜率得出;

所有的获取变量搞定,上面只分析了B在第四象限,A在第一象限的情况。(还有B 在 四,A在 四;B 在四,A在二;B在四,A在三的情况)但是基本思路差不多(实际上是不想画了,号称灵魂画手的我,都画得快扛不住 ahhhhh。。。);

直接上代码:

private float[] getContrlPoint(float[] point1, float[] point2) {

float centerX = mLayer.centerX();

float centerY = mLayer.centerY();

float diffDis = (float) Math.sqrt((point1[0] - point2[0]) * (point1[0] - point2[0]) + (point1[1] - point2[1]) * (point1[1] - point2[1]));

//中垂线函数 y = kx+b 中的 k

float k = (point1[0] - point2[0]) / (point2[1] - point1[1]);

//中垂线函数 y = kx+b 中的 b

float b = (point1[1] + point2[1]) / 2.0f - (point1[0] * point1[0] - point2[0] * point2[0]) / 2.0f / (point2[1] - point1[1]);

float[] point = {0f, 0f};

// cosα 的值

float cosDegrees = (float) (1 / Math.sqrt(1 + k * k));

if (k < 0) {

//magicNum 为0.43

point[0] = (point1[0] + point2[0]) / 2.0f - (cosDegrees * diffDis * magicNum);

} else if (k > 0) {

if (point1[0] > centerX && point1[1] > centerY && point2[0] > centerX) {

point[0] = (point1[0] + point2[0]) / 2.0f - (cosDegrees * diffDis * magicNum);

} else {

point[0] = (point1[0] + point2[0]) / 2.0f + (cosDegrees * diffDis * magicNum);

}

} else {

point[0] = (point1[0] + point2[0]) / 2.0f;

}

point[1] = k * point[0] + b;

return point;

}

3.3、画 AB 向圆心弯曲的贝塞尔曲线

这个就很简单了,两个定点和一个控制点求出来了,就直接画。

//找到第一个定点

getBeginPoint(progress, mBeginPoint);

//找到第二个定点

getSecondPoint(progress, mSecondPoint);

mQuadPath.reset();

mQuadPath.moveTo(mBeginPoint[0], mBeginPoint[1]);

float[] begin = {mBeginPoint[0], mBeginPoint[1]};

//找到拟合圆的贝赛尔曲线控制点

float[] contrlPoint = getContrlPoint(begin, mSecondPoint);

//画贝赛尔曲线

mQuadPath.quadTo(contrlPoint[0], contrlPoint[1], mSecondPoint[0], mSecondPoint[1]);

3.4、画 AB 本身的圆弧

画圆弧函数 public void arcTo(RectF oval, float startAngle, float sweepAngle);现在我们知道 AB两点坐标,能否求出 startAngle 和 sweepAngle ,答案肯定是可以的。还是先上草图:

1588606677198.jpg

上图给出的条件是 A 点在第一象限,B点在第三象限,圆弧的起始位置如草图所示,在圆的右侧中间,即startAngle为0°。那么很明显AB本身圆弧(上方的圆弧)

startAngle=180°-∠BOH

sweepAngle=∠BOH+180°-∠AOH'

∠BOH = Math.toDegrees(Math.asin(BH / circleR))

BH = B 点 Y 坐标 - centerY(圆心 Y 坐标)

∠AOH'= Math.toDegrees(Math.asin(AH'/ circleR))

AH'= centerY(圆心 Y 坐标)- A 点 Y 坐标

所有的获取变量搞定,上面只分析了A 点在第一象限,B点在第三象限的情况。(还有B 在 四,A在 四;B 在四,A在二;B在四,A在三的情况),基本套路一样

下面直接给出代码:

private Pair getAngle(float[] point1, float[] point2) {

float centerX = mLayer.centerX();

float centerY = mLayer.centerY();

float diffY;

float degrees1 = 0;

float degrees2;

float startAngle;

float sweepAngle;

if (point2[0] > centerX && point2[1] > centerY) {

degrees1 = (float) Math.toDegrees(Math.asin((point2[1] - centerY) / circleR));

degrees2 = (float) Math.toDegrees(Math.asin((point1[1] - centerY) / circleR));

startAngle = degrees1;

sweepAngle = degrees2 - degrees1;

} else {

if (point2[0] > centerX) { //一 象限

if (point2[1] < centerY) {

diffY = centerY - point2[1];

degrees1 = (float) Math.toDegrees(Math.asin(diffY / circleR));

}

} else { // 2 3 象限

if (point2[1] < centerY) {

diffY = centerY - point2[1];

degrees1 = 180 - (float) Math.toDegrees(Math.asin(diffY / circleR));

} else {

diffY = point2[1] - centerY;

degrees1 = (float) Math.toDegrees(Math.asin(diffY / circleR)) + 180;

}

}

degrees2 = (float) Math.toDegrees(Math.asin((centerY - point1[1]) / circleR));

startAngle = 360 - degrees1;

sweepAngle = degrees1 - degrees2;

}

return new Pair<>(startAngle, sweepAngle);

}

3.5、画光晕

最后是画光晕,这个就比较简单,直接上代码

//画光晕

canvas.save();

//画布平移到中间,为了等下旋转使用

canvas.translate(mLayer.centerX(), mLayer.centerY());

//计算出当前进度需要画多少个光晕

int count = mNumOfHalo - (int) (progress / mOneOFHaleProgress);

float mHalfHaloWidth = mHaloWidth / 2;

//开始画光晕

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

canvas.drawRoundRect(new RectF(-mHalfHaloWidth,-mLayer.centerY(),mHalfHaloWidth,mHaloHeight - mLayer.centerY()), mHalfHaloWidth, mHalfHaloWidth, mPaint);

canvas.rotate(mOneOFHaleDegrees);

}

canvas.restore();

彩蛋:到这基本就大功告成,虽然动画的细节非常多,但是我们还是把它给画出来了。给出了亮度的动画,这不还得要一个音量调节的动画?左边上下滑动调节亮度,右边上下滑动调节音量。(号称视频播放双动画)好的,这就给你献上,直接拿走不谢!

2020-05-05 18_09_07.gif

下面给出这个两个动画的开源代码,以及使用教程。github传送门。记得给个 Star (点个赞) 哦。

android 上面月亮模式,Android UI 篇- 手势月亮 亮度动画相关推荐

  1. Android五子棋小游戏之UI篇

    最近一直在学习Android自定义View方面的知识,正好看到一个讲解制作五子棋小游戏的案例,遂学习一番,记录下学习过程,帮助那些有需要的人. 首先放上效果图: 下面我将带领大家一步步完成这个五子棋小 ...

  2. android长按加入购物车,《Android APP可能有的东西》之UI篇:加入购物车动画

    很多电商app的加入购物车的动作会要求加上动画效果:飞进购物车,想来也合理,在listview界面时商品快速加入购物车,一直toast用户加入成功好像不太正常,所以添加一个动画,用户自然就懂了,而且也 ...

  3. android 上面月亮模式,玩安卓套路?月亮模式与息屏显示,iPhone13爆料多可信?...

    原标题:玩安卓套路?月亮模式与息屏显示,iPhone13爆料多可信? 相信各位最近有关于iPhone 13或者是iPhone 12s的爆料也是看得比较多了,基本上每天刷新新闻媒体流软件都能够看到号称是 ...

  4. 使用Vitamio打造自己的Android万能播放器(2)—— 手势控制亮度、音量、缩放

    前言 本章继续完善播放相关播放器的核心功能,为后续扩展打好基础. 声明 欢迎转载,但请保留文章原始出处:)  博客园:http://www.cnblogs.com 农民伯伯: http://over1 ...

  5. android自动夜间模式,Android实现日夜间模式的深入理解

    在本篇文章中给出了三种实现日间/夜间模式切换的方案,三种方案综合起来可能导致文章的篇幅过长,请耐心阅读. 1.使用 setTheme的方法让 Activity重新设置主题: 2.设置 Android ...

  6. android自动夜间模式,Android 夜间模式初探

    当下各种阅读类APP(如各种浏览器,某日头条等)都会有夜间模式,也顺应了大家的睡前必须玩一下手机的作息习惯.关于夜间模式的实现,有很多种方法.这篇日志学习一下最简单的实现方式,通过setTheme(i ...

  7. android系统recovery模式,Android系统Recovery模式中文详细说明

    Recovery具体功能: 1.刷系统:新下载好的rom,,直接放sd卡上刷(进nand),,无需windows! 2.像电脑的ghost,,允许用户随意将系统和里面的个人资料备份成一个文件,,并允许 ...

  8. android q 桌面模式,Android Q带来全新桌面模式

    IT之家3月14日消息 谷歌在美国当地时间3月13日(北京时间14日凌晨)正式推送了Android Q的首个Beta版本,"亲儿子"Pixel系列全系手机可以尝鲜体验这最新的系统. ...

  9. android menu夜间模式,Android常用技巧夜间模式开发浅析

    前言 Android的夜间模式主要主用于阅读方面,在QQ,微信读书,新闻阅读类一般会有相应的功能,本文主要介绍整体APP的夜间模式以及webview中夜间模式的实现. 效果图展示 功能实现简介 APP ...

最新文章

  1. 新版vue-cli搭建多页应用2
  2. c语言的256个字符,C语言版 256点FFT算法
  3. AndoridSQLite数据库开发基础教程(9)
  4. ASP.Net MVC如何访问的静态页面
  5. 盒马加速布局,生鲜新零售如何“中场进阶”?
  6. 百炼1001: Exponentiation 解题
  7. charles抓包出现乱码 SSL Proxying not enabled for this host:enable in Proxy Setting,SSL locations...
  8. 太平洋大西洋水流问题如何解决?一文了解图在前端中的应用
  9. 【转】在birt中显示条形码
  10. vmware linux ssh密码,使用 SSH 密钥连接到 Linux VM - Azure Linux Virtual Machines | Azure Docs...
  11. sqlserver 修改表字段长度(好记性不如烂笔头)
  12. 开源重磅分销版微信商城源码首发
  13. 新手小白学JAVA IO流 File 字节流 字符流
  14. 字符串模式匹配——BF算法
  15. 苹果CMS接入GOGO支付实现个人收款回调详细教程(附插件)
  16. 澳大利亚:千万别惹这几种蛇,它们可是人类的噩梦
  17. 2021 阿里巴巴和蚂蚁金服 Java实习生 面试经验贴(重要!!!)
  18. JZ·7.8.2019
  19. 强化学习 (Reinforcement Learning)
  20. java保存cookie在本地_Java保存Cookie

热门文章

  1. 未来教育c语言加载不出来图片,win10系统网页图片加载不出来的六种原因及解决方法[多图]...
  2. 土木结构工程与计算机科学交叉,(081402)结构工程学科硕士研究生培养方案
  3. C语言指针函数和函数指针
  4. 说说TensorFlow实战Google深度学习框架
  5. 给苹果的ipa文件签名
  6. 关于手机浏览器用户群的思考
  7. 【机器人】满级码农入门ROS1机器人编程QA
  8. MATLAB安装步骤详解
  9. 复旦计算机专硕有宿舍,复旦大学只给学硕提供校内宿舍,对不起,专硕学生请自行解决...
  10. mysql 获取某个时间段中每分钟的数据