@act262
2017-06-01T12:57:52.000000Z
字数 3902
阅读 2162
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
public void addRoundRect(float left, float top, float right, float bottom, float[] radii,
Direction dir) {
if (radii.length < 8) {
throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values");
}
isSimplePath = false;
native_addRoundRect(mNativePath, left, top, right, bottom, radii, dir.nativeInt);
}
// ① 4个rx相同的情况下 -> Path.addRoundRectXY
private static native void native_addRoundRect(long nPath, float left, float top,
float right, float bottom,
float rx, float ry, int dir);
// ② 4个rx分别设置的情况 -> Path.addRoundRect8
private static native void native_addRoundRect(long nPath, float left, float top,
float right, float bottom,
float[] radii, int dir);
Path.cpp
// ①
static void addRoundRectXY(JNIEnv* env, jobject clazz, jlong objHandle, jfloat left, jfloat top,
jfloat right, jfloat bottom, jfloat rx, jfloat ry, jint dirHandle) {
SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
SkPath::Direction dir = static_cast<SkPath::Direction>(dirHandle);
obj->addRoundRect(rect, rx, ry, dir); // -> SkPath.addRoundRect -> SkPaht.add_corner_arc
}
// ②
static void addRoundRect8(JNIEnv* env, jobject, jlong objHandle, jfloat left, jfloat top,
jfloat right, jfloat bottom, jfloatArray array, jint dirHandle) {
SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
SkPath::Direction dir = static_cast<SkPath::Direction>(dirHandle);
AutoJavaFloatArray afa(env, array, 8);
#ifdef SK_SCALAR_IS_FLOAT
const float* src = afa.ptr();
#else
#error Need to convert float array to SkScalar array before calling the following function.
#endif
obj->addRoundRect(rect, src, dir); // -> SkPath.addRoundRect()
}
SkPath.cpp
// ① 画圆角
static void add_corner_arc(SkPath* path, const SkRect& rect,
SkScalar rx, SkScalar ry, int startAngle,
SkPath::Direction dir, bool forceMoveTo) {
// rx取 指定的rx或者矩形宽度一半的较小的一个
rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
SkRect r;
r.set(-rx, -ry, rx, ry);
switch (startAngle) {
case 0:
r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
break;
case 90:
r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
break;
case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
}
SkScalar start = SkIntToScalar(startAngle);
SkScalar sweep = SkIntToScalar(90);
if (SkPath::kCCW_Direction == dir) {
start += sweep;
sweep = -sweep;
}
path->arcTo(r, start, sweep, forceMoveTo);
}
// ①
void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
Direction dir) {
// abort before we invoke SkAutoPathBoundsUpdate()
if (rect.isEmpty()) {
return;
}
SkAutoPathBoundsUpdate apbu(this, rect);
if (kCW_Direction == dir) {
add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
} else {
add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
}
this->close();
}
// ②
void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry, Direction dir) {
SkScalar w = rect.width();
SkScalar halfW = SkScalarHalf(w); // rect.width * 0.5f
SkScalar h = rect.height();
SkScalar halfH = SkScalarHalf(h);
if (halfW <= 0 || halfH <= 0) {
return;
}
// x方向的宽度大于矩形宽度的一半则跳过
bool skip_hori = rx >= halfW;
bool skip_vert = ry >= halfH;
// 变成椭圆了
if (skip_hori && skip_vert) {
this->addOval(rect, dir);
return;
}
SkAutoPathBoundsUpdate apbu(this, rect);
// 这里就是关键了
// 椭圆宽度==矩形宽度/2
if (skip_hori) {
rx = halfW;
} else if (skip_vert) {
ry = halfH;
}
所以在shape中设定的宽高大于控件的宽高时就会变成圆角矩形了。
以下基于API 25
的源码分析