Android开发之自定义CheckBox
要实现的效果如下
考虑到关键是动画效果,所以直接继承view
。不过checkbox
的超类compoundbutton
实现了checkable
接口,这一点值得借鉴。
下面记录一下遇到的问题,并从源码的角度解决。
问题一: 支持 wrap_content
由于是直接继承自view
,wrap_content
需要进行特殊处理。
view measure流程的measurespec:
/** * a measurespec encapsulates the layout requirements passed from parent to child. * each measurespec represents a requirement for either the width or the height. * a measurespec is comprised of a size and a mode. * measurespecs are implemented as ints to reduce object allocation. this class * is provided to pack and unpack the <size, mode> tuple into the int. */ public static class measurespec { private static final int mode_shift = 30; private static final int mode_mask = 0x3 << mode_shift; /** * measure specification mode: the parent has not imposed any constraint * on the child. it can be whatever size it wants. */ public static final int unspecified = 0 << mode_shift; /** * measure specification mode: the parent has determined an exact size * for the child. the child is going to be given those bounds regardless * of how big it wants to be. */ public static final int exactly = 1 << mode_shift; /** * measure specification mode: the child can be as large as it wants up * to the specified size. */ public static final int at_most = 2 << mode_shift; /** * extracts the mode from the supplied measure specification. * * @param measurespec the measure specification to extract the mode from * @return {@link android.view.view.measurespec#unspecified}, * {@link android.view.view.measurespec#at_most} or * {@link android.view.view.measurespec#exactly} */ public static int getmode(int measurespec) { return (measurespec & mode_mask); } /** * extracts the size from the supplied measure specification. * * @param measurespec the measure specification to extract the size from * @return the size in pixels defined in the supplied measure specification */ 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()
的源码:
/** * return the total length of the current contour, or 0 if no path is * associated with this measure object. */ 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; /* note: * as we accumulate distance, we have to check that the result of += * actually made it larger, since a very small delta might be > 0, but * still have no effect on distance (if distance >>> delta). * * we do this check below, and in compute_quad_segs and compute_cubic_segs */ 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
中的声明:
/* iterate through all of the segments (lines, quadratics, cubics) of each contours in a path. the iterator cleans up the segments along the way, removing degenerate segments and adding close verbs where necessary. when the forceclose argument is provided, each contour (as defined by a new starting move command) will be completed with a close verb regardless of the contour's contents. /
从这个声明中可以明白iter的作用是遍历在path
中的每一个contour
。看一下iter.next()
方法:
verb next(skpoint pts[4], bool doconsumedegerates = true) { if (doconsumedegerates) { this->consumedegeneratesegments(); } return this->donext(pts); }
返回值是一个verb
类型:
enum verb { kmove_verb, //!< iter.next returns 1 point kline_verb, //!< iter.next returns 2 points kquad_verb, //!< iter.next returns 3 points kconic_verb, //!< iter.next returns 3 points + iter.conicweight() kcubic_verb, //!< iter.next returns 4 points kclose_verb, //!< iter.next returns 1 point (contour's moveto pt) kdone_verb, //!< iter.next returns 0 points }
不管是什么类型的path
,它一定是由点组成,如果是直线,则两个点,贝塞尔曲线则三个点,依次类推。
donext()
方法的代码就不贴出来了,作用就是判断contour
的类型并把相应的点的坐标取出传给pts[4]
当fiter.next()
返回kdone_verb
时,一次遍历结束。
buildsegments
中的循环正是在做此事,而且从case kline_verb
模式的distance += d
;不难发现这个length
是累加起来的。在举的例子当中,mtickpath
有三个contour
(mentrypath
,mleftpath
,mrightpath
),我们调用mtickmeasure.getlength()
时,首先会累计获取mentrypath
这个contour
的长度。
这就不难解释为什么mtickmeasure
获取的长度和mentrypath
的一样了。那么想一想,怎么让buildsegments()
对下一个contour
进行操作呢?关键是把flength
置为-1
/** move to the next contour in the path. return true if one exists, or false if we're done with the path. */ bool skpathmeasure::nextcontour() { flength = -1; return this->getlength() > 0; }
与native
层对应的api是pathmeasure.nextcontour()
总结
以上就是android开发之自定义checkbox的全部内容,希望本文对大家开发android有所帮助。