最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android開發(fā)之自定義CheckBox

 更新時(shí)間:2016年08月11日 11:39:33   投稿:daisy  
本文通過實(shí)例代碼演示如何在Android中如何自定義CheckBox,實(shí)現(xiàn)的效果很好,有需要的可以參考借鑒。

要實(shí)現(xiàn)的效果如下

考慮到關(guān)鍵是動(dòng)畫效果,所以直接繼承View。不過CheckBox的超類CompoundButton實(shí)現(xiàn)了Checkable接口,這一點(diǎn)值得借鑒。

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

問題一: 支持 wrap_content

由于是直接繼承自View,wrap_content需要進(jìn)行特殊處理。

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è)計(jì)了MeasureSpec,它由modesize兩部分構(gòu)成,做這么多終究是為了從父容器向子view傳達(dá)長(zhǎng)寬的要求。

mode有三種模式:

      1、UNSPECIFIED:父容器不對(duì)子view的寬高有任何限制

      2、EXACTLY:父容器已經(jīng)為子view指定了確切的寬高

      3、AT_MOST:父容器指定最大的寬高,子view不能超過

wrap_content屬于AT_MOST模式。

來(lái)看一下大致的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的寬高。再來(lái)看看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()具體點(diǎn)來(lái)說,模仿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)在是由三個(gè)path構(gòu)成,但是mTickMeasure此時(shí)的lengthentryPath長(zhǎng)度是一樣的,到這里我就很奇怪了。看一下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);
 }

從注釋來(lái)看,獲取的是當(dāng)前contour的總長(zhǎng)。

getLength調(diào)用了native層的方法,到這里不得不看底層的實(shí)現(xiàn)了。

通過閱讀源代碼發(fā)現(xiàn),PathPathMeasure實(shí)際分別對(duì)應(yīng)底層的SKPathSKPathMeasure。

查看native層的getLength()源碼:

 SkScalar SkPathMeasure::getLength() {
  if (fPath == NULL) {
   return 0;
  }
  if (fLength < 0) {
   this->buildSegments();
  }
  SkASSERT(fLength >= 0);
  return fLength;
}

實(shí)際上調(diào)用的buildSegments()來(lái)對(duì)fLength賦值,這里底層的設(shè)計(jì)有一個(gè)很聰明的地方——在初始化SKPathMeasure時(shí)對(duì)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);
}

當(dāng)fLength=-1時(shí)我們需要計(jì)算,也就是說當(dāng)還沒有執(zhí)行過getLength()方法時(shí),fLength一直是-1,一旦執(zhí)行則fLength>=0,則下一次就不會(huì)執(zhí)行buildSegments(),這樣避免了重復(fù)計(jì)算.

截取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;

代碼較長(zhǎng)需要慢慢思考。fIter是一個(gè)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. /

從這個(gè)聲明中可以明白Iter的作用是遍歷在path中的每一個(gè)contour??匆幌?code>Iter.next()方法:

 Verb next(SkPoint pts[4], bool doConsumeDegerates = true) {
   if (doConsumeDegerates) {
    this->consumeDegenerateSegments();
   }
   return this->doNext(pts);
 }

返回值是一個(gè)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,它一定是由點(diǎn)組成,如果是直線,則兩個(gè)點(diǎn),貝塞爾曲線則三個(gè)點(diǎn),依次類推。

doNext()方法的代碼就不貼出來(lái)了,作用就是判斷contour的類型并把相應(yīng)的點(diǎn)的坐標(biāo)取出傳給pts[4]

當(dāng)fIter.next()返回kDone_Verb時(shí),一次遍歷結(jié)束。

buildSegments中的循環(huán)正是在做此事,而且從case kLine_Verb模式的distance += d;不難發(fā)現(xiàn)這個(gè)length是累加起來(lái)的。在舉的例子當(dāng)中,mTickPath有三個(gè)contourmEntryPath,mLeftPath,mRightPath),我們調(diào)用mTickMeasure.getLength()時(shí),首先會(huì)累計(jì)獲取mEntryPath這個(gè)contour的長(zhǎng)度。

這就不難解釋為什么mTickMeasure獲取的長(zhǎng)度和mEntryPath的一樣了。那么想一想,怎么讓buildSegments()對(duì)下一個(gè)contour進(jìn)行操作呢?關(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層對(duì)應(yīng)的API是PathMeasure.nextContour()

總結(jié)

以上就是Android開發(fā)之自定義CheckBox的全部?jī)?nèi)容,希望本文對(duì)大家開發(fā)Android有所幫助。

相關(guān)文章

最新評(píng)論

建始县| 周宁县| 万载县| 抚顺县| 巴林右旗| 宁海县| 平武县| 江油市| 昔阳县| 海城市| 乐安县| 高阳县| 中超| 禹城市| 鸡泽县| 涟源市| 赣榆县| 鄂伦春自治旗| 承德县| 普格县| 新干县| 辽宁省| 辽源市| 巴中市| 金门县| 司法| 岑巩县| 永安市| 斗六市| 河池市| 磐安县| 佛教| 方城县| 凤翔县| 兰溪市| 苗栗县| 临湘市| 长宁区| 霍林郭勒市| 闽清县| 临夏县|