源码网商城,靠谱的源码在线交易网站 我的订单 购物车 帮助

源码网商城

Android开发之自定义CheckBox

  • 时间:2020-05-28 04:19 编辑: 来源: 阅读:
  • 扫一扫,手机访问
摘要:Android开发之自定义CheckBox
[b]要实现的效果如下[/b] [img]http://files.jb51.net/file_images/article/201608/2016811113134505.gif?2016711113145[/img] 考虑到关键是动画效果,所以直接继承[code]View[/code]。不过[code]CheckBox[/code]的超类[code]CompoundButton[/code]实现了[code]Checkable[/code]接口,这一点值得借鉴。 下面记录一下遇到的问题,并从源码的角度解决。 [b]问题一: 支持 wrap_content[/b] 由于是直接继承自[code]View[/code],[code]wrap_content[/code]需要进行特殊处理。 [b]View measure流程的MeasureSpec:[/b]
 /**
  * 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为了节约内存,设计了[code]MeasureSpec[/code],它由[code]mode[/code]和[code]size[/code]两部分构成,做这么多终究是为了从父容器向子view传达长宽的要求。 [b]mode有三种模式:[/b]       1、[b]UNSPECIFIED:[/b]父容器不对子view的宽高有任何限制       2、[b]EXACTLY:[/b]父容器已经为子view指定了确切的宽高       3、[b]AT_MOST:[/b]父容器指定最大的宽高,子view不能超过 [b]wrap_content属于AT_MOST模式。[/b] 来看一下大致的measure过程: [b]在View中首先调用measure(),最终调用onMeasure()[/b]
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
 }
[code]setMeasuredDimension[/code]设置[code]view[/code]的宽高。再来看看[code]getDefaultSize()[/code]
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;
 }
由于[code]wrap_content[/code]属于模式[code]AT_MOST[/code],所以宽高为[code]specSize[/code],也就是父容器的[code]size[/code],这就和[code]match_parent[/code]一样了。支持[code]wrap_content[/code]总的思路是重写[code]onMeasure()[/code]具体点来说,模仿[code]getDefaultSize()[/code]重新获取宽高。
 @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);
 }
[b]问题二:Path.addPath()和PathMeasure结合使用[/b] 举例子说明问题:
 mTickPath.addPath(entryPath);
 mTickPath.addPath(leftPath);
 mTickPath.addPath(rightPath);
 mTickMeasure = new PathMeasure(mTickPath, false);
 // mTickMeasure is a PathMeasure
尽管[code]mTickPath[/code]现在是由三个[code]path[/code]构成,但是[code]mTickMeasure[/code]此时的[code]length[/code]和[code]entryPath[/code]长度是一样的,到这里我就很奇怪了。看一下[code]getLength()[/code]的源码:
 /**
  * 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);
 }
从注释来看,获取的是当前[code]contour[/code]的总长。 [code]getLength[/code]调用了[code]native[/code]层的方法,到这里不得不看底层的实现了。 通过阅读源代码发现,[code]Path[/code]和[code]PathMeasure[/code]实际分别对应底层的[code]SKPath[/code]和[code]SKPathMeasure[/code]。 查看[code]native[/code]层的[code]getLength()[/code]源码:
 SkScalar SkPathMeasure::getLength() {
  if (fPath == NULL) {
   return 0;
  }
  if (fLength < 0) {
   this->buildSegments();
  }
  SkASSERT(fLength >= 0);
  return fLength;
}
实际上调用的[code]buildSegments()[/code]来对[code]fLength[/code]赋值,这里底层的设计有一个很聪明的地方——在初始化[code]SKPathMeasure[/code]时对[code]fLength[/code]做了特殊处理:
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);
}
当[code]fLength=-1[/code]时我们需要计算,也就是说当还没有执行过[code]getLength()[/code]方法时,[code]fLength[/code]一直是-1,一旦执行则[code]fLength>=0[/code],则下一次就不会执行[code]buildSegments(),[/code]这样避免了重复计算. [b]截取buildSegments()部分代码:[/b]
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;
代码较长需要慢慢思考。[code]fIter[/code]是一个[code]Iter[/code]类型,在[code]SKPath.h[/code]中的声明:
/* 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的作用是遍历在[code]path[/code]中的每一个[code]contour[/code]。看一下[code]Iter.next()[/code]方法:
 Verb next(SkPoint pts[4], bool doConsumeDegerates = true) {
   if (doConsumeDegerates) {
    this->consumeDegenerateSegments();
   }
   return this->doNext(pts);
 }
返回值是一个[code]Verb[/code]类型:
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
}
不管是什么类型的[code]Path[/code],它一定是由点组成,如果是直线,则两个点,贝塞尔曲线则三个点,依次类推。 [code]doNext()[/code]方法的代码就不贴出来了,作用就是判断[code]contour[/code]的类型并把相应的点的坐标取出传给[code]pts[4][/code] 当[code]fIter.next()[/code]返回[code]kDone_Verb[/code]时,一次遍历结束。 [code]buildSegments[/code]中的循环正是在做此事,而且从[code]case kLine_Verb[/code]模式的[code]distance += d[/code];不难发现这个[code]length[/code]是累加起来的。在举的例子当中,[code]mTickPath[/code]有三个[code]contour[/code]([code]mEntryPath[/code],[code]mLeftPath[/code],[code]mRightPath[/code]),我们调用[code]mTickMeasure.getLength()[/code]时,首先会累计获取[code]mEntryPath[/code]这个[code]contour[/code]的长度。 这就不难解释为什么[code]mTickMeasure[/code]获取的长度和[code]mEntryPath[/code]的一样了。那么想一想,怎么让[code]buildSegments()[/code]对下一个[code]contour[/code]进行操作呢?关键是把[code]fLength[/code]置为-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;
}
与[code]native[/code]层对应的API是[code]PathMeasure.nextContour()[/code] [b]总结[/b] 以上就是Android开发之自定义CheckBox的全部内容,希望本文对大家开发Android有所帮助。
  • 全部评论(0)
联系客服
客服电话:
400-000-3129
微信版

扫一扫进微信版
返回顶部