以LabelView(效果类似于TextView)为例示范简单的自定义View。首先,理清思路,明确要定义的组件的一些属性(类似组件的简单拼接,可不定义属性)

第一步:配置属性:在Values文件夹的attrs.xml中配置出来,见下图所示。

clip_image002

代码如下:

 

  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <resources> 
  3.     <!-- 自定义属性  --> 
  4.     <declare-styleable name="MyLabelView"> 
  5.         <attr name="text" format="string" /> 
  6.         <attr name="textColor" format="color" /> 
  7.         <attr name="textSize" format="dimension" /> 
  8.     </declare-styleable> 
  9. </resources> 

第二步:重写构造方法

 

第三步:在构造方法中绑定属性:即在xml中配置的自定义组件的属性真正起到作用

 

 

代码:

 

  1. public MyLabelView(Context context) { 
  2.         super(context); 
  3.         // TODO Auto-generated constructor stub 
  4.         initLabelView(); 
  5.     } 
  6.      
  7.  
  8.     public MyLabelView(Context context, AttributeSet attrs) { 
  9.         super(context, attrs); 
  10.         // TODO Auto-generated constructor stub 
  11.         initLabelView(); 
  12.          
  13.         TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.MyLabelView); 
  14.         //获得三个属性值 
  15.          
  16.         //1 
  17.         String textContent = typeArray.getString(R.styleable.MyLabelView_text); 
  18.         if (null != textContent) { 
  19.             setText(textContent); 
  20.         } 
  21.          
  22.         //2 
  23.         int color = typeArray.getColor(R.styleable.MyLabelView_textColor, 0xffff0000); 
  24.         setTextColor(color); 
  25.          
  26.         //3 
  27.         float dimension = typeArray.getDimension(R.styleable.MyLabelView_textSize, 0); 
  28.         if (dimension > 0) { 
  29.             setTextSize(dimension);  
  30.         } 
  31.          
  32.         typeArray.recycle();//回收 
  33.          
  34.     } 

 

 

第四步:覆写方法以及创建新方法,实现相应功能

 

  1. /* 
  2.  *  onMeasure传入的widthMeasureSpec和heightMeasureSpec不是一般的尺寸数值,而是将模式和尺寸组合在一起的数值。 
  3.  *  a) int mode = MeasureSpec.getMode(widthMeasureSpec); 
  4.  *   
  5.  *     1)mode:MeasureSpec.UNSPECIFIED ----未指定尺寸,这种情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式 
  6.  *     2)mode:MeasureSpec.EXACTLY---------是精确尺寸,当我们将控件的layout_width或layout_height指定为具体数值时如andorid:layout_width="50dip", 
  7.  *                                        或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。 
  8.  *     3)mode:MeasureSpec.AT_MOST---------最大尺寸,当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化, 
  9.  *                                        此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。 
  10.  *   
  11.  *  b) int size = MeasureSpec.getSize(widthMeasureSpec) 
  12.  *   
  13.  *  Note:在重写onMeasure方法时要根据模式不同进行尺寸计算,以下的代码是典型的写法 
  14.  *   
  15.  */ 
  16. @Override 
  17. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
  18.     // TODO Auto-generated method stub 
  19.     setMeasuredDimension(measuredWidth(widthMeasureSpec), measuredHeight(heightMeasureSpec)); 
  20.  
  21.  
  22. private int measuredWidth(int widthMeasureSpec) { 
  23.     // TODO Auto-generated method stub 
  24.     int result = 0
  25.     int widthMode = MeasureSpec.getMode(widthMeasureSpec); 
  26.     widthSize = MeasureSpec.getSize(widthMeasureSpec); 
  27.      
  28.     if (MeasureSpec.EXACTLY == widthMode) {//指定 
  29.         result = widthSize; 
  30.     } else {//测量 
  31.         result = (int) (mPaint.measureText(mText) + getPaddingLeft() + getPaddingRight()); 
  32.         if (MeasureSpec.AT_MOST == widthMode) { 
  33.             result = Math.min(result, widthSize);//size是父组件允许的最大值 
  34.         } 
  35.     } 
  36.     return result; 
  37.  
  38.  
  39. private int measuredHeight(int heightMeasureSpec) { 
  40.     // TODO Auto-generated method stub 
  41.     int result = 0
  42.     int mode = MeasureSpec.getMode(heightMeasureSpec); 
  43.     int size = MeasureSpec.getSize(heightMeasureSpec); 
  44.     mAscent = (int) mPaint.ascent(); 
  45.      
  46.     if (MeasureSpec.EXACTLY == mode) {//指定 
  47.         result = size; 
  48.     } else {//测量 
  49.         result = (int) (-mAscent + mPaint.descent()) + getPaddingTop() 
  50.                    + getPaddingBottom();//copy,错误处 
  51.         if (MeasureSpec.AT_MOST == mode) { 
  52.             result = Math.min(result, size); 
  53.         } 
  54.     } 
  55.     return result; 
  56.  
  57.  
  58.  
  59. @Override 
  60. protected void onDraw(Canvas canvas) { 
  61.     // TODO Auto-generated method stub 
  62.     super.onDraw(canvas); 
  63.     canvas.drawText(mText, getPaddingLeft(), getPaddingTop()-mAscent, mPaint);//Canvas 作为绘制文本时,是以基线为基准绘制的,不是左上角 
  64.        this.setBackgroundColor(Color.RED); 
  65.      
  66.  
  67.  
  68. private void initLabelView() { 
  69.     mPaint = new Paint(); 
  70.     mPaint.setColor(Color.WHITE); 
  71.     mPaint.setAntiAlias(true); 
  72.     mPaint.setTextSize(15); 
  73.     setPadding(10101010); 
  74.  
  75.  
  76. public void setText(String text) { 
  77.     mText = text; 
  78.     requestLayout();//重新排版 
  79.     invalidate(); 
  80.  
  81. public void setTextSize(float dimension) { 
  82.     mPaint.setTextSize(dimension); 
  83.     requestLayout();//重新排版 
  84.     invalidate(); 
  85.  
  86. public void setTextColor(int color) { 
  87.     mPaint.setColor(color); 
  88.     requestLayout();//重新排版 
  89.     invalidate(); 

第五步:xml中的使用(命名空间和标签名)

 

  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <LinearLayout  
  3.     xmlns:android="http://schemas.android.com/apk/res/android" 
  4.     xmlns:my="http://schemas.android.com/apk/res/com.mike.activity" 
  5.     android:layout_width="fill_parent" 
  6.     android:layout_height="fill_parent" 
  7.     android:orientation="vertical" > 
  8.  
  9.  
  10.     <com.mike.activity.MyLabelView 
  11.         android:id="@+id/textView1" 
  12.         android:layout_width="wrap_content" 
  13.         android:layout_height="wrap_content" 
  14.         my:textSize="25dip" 
  15.         my:textColor="@android:color/white" 
  16.         my:text="my_label_view" /> 
  17.  
  18. </LinearLayout>