Android開發(fā)之自定義CheckBox
要實現(xiàn)的效果如下
考慮到關(guān)鍵是動畫效果,所以直接繼承View
。不過CheckBox
的超類CompoundButton
實現(xiàn)了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為了節(jié)約內(nèi)存,設(shè)計了MeasureSpec
,它由mode
和size
兩部分構(gòu)成,做這么多終究是為了從父容器向子view傳達長寬的要求。
mode有三種模式:
1、UNSPECIFIED:父容器不對子view的寬高有任何限制
2、EXACTLY:父容器已經(jīng)為子view指定了確切的寬高
3、AT_MOST:父容器指定最大的寬高,子view不能超過
wrap_content屬于AT_MOST模式。
來看一下大致的measure過程:
在View中首先調(diào)用measure(),最終調(diào)用onMeasure()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
setMeasuredDimension
設(shè)置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結(jié)合使用
舉例子說明問題:
mTickPath.addPath(entryPath); mTickPath.addPath(leftPath); mTickPath.addPath(rightPath); mTickMeasure = new PathMeasure(mTickPath, false); // mTickMeasure is a PathMeasure
盡管mTickPath
現(xiàn)在是由三個path
構(gòu)成,但是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
調(diào)用了native
層的方法,到這里不得不看底層的實現(xiàn)了。
通過閱讀源代碼發(fā)現(xiàn),Path
和PathMeasure
實際分別對應(yīng)底層的SKPath
和SKPathMeasure
。
查看native
層的getLength()
源碼:
SkScalar SkPathMeasure::getLength() { if (fPath == NULL) { return 0; } if (fLength < 0) { this->buildSegments(); } SkASSERT(fLength >= 0); return fLength; }
實際上調(diào)用的buildSegments()
來對fLength
賦值,這里底層的設(shè)計有一個很聰明的地方——在初始化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
時我們需要計算,也就是說當還沒有執(zhí)行過getLength()
方法時,fLength
一直是-1,一旦執(zhí)行則fLength>=0
,則下一次就不會執(zhí)行buildSegments(),
這樣避免了重復(fù)計算.
截取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
??匆幌?code>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
的類型并把相應(yīng)的點的坐標取出傳給pts[4]
當fIter.next()
返回kDone_Verb
時,一次遍歷結(jié)束。
buildSegments
中的循環(huán)正是在做此事,而且從case kLine_Verb
模式的distance += d
;不難發(fā)現(xiàn)這個length
是累加起來的。在舉的例子當中,mTickPath
有三個contour
(mEntryPath
,mLeftPath
,mRightPath
),我們調(diào)用mTickMeasure.getLength()
時,首先會累計獲取mEntryPath
這個contour
的長度。
這就不難解釋為什么mTickMeasure
獲取的長度和mEntryPath
的一樣了。那么想一想,怎么讓buildSegments()
對下一個contour
進行操作呢?關(guān)鍵是把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
層對應(yīng)的API是PathMeasure.nextContour()
總結(jié)
以上就是Android開發(fā)之自定義CheckBox的全部內(nèi)容,希望本文對大家開發(fā)Android有所幫助。
- 詳解Android Checkbox的使用方法
- Android中ListView結(jié)合CheckBox實現(xiàn)數(shù)據(jù)批量選擇(全選、反選、全不選)
- Android控件之CheckBox、RadioButton用法實例分析
- Android中自定義Checkbox組件實例
- android開發(fā)教程之自定義控件checkbox的樣式示例
- android RadioButton和CheckBox組件的使用方法
- Android checkbox的listView(多選,全選,反選)具體實現(xiàn)方法
- Android CheckBox 的使用案例分析
- Android在listview添加checkbox實現(xiàn)原理與代碼
- Android控件系列之CheckBox使用介紹
相關(guān)文章
Android進程運行中權(quán)限被收回導(dǎo)致關(guān)閉的問題解決
在Android開發(fā)中我們可能會遇到這樣的問題,進程還在運行著某些權(quán)限卻被收回了,這就導(dǎo)致進程崩潰被迫關(guān)閉,本篇文章將帶你了解這個問題的發(fā)生與解決方法2021-10-10解析Android開發(fā)優(yōu)化之:軟引用與弱引用的應(yīng)用
Java從JDK1.2版本開始,就把對象的引用分為四種級別,從而使程序能更加靈活的控制對象的生命周期。這四種級別由高到低依次為:強引用、軟引用、弱引用和虛引用,本篇文章重點介紹一下軟引用和弱引用2013-05-05Android獲取移動網(wǎng)絡(luò)信號強度的方法
這篇文章主要介紹了Android獲取移動網(wǎng)絡(luò)信號強度的方法,幫助大家更好的理解和學習使用Android,感興趣的朋友可以了解下2021-04-04