要实现的效果如下
考虑到关键是动画效果,所以直接继承
View
。不过CheckBox
的超类CompoundButton
实现了Checkable
接口,这一点值得借鉴。
下面记录一下遇到的问题,并从源码的角度解决。
问题一: 支持 wrap_content
由于是直接继承自
View
,wrap_content
需要进行特殊处理。View measure流程的MeasureSpec:
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
从文档说明知道android为了节约内存,设计了
MeasureSpec
,它由mode
和size
两部分构成,做这么多终究是为了从父容器向子view传达长宽的要求。
mode有三种模式:
1、UNSPECIFIED:父容器不对子view的宽高有任何限制
2、EXACTLY:父容器已经为子view指定了确切的宽高
3、AT_MOST:父容器指定最大的宽高,子view不能超过
wrap_content属于AT_MOST模式。
来看一下大致的measure过程:
在View中首先调用measure(),最终调用onMeasure()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasuredDimension
设置view
的宽高。再来看看getDefaultSize()
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
由于
wrap_content
属于模式AT_MOST
,所以宽高为specSize
,也就是父容器的size
,这就和match_parent
一样了。支持wrap_content
总的思路是重写onMeasure()
具体点来说,模仿getDefaultSize()
重新获取宽高。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = widthSize, height = heightSize;
if (widthMode == MeasureSpec.AT_MOST) {
width = dp2px(DEFAULT_SIZE);
}
if (heightMode == MeasureSpec.AT_MOST) {
height = dp2px(DEFAULT_SIZE);
}
setMeasuredDimension(width, height);
}
问题二:Path.addPath()和PathMeasure结合使用
举例子说明问题:
mTickPath.addPath(entryPath);
mTickPath.addPath(leftPath);
mTickPath.addPath(rightPath);
mTickMeasure = new PathMeasure(mTickPath, false);
// mTickMeasure is a PathMeasure
尽管
mTickPath
现在是由三个path
构成,但是mTickMeasure
此时的length
和entryPath
长度是一样的,到这里我就很奇怪了。看一下getLength()
的源码:
public float getLength() {
return native_getLength(native_instance);
}
从注释来看,获取的是当前
contour
的总长。
getLength
调用了native
层的方法,到这里不得不看底层的实现了。通过阅读源代码发现,
Path
和PathMeasure
实际分别对应底层的SKPath
和SKPathMeasure
。
查看
native
层的getLength()
源码:
SkScalar SkPathMeasure::getLength() {
if (fPath == NULL) {
return 0;
}
if (fLength < 0) {
this->buildSegments();
}
SkASSERT(fLength >= 0);
return fLength;
}
实际上调用的
buildSegments()
来对fLength
赋值,这里底层的设计有一个很聪明的地方——在初始化SKPathMeasure
时对fLength
做了特殊处理:
SkPathMeasure::SkPathMeasure(const SkPath& path, bool forceClosed) {
fPath = &path;
fLength = -1; // signal we need to compute it
fForceClosed = forceClosed;
fFirstPtIndex = -1;
fIter.setPath(path, forceClosed);
}
当
fLength=-1
时我们需要计算,也就是说当还没有执行过getLength()
方法时,fLength
一直是-1,一旦执行则fLength>=0
,则下一次就不会执行buildSegments(),
这样避免了重复计算.
截取buildSegments()部分代码:
void SkPathMeasure::buildSegments() {
SkPoint pts[4];
int ptIndex = fFirstPtIndex;
SkScalar distance = 0;
bool isClosed = fForceClosed;
bool firstMoveTo = ptIndex < 0;
Segment* seg;
fSegments.reset();
bool done = false;
do {
switch (fIter.next(pts)) {
case SkPath::kMove_Verb:
ptIndex += 1;
fPts.append(1, pts);
if (!firstMoveTo) {
done = true;
break;
}
firstMoveTo = false;
break;
case SkPath::kLine_Verb: {
SkScalar d = SkPoint::Distance(pts[0], pts[1]);
SkASSERT(d >= 0);
SkScalar prevD = distance;
distance += d;
if (distance > prevD) {
seg = fSegments.append();
seg->fDistance = distance;
seg->fPtIndex = ptIndex;
seg->fType = kLine_SegType;
seg->fTValue = kMaxTValue;
fPts.append(1, pts + 1);
ptIndex++;
}
} break;
case SkPath::kQuad_Verb: {
SkScalar prevD = distance;
distance = this->compute_quad_segs(pts, distance, 0, kMaxTValue, ptIndex);
if (distance > prevD) {
fPts.append(2, pts + 1);
ptIndex += 2;
}
} break;
case SkPath::kConic_Verb: {
const SkConic conic(pts, fIter.conicWeight());
SkScalar prevD = distance;
distance = this->compute_conic_segs(conic, distance, 0, kMaxTValue, ptIndex);
if (distance > prevD) {
// we store the conic weight in our next point, followed by the last 2 pts
// thus to reconstitue a conic, you'd need to say
// SkConic(pts[0], pts[2], pts[3], weight = pts[1].fX)
fPts.append()->set(conic.fW, 0);
fPts.append(2, pts + 1);
ptIndex += 3;
}
} break;
case SkPath::kCubic_Verb: {
SkScalar prevD = distance;
distance = this->compute_cubic_segs(pts, distance, 0, kMaxTValue, ptIndex);
if (distance > prevD) {
fPts.append(3, pts + 1);
ptIndex += 3;
}
} break;
case SkPath::kClose_Verb:
isClosed = true;
break;
case SkPath::kDone_Verb:
done = true;
break;
}
} while (!done);
fLength = distance;
fIsClosed = isClosed;
fFirstPtIndex = ptIndex;
代码较长需要慢慢思考。
fIter
是一个Iter
类型,在SKPath.h
中的声明:
bool SkPathMeasure::nextContour() {
fLength = -1;
return this->getLength() > 0;
}
与
native
层对应的API是PathMeasure.nextContour()
总结
以上就是Android开发之自定义CheckBox的全部内容,希望本文对大家开发Android有所帮助。
您可能感兴趣的文章:详解Android Checkbox的使用方法Android中ListView结合CheckBox实现数据批量选择(全选、反选、全不选)Android控件之CheckBox、RadioButton用法实例分析Android中自定义Checkbox组件实例android开发教程之自定义控件checkbox的样式示例android RadioButton和CheckBox组件的使用方法Android checkbox的listView(多选,全选,反选)具体实现方法Android CheckBox 的使用案例分析Android在listview添加checkbox实现原理与代码Android控件系列之CheckBox使用介绍