很早之前就看见过这样一个特效

心怡很久,却一直恐于自定义View这座大山。最近在突击自定义View的技能,学习贝塞尔曲线的绘制,前面搞了个很简单的MagicButton,甚是兴奋😄 所以斗胆来试试看实现这个特效。
分析
找了半天终于找到当初看见的这个特效的原博客 –三次贝塞尔曲线练习之弹性的圆
另外在评论中发现竟然有人已经实现了这个自定义View了–自定义View之炫酷的水滴ViewPageIndicator,效果很不错,借鉴之😄
关于最核心的贝塞尔小球动效的绘制,博主进行了很详细的解析及描述,并且提供了一个demo,万分感谢😄
这里简单回顾一下这个小球的绘制过程:
为了控制小球的不同形态,我们这里使用三阶贝塞尔曲线cubicTo来绘制小球。
而小球一共可以分成5个状态来绘制

状态1

状态2

状态3

状态4

状态5
绘制
计算控件宽高
作为一个导航控件,我暂时不考虑宽度设置为warp_content的状态,设置wrap_content一律计算为屏幕的最大宽高.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @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呈现出的大小,也就是效果图中小图标在白色圆环中的大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @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中关于小球的绘制更加面向对象,更加简洁。不过既然是原创,还是要贴出自己的代码,仅供参考😄
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
| 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;
mData[2] = centerX + radius; mData[3] = centerY;
mData[4] = centerX; mData[5] = centerY - radius;
mData[6] = centerX - radius; mData[7] = centerY;
//初始化控制点 mCtrl[0] = mData[0] + mDifference; mCtrl[1] = mData[1];
mCtrl[2] = mData[2]; mCtrl[3] = mData[3] + mDifference;
mCtrl[4] = mData[2]; mCtrl[5] = mData[3] - mDifference;
mCtrl[6] = mData[4] + mDifference; mCtrl[7] = mData[5];
mCtrl[8] = mData[4] - mDifference; mCtrl[9] = mData[5];
mCtrl[10] = mData[6]; mCtrl[11] = mData[7] - mDifference;
mCtrl[12] = mData[6]; mCtrl[13] = mData[7] + mDifference;
mCtrl[14] = mData[0] - mDifference; mCtrl[15] = mData[1]; }
public void setCurrentAndTarget(PointF currentPoint, PointF targetPoint) { this.currentPoint = currentPoint; this.targetPoint = targetPoint; float distance = targetPoint.x - currentPoint.x; moveDistance = distance > 0 ? distance - 2 * stretchDistance : distance + 2 * stretchDistance; }
public void setProgress(float progress) { if ((progress > 0 && progress <= 0.2) || (progress < 0 && progress >= -0.2)) { model1(progress); } else if ((progress > 0.2 && progress <= 0.5) || (progress < -0.2 && progress >= -0.5)) { model2(progress); } else if ((progress > 0.5 && progress <= 0.8) || (progress < -0.5 && progress >= -0.8)) { model3(progress); } else if ((progress > 0.8 && progress <= 0.9) || (progress < -0.8 && progress >= -0.9)) { model4(progress); } else if ((progress > 0.9 && progress < 1) || (progress < -0.9 && progress > -1)) { model5(progress); } // } else if (progress >= 1 || progress <= -1) { // Log.i(TAG,"-------------------------------------------"); //// centerX = targetPoint.x; //// centerY = targetPoint.y; //// initControlPoint(); // } }
public void model1(float progress) { if (progress > 0) mData[2] = centerX + radius + stretchDistance * progress * 5;
if (progress < 0) mData[6] = centerX - radius + stretchDistance * progress * 5;
mCtrl[2] = mData[2]; if (progress > 0) mCtrl[3] = mData[3] + mDifference + cDistance * progress * 5;
mCtrl[4] = mData[2]; if (progress > 0) mCtrl[5] = mData[3] - mDifference - cDistance * progress * 5;
mCtrl[10] = mData[6]; if (progress < 0) mCtrl[11] = mData[7] - mDifference + cDistance * progress * 5;
mCtrl[12] = mData[6]; if (progress < 0) mCtrl[13] = mData[7] + mDifference - cDistance * progress * 5; }
public void model2(float progress) { model1(progress > 0 ? 0.2f : -0.2f);
progress = progress > 0 ? (progress - 0.2f) * (10f / 3) : (progress + 0.2f) * (10f / 3); //初始化数据点 mData[0] = centerX + stretchDistance * progress;
if (progress > 0) mData[2] = centerX + radius + stretchDistance * (1 + progress); else mData[2] = centerX + radius;
mData[4] = centerX + stretchDistance * progress;
if (progress < 0) mData[6] = centerX - radius - stretchDistance + stretchDistance * progress; else mData[6] = centerX - radius;
//初始化控制点 mCtrl[0] = mData[0] + mDifference;
mCtrl[2] = mData[2]; if (progress > 0) mCtrl[3] = mData[3] + mDifference + cDistance; else mCtrl[3] = mData[3] + mDifference - cDistance * progress;
mCtrl[4] = mData[2]; if (progress > 0) mCtrl[5] = mData[3] - mDifference - cDistance; else mCtrl[5] = mData[3] - mDifference + cDistance * progress;
mCtrl[6] = mData[4] + mDifference;
mCtrl[8] = mData[4] - mDifference;
mCtrl[10] = mData[6]; if (progress > 0) mCtrl[11] = mData[7] - mDifference - cDistance * progress; else mCtrl[11] = mData[7] - mDifference - cDistance;
mCtrl[12] = mData[6]; if (progress > 0) mCtrl[13] = mData[7] + mDifference + cDistance * progress; else mCtrl[13] = mData[7] + mDifference + cDistance;
mCtrl[14] = mData[0] - mDifference; }
public void model3(float progress) { model2(progress > 0 ? 0.5f : -0.5f); progress = progress > 0 ? (progress - 0.5f) * (10f / 3) : (progress + 0.5f) * (10f / 3);
//初始化数据点 if (progress > 0) mData[0] = centerX + moveDistance * progress + stretchDistance; else mData[0] = centerX - moveDistance * progress - stretchDistance;
if (progress > 0) mData[2] = centerX + moveDistance * progress + radius + 2 * stretchDistance; else mData[2] = centerX - moveDistance * progress + radius;
if (progress > 0) mData[4] = centerX + moveDistance * progress + stretchDistance; else mData[4] = centerX - moveDistance * progress - stretchDistance;
if (progress > 0) mData[6] = centerX + moveDistance * progress - radius; else mData[6] = centerX - moveDistance * progress - radius - 2 * stretchDistance;
//初始化控制点 mCtrl[0] = mData[0] + mDifference;
mCtrl[2] = mData[2]; mCtrl[3] = mData[3] + mDifference + cDistance;
mCtrl[4] = mData[2]; mCtrl[5] = mData[3] - mDifference - cDistance;
mCtrl[6] = mData[4] + mDifference;
mCtrl[8] = mData[4] - mDifference;
mCtrl[10] = mData[6]; mCtrl[11] = mData[7] - mDifference - cDistance;
mCtrl[12] = mData[6]; mCtrl[13] = mData[7] + mDifference + cDistance;
mCtrl[14] = mData[0] - mDifference; }
public void model4(float progress) {
model3(progress > 0 ? 0.8f : -0.8f);
progress = progress > 0 ? (progress - 0.8f) * 10 : (progress + 0.8f) * 10;
//初始化数据点 if (progress > 0) mData[0] = centerX + moveDistance + stretchDistance + stretchDistance * progress; else mData[0] = centerX + moveDistance - stretchDistance + stretchDistance * progress;
if (progress > 0) mData[2] = centerX + moveDistance + radius + 2 * stretchDistance; else mData[2] = centerX + moveDistance + radius + stretchDistance * progress;
if (progress > 0) mData[4] = centerX + moveDistance + stretchDistance + stretchDistance * progress; else mData[4] = centerX + moveDistance - stretchDistance + stretchDistance * progress;
if (progress > 0) mData[6] = centerX + moveDistance - radius + stretchDistance * progress; else mData[6] = centerX + moveDistance - radius - 2 * stretchDistance;
//初始化控制点 mCtrl[0] = mData[0] + mDifference;
mCtrl[2] = mData[2]; if (progress > 0) mCtrl[3] = mData[3] + mDifference + cDistance - cDistance * progress; else mCtrl[3] = mData[3] + mDifference + cDistance;
mCtrl[4] = mData[2]; if (progress > 0) mCtrl[5] = mData[3] - mDifference - cDistance + cDistance * progress; else mCtrl[5] = mData[3] - mDifference - cDistance;
mCtrl[6] = mData[4] + mDifference;
mCtrl[8] = mData[4] - mDifference;
mCtrl[10] = mData[6]; if (progress > 0) mCtrl[11] = mData[7] - mDifference - cDistance; else mCtrl[11] = mData[7] - mDifference - cDistance - cDistance * progress;
mCtrl[12] = mData[6]; if (progress > 0) mCtrl[13] = mData[7] + mDifference + cDistance; else mCtrl[13] = mData[7] + mDifference + cDistance + cDistance * progress;
mCtrl[14] = mData[0] - mDifference; }
public void model5(float progress) { model4(progress > 0 ? 0.9f : -0.9f);
progress = progress > 0 ? (progress - 0.9f) * 10 : (progress + 0.9f) * 10;
//初始化数据点 if (progress > 0) mData[0] = centerX + moveDistance + 2 * stretchDistance; else mData[0] = centerX + moveDistance - 2 * stretchDistance;
if (progress > 0) mData[2] = centerX + moveDistance + radius + 2 * stretchDistance; else mData[2] = (float) (centerX + moveDistance + radius - stretchDistance - (Math.sin(Math.PI * 3 / 2 * Math.abs(progress) - Math.PI / 2) + 1) * stretchDistance);
if (progress > 0) mData[4] = centerX + moveDistance + 2 * stretchDistance; else mData[4] = centerX + moveDistance - 2 * stretchDistance;
if (progress > 0) mData[6] = (float) (centerX + moveDistance - radius + stretchDistance + (Math.sin(Math.PI * 3 / 2 * progress - Math.PI / 2) + 1) * stretchDistance); else mData[6] = centerX + moveDistance - radius - 2 * stretchDistance;
//初始化控制点 mCtrl[0] = mData[0] + mDifference;
mCtrl[2] = mData[2]; if (progress < 0) mCtrl[3] = mData[3] + mDifference + cDistance + cDistance * progress;
mCtrl[4] = mData[2]; if (progress < 0) mCtrl[5] = mData[3] - mDifference - cDistance - cDistance * progress;
mCtrl[6] = mData[4] + mDifference;
mCtrl[8] = mData[4] - mDifference;
mCtrl[10] = mData[6]; if (progress > 0) mCtrl[11] = mData[7] - mDifference - cDistance + cDistance * progress;
mCtrl[12] = mData[6]; if (progress > 0) mCtrl[13] = mData[7] + mDifference + cDistance - cDistance * progress;
mCtrl[14] = mData[0] - mDifference; }
public void drawCircle(Canvas canvas, Paint mPaint) { Path path = new Path(); path.moveTo(mData[0], mData[1]);
path.cubicTo(mCtrl[0], mCtrl[1], mCtrl[2], mCtrl[3], mData[2], mData[3]); path.cubicTo(mCtrl[4], mCtrl[5], mCtrl[6], mCtrl[7], mData[4], mData[5]); path.cubicTo(mCtrl[8], mCtrl[9], mCtrl[10], mCtrl[11], mData[6], mData[7]); path.cubicTo(mCtrl[12], mCtrl[13], mCtrl[14], mCtrl[15], mData[0], mData[1]);
canvas.drawPath(path, mPaint); }
public void resetCircular(PointF pointF) { setCenter(pointF.x, pointF.y); initControlPoint(); }
}
|
确定子View点击位置
通过OnTouchEvent 方法计算触摸点在哪个子View的绘制范围内,确定点击位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| float touchX = 0; float touchY = 0;
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: touchX = event.getX(); touchY = event.getY(); break;
case MotionEvent.ACTION_UP: Log.i(TAG, "touchX: " + touchX + " touchY: " + touchY); for (int i = 0; i < anchorList.size(); i++) { PointF pointF = anchorList.get(i); if (touchX > (pointF.x - childSideLength / 2) && touchX < (pointF.x + childSideLength / 2) && touchY > (pointF.y - childSideLength / 2) && touchY < (pointF.y + childSideLength / 2)) { onClickIndex(i); } } break; } return true; }
private void onClickIndex(int position) { if (!isAnimatorStart && !isViewPagerScoll && position != currentPosition) { targetPosition = position; isAnimatorStart = true; startAnimator(); //开始动画 clickAnimator(); //点击效果 if (viewPager != null) { viewPager.setCurrentItem(position); } // currentPosition = position; Log.i(TAG, "点击了第 " + position + " 项!"); } }
|
点击切换动画
通过ValueAnimator动态更改贝塞尔小球的绘制进度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| /** * 切换动画 */ private void startAnimator() { bezierCircular.setCurrentAndTarget(anchorList.get(currentPosition), anchorList.get(targetPosition)); ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, targetPosition > currentPosition ? 1 : -1); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { bezierCircular.setProgress((Float) animation.getAnimatedValue()); bezierPaint.setColor(circularColors.size() > 0 ? setCircularColor(Math.abs((Float) animation.getAnimatedValue())) : circularColor); postInvalidate(); } });
valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { currentPosition = targetPosition; bezierPaint.setColor(circularColors.size() > 0 ? circularColors.get(currentPosition) : circularColor); bezierCircular.resetCircular(anchorList.get(currentPosition)); isAnimatorStart = false; postInvalidate(); super.onAnimationEnd(animation); } });
int count = Math.abs(targetPosition - currentPosition); if (count == 0) { return; } int duration = 600; valueAnimator.setDuration(duration); valueAnimator.start(); }
|
与ViewPager的联动这一块挺头疼的,ViewPager滚动过程中设置滑动监听 void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) 回调方法中的 positionOffset 参数,在从左往右滑是0~1逐渐增大,但是最后又会突变到0。而且 void onPageSelected(int position)回调方法并不是在ViewPager滑动结束的时候调用,而是在你的手指离开时调用,有可能ViewPager还在惯性滑动的时候void onPageSelected(int position)方法已经调用了,所以也没办法通过这个回调来确定 currentPositon与targetPosition。
通过观察,ViewPager的滑动监听 void onPageScrollStateChanged(int state)回调方法中有三个状态
- state == 1 表示正在滑动
- state == 2 表示滑动结束
- state == 0 表示什么都没有做
这里的滑动指的是手指在屏幕上的滑动,而当ViewPager惯性滑动结束时 state == 0,所以最后决定在void onPageScrollStateChanged(int state)方法中进行相关处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| public void setViewPager(ViewPager viewPager) { this.viewPager = viewPager;
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { if (anchorList != null && anchorList.size() > 0 && !isAnimatorStart) { isViewPagerScoll = true; updateDrop(position, positionOffset, positionOffsetPixels); } // 页面正在滚动时不断调用 Log.d(TAG, "onPageScrolled————>" + " position:" + position + " positionOffest:" + positionOffset + " positionOffsetPixels:" + positionOffsetPixels); }
@Override public void onPageSelected(int position) { Log.e(TAG, "onPagerSelected————> position:" + position); isSelected = true; }
@Override public void onPageScrollStateChanged(int state) { if (state == 0 && isSelected && !isAnimatorStart) { // Log.e(TAG, "onPageScrollStateChanged————> 设置状态:"); isSelected = false; isViewPagerScoll = false; bezierCircular.setProgress(direction ? 1.0f : -1.0f); currentPosition = targetPosition;
// Log.i(TAG, "currentPosition::::" + currentPosition); bezierPaint.setColor(circularColors.size() > 0 ? circularColors.get(currentPosition) : circularColor); bezierCircular.resetCircular(anchorList.get(currentPosition)); postInvalidate(); } Log.i(TAG, "onPageScrollStateChanged————> state:" + state); } }); }
float lastProgress = 0; float currentProgress = 0;
//滑动ViewPager时更新指示器的动画 private void updateDrop(int position, float positionOffset, int positionOffsetPixels) {
if ((position + positionOffset) - currentPosition > 0) { direction = true; } else if ((position + positionOffset) - currentPosition < 0) { direction = false; }
//防止数组越界 if ((!direction && currentPosition - 1 < 0) || (direction && currentPosition + 1 > getChildCount() - 1)) { return; }
if (direction) targetPosition = currentPosition + 1; else targetPosition = currentPosition - 1;
currentProgress = positionOffset;
// Log.e(TAG, "direction:::" + direction + " currentPosition:::" + currentPosition + " targetPosition:::" + targetPosition); bezierCircular.setCurrentAndTarget(anchorList.get(currentPosition), anchorList.get(targetPosition));
if (currentProgress == 0 && lastProgress > 0.9) { if (lastProgress > 0.9) { currentProgress = 1; } if (lastProgress < 0.1) { currentProgress = 0; } }
bezierCircular.setProgress(direction ? currentProgress : currentProgress - 1); bezierPaint.setColor(circularColors.size() > 0 ? setCircularColor(direction ? currentProgress : 1 - currentProgress) : circularColor); invalidate(); lastProgress = currentProgress; }
|
onDraw(Canvas canvas)
onDraw方法中代码就很少了
1 2 3 4 5 6 7 8
| @Override protected void onDraw(Canvas canvas) {
drawChildBg(canvas); bezierCircular.drawCircle(canvas, bezierPaint); drawClick(canvas); super.onDraw(canvas); }
|
附上子View背景绘制,及点击效果绘制代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| //绘制子View的背景 private void drawChildBg(Canvas canvas) { if (anchorList == null || anchorList.size() == 0) { Log.i(TAG, "锚点位置为空"); return; }
for (int i = 0; i < anchorList.size(); i++) { PointF pointF = anchorList.get(i); canvas.drawCircle(pointF.x, pointF.y, (childSideLength - 4) / 2, childBgPaint); } }
//绘制点击效果 private void drawClick(Canvas canvas) { PointF pointF = anchorList.get(targetPosition);
canvas.drawCircle(pointF.x, pointF.y, clickRadius, clickPaint); }
|
效果
最终效果如下,可能与原概念图有些差距,但也算小有成就吧😄
附上github地址:https://github.com/lichenming0516/BezierIndicator

小结
通过这两次自定义View的学习尝试,让自己对自定义View的绘制流程有了更深刻的了解,一些常见方法onMeasure()、onLayout()、onDraw()以及自定义属性的解析理解的更清晰一点。对于自定义View这座大山应该能算的上爬上半山腰了吧 😄