国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

Android開發之自定義CheckBox

2019-12-12 05:42:59
字體:
來源:轉載
供稿:網友

要實現的效果如下

考慮到關鍵是動畫效果,所以直接繼承View。不過CheckBox的超類CompoundButton實現了Checkable接口,這一點值得借鑒。

下面記錄一下遇到的問題,并從源碼的角度解決。

問題一: 支持 wrap_content

由于是直接繼承自Viewwrap_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,它由modesize兩部分構成,做這么多終究是為了從父容器向子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此時的lengthentryPath長度是一樣的,到這里我就很奇怪了。看一下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層的方法,到這里不得不看底層的實現了。

通過閱讀源代碼發現,PathPathMeasure實際分別對應底層的SKPathSKPathMeasure

查看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) ofeach contours in a path.The iterator cleans up the segments along the way, removing degeneratesegments and adding close verbs where necessary. When the forceCloseargument is provided, each contour (as defined by a new startingmove command) will be completed with a close verb regardless of thecontour'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有三個contourmEntryPath,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有所幫助。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 建始县| 庆城县| 和龙市| 雷山县| 靖宇县| 潼南县| 页游| 淳安县| 保靖县| 吴忠市| 西贡区| 县级市| 屏南县| 虞城县| 建阳市| 修武县| 崇仁县| 定南县| 庆安县| 竹北市| 潜山县| 延长县| 邛崃市| 汾西县| 定安县| 鄂托克旗| 略阳县| 集安市| 乌拉特后旗| 上饶市| 芦溪县| 夏津县| 来宾市| 改则县| 邵阳市| 信宜市| 义马市| 新竹市| 北宁市| 武威市| 蒙城县|