android 代码自定义checkbox,Android开源 - 自定义CheckBox

本文探讨了在Android开发中,为了实现特定的动画效果,选择直接继承View而非CheckBox的情况。主要围绕两个问题展开:一是如何处理wrap_content的测量模式,通过重写onMeasure()方法确保正确尺寸;二是Path.addPath()和PathMeasure结合使用时的疑惑,通过源码分析理解其行为。文章深入源码,解析了MeasureSpec的工作原理,以及PathMeasure的getLength()方法的底层实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

继承View还是CheckBox

要实现的效果是类似

ba4b34a6ab495e646030cfdbdef97638.gif

考虑到关键是动画效果,所以直接继承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 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有三种模式:

UNSPECIFIED:父容器不对子view的宽高有任何限制

EXACTLY:父容器已经为子view指定了确切的宽高

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。

(责任编辑:ioter)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值