Android开发之自定义CheckBox

要实现的效果如下

考虑到关键是动画效果,所以直接继承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) 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。看一下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有所帮助。

(0)

相关推荐

  • Android CheckBox 的使用案例分析

    复制代码 代码如下: public class MainActivity extends Activity { TextView tv; CheckBox cb1; CheckBox cb2; @Override protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_main); cb1 = (Check

  • 详解Android Checkbox的使用方法

    0和1是计算机的基础,数理逻辑中0和1代表两种状态,真与假.0和1看似简单,其实变化无穷. 今天我就来聊聊android控件中拥有着0和1这种特性的魔力控件checkbox. 先来讲讲Checkbox的基本使用.在XML中定义. <?xml version="1.0" encoding="utf-8"?> <CheckBox xmlns:android="http://schemas.android.com/apk/res/android

  • Android checkbox的listView(多选,全选,反选)具体实现方法

    布局文件:[html]  复制代码 代码如下: <?xml version="1.0" encoding="utf-8"?>  <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="fill_parent"      android:layout_height=

  • Android中自定义Checkbox组件实例

    在Android中,Checkbox是一个很重要的UI组件,而且在Android中,它展现的形式越来越好看,这就说明有些系统,比如4.0以下,checkbox还是比较不好看,或者跟软件的风格不协调,就需要我们自定义这个组件. 自定义这个组件很简单,简单的增加修改xml文件即可. 准备工作 准备好两张图片,一个是选中的图片,另一个是未选中的图片.本文以checked.png和unchecked.png为例. 设置选择框 在drawable下新建文件custom_checkbox.xml 复制代码

  • android开发教程之自定义控件checkbox的样式示例

    主界面xml文件 复制代码 代码如下: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_p

  • Android在listview添加checkbox实现原理与代码

    主界面CheckBoxinListViewActivity.java代码如下: 复制代码 代码如下: public class CheckBoxinListViewActivity extends Activity { /** Called when the activity is first created. */ private MyAdapter adapter; private ListView listview; private Button checkAll; private But

  • android RadioButton和CheckBox组件的使用方法

    RadioButton是单选按钮,多个RadioButton放在一个RadioGroup控件中,也就是说每次只能有1个RadioButton被选中.而CheckBox是多选按钮,Toatst是android中带的一个用于显示提示小窗口消息的控件,其提示的内容过一会儿会自动消失.RadioGroup和CheckBox控件设置监听器都是用的setOnCheckedChangeListener函数,其输入参数是一个函数,且函数内部要实现1个内部类.RadioGroup监听器的输入参数用的是RadioG

  • Android中ListView结合CheckBox实现数据批量选择(全选、反选、全不选)

    APP的开发中,会常遇到这样的需求:批量取消(删除)List中的数据.这就要求ListVIew支持批量选择.全选.单选等等功能,做一个比较强大的ListView批量选择功能是很有必要的,那如何做呢? 可想而知,要支持批量选择,那CheckBox的使用是不可或缺的,下面,就使用ListView结合CheckBox实现数据的批量选择. 先看下效果图,有图有真相: 先说明接下来要实现的ListView+CheckBox支持的功能:     1.  外部点击"编辑"(长按ListView的某一

  • Android控件之CheckBox、RadioButton用法实例分析

    本文实例讲述了Android控件之CheckBox.RadioButton用法.分享给大家供大家参考.具体如下: CheckBox和RadioButton控件都只有选中和未选中状态,不同的是RadioButton是单选按钮,需要编制到一个RadioGroup中,同一时刻一个RadioGroup中只能有一个按钮处于选中状态. 以下为CheckBox和RadioButton常用方法及说明 以下为单选按钮和复选按钮的使用方法 目录结构: main.xml布局文件: <?xml version="

  • Android控件系列之CheckBox使用介绍

    学习目的: 1.掌握在Android中如何建立CheckBox 2.掌握CheckBox的常用属性 3.掌握CheckBox选中状态变换的事件(监听器) CheckBox简介: CheckBox和Button一样,也是一种古老的控件,它的优点在于,不用用户去填写具体的信息,只需轻轻点击,缺点在于只有"是"和"否"两种情况,但我们往往利用它的这个特性,来获取用户的一些信息. 如一个身份表单中,常常让用户填写"是否已经结婚",显然让用户去填写&quo

随机推荐