晨鸣的博客–神奇的水滴效果导航栏-BezierIndicator
很早之前就看见过这样一个特效
心怡很久,却一直恐于自定义View这座大山。最近在突击自定义View的技能,学习贝塞尔曲线的绘制,前面搞了个很简单的MagicButton,甚是兴奋�� 所以斗胆来试试看实现这个特效。
分析
找了半天终于找到当初看见的这个特效的原博客 –三次贝塞尔曲线练习之弹性的圆
另外在评论中发现竟然有人已经实现了这个自定义View了–自定义View之炫酷的水滴ViewPageIndicator,效果很不错,借鉴之��
关于最核心的贝塞尔小球动效的绘制,博主进行了很详细的解析及描述,并且提供了一个demo,万分感谢��
这里简单回顾一下这个小球的绘制过程:
为了控制小球的不同形态,我们这里使用三阶贝塞尔曲线cubicTo
来绘制小球。
而小球一共可以分成5个状态来绘制
状态1
状态2
状态3
状态4
状态5
绘制
计算控件宽高
作为一个导航控件,我暂时不考虑宽度设置为warp_content
的状态,设置wrap_content
一律计算为屏幕的最大宽高.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
WindowManager wm = (WindowManager) getContext()
.getSystemService(Context.WINDOW_SERVICE);
/**
* 获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式
*/
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
width = sizeWidth;
} else {
width = wm.getDefaultDisplay().getWidth();
}
if (heightMode == MeasureSpec.EXACTLY) {
height = sizeHeight;
} else {
height = wm.getDefaultDisplay().getHeight();
}
if (getChildCount() != 0) {
childSideLength = (width - getPaddingRight() - getPaddingLeft()) / getChildCount() > height - getPaddingBottom() - getPaddingTop() ? height - getPaddingBottom() - getPaddingTop() : (width - getPaddingLeft() - getPaddingRight()) / getChildCount();
// //计算出所有的ChildView的宽和高
// measureChildren(widthMeasureSpec, heightMeasureSpec);
bezierCircular = new BezierCircular(childSideLength / 2);
}
setMeasuredDimension(width, height);
}
计算子控件的位置
为了方便管理,子View的大小统一计算为一个正方形区域,设置一个子View的padding值childPadding
,可以通过childPadding
值控制我们添加的子view呈现出的大小,也就是效果图中小图标在白色圆环中的大小。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
if (childCount == 0) {
return;
}
//相邻两个子View中心点的间距
float childDis = (width - getPaddingLeft() - getPaddingRight() - 2 * defaultLeftRightGap - childSideLength) / (childCount - 1);
float cWidth = childSideLength - 2 * childPadding;
float cHeight = cWidth;
anchorList.clear();
//计算子控件的位置,强制将子View控制绘制在均分的几个锚点上
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
PointF anchorPoint = new PointF((childDis * i + defaultLeftRightGap + childSideLength / 2 + getPaddingLeft()), getPaddingTop() + childSideLength / 2);
anchorList.add(anchorPoint);
childView.layout((int) (anchorPoint.x - cWidth / 2), (int) (anchorPoint.y - cHeight / 2), (int) (anchorPoint.x + cWidth / 2), (int) (anchorPoint.y + cHeight / 2));
}
PointF pointF = anchorList.get(0);
bezierCircular.setCenter(pointF.x, pointF.y);
bezierCircular.initControlPoint();
}
绘制贝塞尔小球
将贝塞尔小球的一些参数及计算封装成一个对象BezierCircular
,因为刚开始只是看了原博客的思路就动手了,绘制贝塞尔小球使用了最原始的方法,定义了4个数据点和8个控制点,在进行五个状态的绘制计算的时候太麻烦了,后面看了博客中的Demo,发现自己的计算太原始笨重了,博客中的demo中关于小球的绘制更加面向对象,更加简洁。不过既然是原创,还是要贴出自己的代码,仅供参考��
public class BezierCircular {
private static final String TAG = "BezierCircular";
private static final float C = 0.551915024494f; //常量
//圆中心坐标
float centerX;
float centerY;
//圆半径
float radius;
private PointF currentPoint;
private PointF targetPoint;
private float mDifference;
private float stretchDistance;
private float cDistance;
private float moveDistance;
private float[] mData = new float[8]; //顺时针记录绘制圆形的四个数据点
private float[] mCtrl = new float[16]; //顺时针记录绘制圆形的八个控制点
public BezierCircular(float radius) {
this.radius = radius;
stretchDistance = radius / 3 * 2;
mDifference = radius * C;
cDistance = mDifference * 0.45f;
}
public void setCenter(float centerX, float centerY) {
this.centerX = centerX;
this.centerY = centerY;
}
public void initControlPoint() {
//初始化数据点
mData[0] = centerX;
mData[1] = centerY + radius;