Android控件实现水滴效果
作者:LHavoc 发布时间:2021-07-31 20:43:51
看到ios版上QQ刷新效果像水滴,然后自己也想着去实现这样的效果,这篇文章暂时没有介绍下拉刷新的效果,只是单独用一个控件来实现这样的水滴效果。
效果图如下:
一、总体思路
1、画两个圆形,其中一个就是上面的大圆,还有一个是下面的小圆,大圆和小圆不断变小,大圆的位置保持不变,小圆的位置不断向下移动,即圆心不断下移。
2、画两边的曲线,这时候用到贝塞尔曲线去画。
3、用属性动画实现动态的效果。
二、代码实现
1、找出画曲线的几个关键点。
其实我是在第一张图的基础上,再在上面分别画两个圆,就可以得到第二张图了。关键是画出第一张图。
(1)在这里,p1,p2,p3,p4,这4个点分别对应两个圆的两边的点,即p1到p2就是圆的直径。p3和p4同理,那么就很容易确定这四个点的坐标了。
(2)然后c1和c2是分别控制p1到p3、p2到p4的曲线,是贝塞尔曲线的控制点。它们的横坐标对应的是p3,p4的横坐标(相等),纵坐标取两个圆心距离的一半。这样画出这个静态的图片就不难了。
(3)画上下两个圆进去,就会变成第二张图的效果。
2、在构造方法中调用init()初始化一些基本的变量
private void init(Context context, AttributeSet attrs) {
drawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG
| Paint.FILTER_BITMAP_FLAG);
paint = new Paint();
paint.setColor(fillColor);
// 转换为像素单位
bigRadius = dip2px(context, bigRadius);
smallRadius = dip2px(context, smallRadius);
distance = dip2px(context, distance);
}
3、在onDraw()方法中画水滴效果
要注意的是path需要重新new, 贝塞尔曲线的绘制,用到是path.quadTo这方法。具体可以看代码。
@Override
protected void onDraw(Canvas canvas) {
// 必须重新new,不然路径会重复,我之前就是这样
path = new Path();
// 把画布移到中心
canvas.translate(width / 2, height / 2);
// 从canvas层面去除绘制时锯齿
canvas.setDrawFilter(drawFilter);
// 当前的两个圆心的距离
currentDis = distance * fraction;
// 计算当前大圆的半径
float bigRadius = this.bigRadius - currentDis / bigPercent;
float smallRadius = 0;
if (currentDis > 5) {// 距离大于5才改变小圆的半径
smallRadius = this.smallRadius - currentDis / smallPercent;
}
// 大圆两边的两个点坐标
leftX = -bigRadius;// 大圆当前的半径
leftY = rightY = 0;
rightX = bigRadius;// 大圆当前的半径
// 小圆两边的两个点坐标
leftX2 = -smallRadius;// 小圆当前的半径
leftY2 = rightY2 = currentDis;
rightX2 = -leftX2; // 小圆当前的半径
// 两个控制点
controlX1 = -smallRadius;// x坐标取小圆当前的半径大小
controlY1 = currentDis / 2;// y坐标取两个圆距离的一半
controlX2 = smallRadius;// x坐标取小圆当前的半径大小
controlY2 = currentDis / 2;// y坐标取两个圆距离的一半
path.moveTo(leftX, leftY);
path.lineTo(rightX, rightY);
// 用二阶贝塞尔曲线画右边的曲线,参数的第一个点是右边的一个控制点
path.quadTo(controlX2, controlY2, rightX2, rightY2);
path.lineTo(leftX2, leftY2);
// 用二阶贝塞尔曲线画左边边的曲线,参数的第一个点是左边的一个控制点
path.quadTo(controlX1, controlY1, leftX, leftY);
// 画大圆
canvas.drawCircle(0, 0, bigRadius, paint);
// 画小圆
canvas.drawCircle(0, currentDis, smallRadius, paint);
// 画path
canvas.drawPath(path, paint);
}
4、用属性动画,实现动态的效果。
/*** 执行属性动画,实现水滴的效果 */
public void perforAnim() {
ValueAnimator valAnimator = ObjectAnimator.ofFloat(0, 1);
valAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
fraction = (float) animation.getAnimatedValue();
postInvalidate();
}
});
valAnimator.setDuration(duration);
valAnimator.start();
}
5、重写onMeasure()方法,处理wrap_content情况。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/*
* 处理为wrap_content情况,那么它的specMode是AT_MOST模式,在这种模式下它的宽/高
* 等于spectSize,这种情况下view的spectSize是parentSize,而parentSize是
* 父容器目前可以使用大小,就是父容器当前剩余的空间大小, 就相当于使用match_parent一样 的效果,因此我们可以设置一个默认的值
*/
int widthSpectMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpectSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpectMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpectSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpectMode == MeasureSpec.AT_MOST
&& heightSpectMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(width, height);
} else if (widthSpectMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(width, heightSpectSize);
} else if (heightSpectMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpectSize, height);
}
}
6、其它的一些方法实现。
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
width = right - left;
height = bottom - top;
}
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
7、字段的定义
private final int fillColor = 0xff999999;// 填充颜色
private Paint paint;
private int width = 100, height = 300;// 默认宽高
/* 两个圆心的最大距离 /
private int distance = 60;
/* 当前两个圆心的距离 /
private float currentDis = 0;
private float bigRadius = 20;// 大圆半径
private float smallRadius = 10;// 小圆半径
private float controlX1, controlX2, controlY1, controlY2;// 两个控制点的坐标
private float leftX, leftY, rightX, rightY;// 大圆两边的两个点的坐标
private float leftX2, leftY2, rightX2, rightY2; // 小圆两边的两个坐标
DrawFilter drawFilter;
Path path;
/* 由属性动画控制,范围为0-1 */
float fraction = 0;// 比例值
/* 大圆半径变化的比例 /
private final int bigPercent = 8;
/* 小圆半径变化的比例 /
private final int smallPercent = 20;
// 动画的执行时间
private long duration = 3000;
三、总结
一种动画效果,应该先分析它的静态的实现,然后添加动态的效果,这样就比较容易实现它的动画效果了。
来源:https://blog.csdn.net/LHavoc/article/details/54603197


猜你喜欢
- 小米系统自带的长截屏应该很多人都用过,效果不错。当长截屏时listview就会自动滚动,当按下停止截屏时,就会得到一张完整的截屏。该篇就介绍
- Java SSM框架即指Spring+SpringMVC+MyBatis的简称,框架集由Spring、MyBatis两个开源框架整合而成(S
- 先看MVC模式流程图(其实MVC设计模式就是java中的model2。): &nb
- 摘要在我们使用MyBatis的过程中,如果所有实体类和单表CRUD代码都需要手写,那将会是一件相当麻烦的事情。MyBatis官方代码生成器M
- 一、数组(Array)数组具有以下的特点:数组属于线性结构,在内存中是连续存放的。数组的元素类型必须相同。数组可以直接通过下标访问。数组的查
- 微信是现在比较流行的应用了,在各大安卓市场几乎都是名列前茅了。说实话不得不羡慕腾讯庞大的用户群体,只要腾讯敢做,就会有很多人去用。废话不多说
- Intro做项目的时候,页面上有一些敏感信息,需要用“*”隐藏一些比较重要的信息,于是打算写一个通用的方法。Let's do it
- 前言有时候我们在项目中,会用到一些本地 jar 包文件,比如隔壁公司自己打包的;此时无法从maven远程仓库拉取;那么我们可以考虑把 jar
- 本文实例为大家分享了C语言实现贪吃蛇小游戏的具体代码,供大家参考,具体内容如下程序介绍代码#include<stdafx.h>?
- 前面阿粉说了关于 MD5 加密算法,还有 RSA 加密算法的实现,以及他们的前世今生,今天阿粉在来说一下这个关于 DES 加密算法,又是怎么
- 概览Android Studio 4.1 目前已经发布,该版本共修复了2370 个 bug 以及 275 个 issue,主要包含如下新增功
- 在本文中,笔者向大家介绍下Java中一个非常重要也非常有趣的特性,就是自动装箱与拆箱,并从源码中解读自动装箱与拆箱的原理,同时这种特性也留有
- 做Java的面试题时遇到了以下这题,百度了一下Math.round()的修约规则,有的说是四舍五入,有的说是四舍六入,发现和我学分析化学时用
- 在java中,可以根据Class类的对象,知道某个类(接口)的一些属性(成员 ,方法,注释,注解)等。由于最近的工作中用到了这些,其中需要在
- 本文章向大家讲解java中时间的获取和格式化, 一. 获取当前系统时间和日期并格式化输出:import java.util.Dat
- 其实大家都知道多线程传值有三种方式:1:通过构造方法传递数据2:通过变量和方法传递数据3:通过回调函数传递数据那么博主有个非常变态的需求,所
- (一) collection和collections这两者均位于java.util包下,不同的是:collection是一个集合接口,有Li
- 项目里使用了Feign进行远程调用,有时为了问题排查,需要开启请求和响应日志下面简介一下如何开启Feign日志:注:本文基于spring-b
- TaskAwaiter先说一下 TaskAwaiter,TaskAwaiter 表示等待异步任务完成的对象并为结果提供参
- 出处:https://www.cnblogs.com/SunSpring作为初学者配置maven一般网上搜索。然后你就看到各种配置文件片段,