[关闭]
@act262 2017-06-01T12:57:52.000000Z 字数 3902 阅读 2010

Android Path 圆角分析

Android_Drawable


Java层的Path.java 对应C层的Path.cpp,SkPath.cpp
对应的C代码位置分别在:
android / platform / frameworks / base / core / jni / android / graphics / Path.cpp
android / platform / external / skia / src / core / SkPath.cpp

API 18+开始SkPath.cpp的实现有大变动,在API 18之前设置radius>矩形的宽高时不会变成半圆形效果

Q:为什么在xml文件中定义自定义shape时,设置radius属性大于控件本身的大小时就产生了圆角矩形效果
A:xml文件自定义的shape对应到GradientDrawable,设置圆角的具体实现在Path类中。

如果要实现半边圆角矩形效果,则需要矩形的rx/ry分别为矩形的高的一半即可。

基于API 17的源码分析
Path.java

  1. public void addRoundRect(float left, float top, float right, float bottom, float[] radii,
  2. Direction dir) {
  3. if (radii.length < 8) {
  4. throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values");
  5. }
  6. isSimplePath = false;
  7. native_addRoundRect(mNativePath, left, top, right, bottom, radii, dir.nativeInt);
  8. }
  9. // ① 4个rx相同的情况下 -> Path.addRoundRectXY
  10. private static native void native_addRoundRect(long nPath, float left, float top,
  11. float right, float bottom,
  12. float rx, float ry, int dir);
  13. // ② 4个rx分别设置的情况 -> Path.addRoundRect8
  14. private static native void native_addRoundRect(long nPath, float left, float top,
  15. float right, float bottom,
  16. float[] radii, int dir);

Path.cpp

  1. // ①
  2. static void addRoundRectXY(JNIEnv* env, jobject clazz, jlong objHandle, jfloat left, jfloat top,
  3. jfloat right, jfloat bottom, jfloat rx, jfloat ry, jint dirHandle) {
  4. SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
  5. SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
  6. SkPath::Direction dir = static_cast<SkPath::Direction>(dirHandle);
  7. obj->addRoundRect(rect, rx, ry, dir); // -> SkPath.addRoundRect -> SkPaht.add_corner_arc
  8. }
  9. // ②
  10. static void addRoundRect8(JNIEnv* env, jobject, jlong objHandle, jfloat left, jfloat top,
  11. jfloat right, jfloat bottom, jfloatArray array, jint dirHandle) {
  12. SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
  13. SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
  14. SkPath::Direction dir = static_cast<SkPath::Direction>(dirHandle);
  15. AutoJavaFloatArray afa(env, array, 8);
  16. #ifdef SK_SCALAR_IS_FLOAT
  17. const float* src = afa.ptr();
  18. #else
  19. #error Need to convert float array to SkScalar array before calling the following function.
  20. #endif
  21. obj->addRoundRect(rect, src, dir); // -> SkPath.addRoundRect()
  22. }

SkPath.cpp

  1. // ① 画圆角
  2. static void add_corner_arc(SkPath* path, const SkRect& rect,
  3. SkScalar rx, SkScalar ry, int startAngle,
  4. SkPath::Direction dir, bool forceMoveTo) {
  5. // rx取 指定的rx或者矩形宽度一半的较小的一个
  6. rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
  7. ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
  8. SkRect r;
  9. r.set(-rx, -ry, rx, ry);
  10. switch (startAngle) {
  11. case 0:
  12. r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
  13. break;
  14. case 90:
  15. r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
  16. break;
  17. case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
  18. case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
  19. default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
  20. }
  21. SkScalar start = SkIntToScalar(startAngle);
  22. SkScalar sweep = SkIntToScalar(90);
  23. if (SkPath::kCCW_Direction == dir) {
  24. start += sweep;
  25. sweep = -sweep;
  26. }
  27. path->arcTo(r, start, sweep, forceMoveTo);
  28. }
  29. // ①
  30. void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
  31. Direction dir) {
  32. // abort before we invoke SkAutoPathBoundsUpdate()
  33. if (rect.isEmpty()) {
  34. return;
  35. }
  36. SkAutoPathBoundsUpdate apbu(this, rect);
  37. if (kCW_Direction == dir) {
  38. add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
  39. add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
  40. add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
  41. add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
  42. } else {
  43. add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
  44. add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
  45. add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
  46. add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
  47. }
  48. this->close();
  49. }
  1. // ②
  2. void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry, Direction dir) {
  3. SkScalar w = rect.width();
  4. SkScalar halfW = SkScalarHalf(w); // rect.width * 0.5f
  5. SkScalar h = rect.height();
  6. SkScalar halfH = SkScalarHalf(h);
  7. if (halfW <= 0 || halfH <= 0) {
  8. return;
  9. }
  10. // x方向的宽度大于矩形宽度的一半则跳过
  11. bool skip_hori = rx >= halfW;
  12. bool skip_vert = ry >= halfH;
  13. // 变成椭圆了
  14. if (skip_hori && skip_vert) {
  15. this->addOval(rect, dir);
  16. return;
  17. }
  18. SkAutoPathBoundsUpdate apbu(this, rect);
  19. // 这里就是关键了
  20. // 椭圆宽度==矩形宽度/2
  21. if (skip_hori) {
  22. rx = halfW;
  23. } else if (skip_vert) {
  24. ry = halfH;
  25. }

所以在shape中设定的宽高大于控件的宽高时就会变成圆角矩形了。

以下基于API 25的源码分析

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注