android构建自定义的视图组件

在这篇文章http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2013/0307/991.html中我们给出了创建一个自定义view的基本框架,这篇文章中我们做更详细的解释。

android提供了精巧和有力的组件化模型构建用户的UI部分。主要是基于布局类:View和ViewGroup。在此基础上,android平 台提供了大量的预制的View和ViewGroup子类,即布局(layout)和窗口小部件(widget)。可以用它们构建自己的UI。

如果没有符合你需求的预制窗口小部件,你可以创建自己的视图子类。如果只是对已存在的窗口小部件或者布局做小的调整,只需继承该类,覆盖相关的方法。

创建你自己的View子类可以更精确控制视图元素的外观和功能。

  • 可创建完整的自定义渲染视图类型,比如创建一个2d的控制条;

  • 可将一组视图组件合成为一个新的单一组件,比如双选的列表,选择省和市;

  • 覆盖EditText组件,比如notepad tutorial中的示例;

  • 捕捉其他事件比如按键事件,并执行自定义的处理方式,比如在游戏中。

基本方法

总的来说,创建自定义的视图组件步骤是:

  1. 创建自己的类,继承已经存在的View类或者子类;

  2. 覆盖超类的一些方法。这些超类的方法一般以“on”开头,比如onDraw()方法等等;

  3. 使用新创建的扩展类。一旦完成,你的新扩展类就可以用于所有View使用的地方。

**注意:**扩展类可以定义为内部类,在你创建的Activity类之中。这很有用,因为这样可以控制外界的访问,但是这不是必须的,因为你可能需要一个public的自定义View类供更广泛的使用。

完全自定义组件

完全自定义的组件可以创建图形组件显示在你需要的任何地方。

步骤如下:

  1. 可以继承的最通用的视图类是View,可以继承它创建自定义的组件超类;

  2. 可以提供构造方法,并通过xml文件获取属性值和参数;

  3. 创建自己的事件监听器,属性访问器和编辑器等等;

  4. 一般情况下会覆盖onMeasure()方法和onDraw()方法,这会让组件显示一些东西。如果都用默认的行为,onDraw()方法不做任何事情,onMeasure()方法设置一个100×100的区域;

  5. 根据需求覆盖其他on…方法。

扩展onDraw()和onMeasure()方法

onDraw()方法提供给你一个Canvas对象,在它之上可以实现任何你想要的东西,通过2d图形api。比如其他标准的后者自定义的组件,风格化的文字后者其他。

注意:这里不提供3d图形api的支持。如果你需要3d图形支持,必须继承SurfaceView而不是View,并且通过单独的线程画图。可以通过GLSurfaceViewActivity实例查看详细信息。

onMeasure()方法有些麻烦。该方法是在容器和自定义组件之间渲染的重要部分。该方法覆盖,要高效率的和精确的报告被包含区域的测量值。

总的来看,实现onMeasure()方法类似如下步骤:

  1. 调用已经覆盖的onMeasure()方法,传递长和宽规范参数;

  2. 自定义组件在onMeasure()方法中计算需要渲染的组件的长和宽,应该在规范参数的范围内;

  3. 一旦长和宽计算出来,必须调用setMeasuredDimension(int width, int height)方法,这步失败会导致异常的抛出。

一个自定义视图的示例

自定义视图的示例,见:LabelView

该示例演示了一些自定义组件的不同方面:

  • 继承View类,用于完全自定义组件;

  • 参数化的构造方法,提供更多的参数,定义在xml文件中;

  • 标准的公开方法,用于设置标签,比如setText()方法等;

  • 覆盖onMeasure方法确定渲染的组件尺寸;

  • 覆盖onDraw方法,在提供的canvas中画标签。

可以找到对示例的一些使用,在custom_view_1.xml文件中。

该示例运行效果:

image

android示例是混在一起的,比较乱,我这里改写了一下,只有相关示例的代码和配置。看起来比较简单:

LabelView.java

package com.easymorse;
  
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
  
public class LabelView extends View {
    private Paint mTextPaint;
    private String mText;
    private int mAscent;
  
    /**
     * Constructor. This version is only needed if you will be instantiating the
     * object manually (not from a layout XML file).
     * 
     * @param context
     */
    public LabelView(Context context) {
        super(context);
        initLabelView();
    }
  
    /**
     * Construct object, initializing with any attributes we understand from a
     * layout file. These attributes are defined in
     * SDK/assets/res/any/classes.xml.
     * 
     * @see android.view.View#View(android.content.Context,
     *      android.util.AttributeSet)
     */
    public LabelView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initLabelView();
  
        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.LabelView);
  
        CharSequence s = a.getString(R.styleable.LabelView_text);
        if (s != null) {
            setText(s.toString());
        }
  
        // Retrieve the color(s) to be used for this view and apply them.
        // Note, if you only care about supporting a single color, that you
        // can instead call a.getColor() and pass that to setTextColor().
        // setTextColor(a.getColor(R.styleable.LabelView_textColor,
        // 0xFF000000));
        //
        // int textSize = a.getDimensionPixelOffset(
        // R.styleable.LabelView_textSize, 0);
        // if (textSize > 0) {
        // setTextSize(textSize);
        // }
  
        a.recycle();
    }
  
    private final void initLabelView() {
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize(16);
        mTextPaint.setColor(0xFF000000);
        setPadding(3, 3, 3, 3);
    }
  
    /**
     * Sets the text to display in this label
     * 
     * @param text
     *            The text to display. This will be drawn as one line.
     */
    public void setText(String text) {
        mText = text;
        requestLayout();
        invalidate();
    }
  
    /**
     * Sets the text size for this label
     * 
     * @param size
     *            Font size
     */
    public void setTextSize(int size) {
        mTextPaint.setTextSize(size);
        requestLayout();
        invalidate();
    }
  
    /**
     * Sets the text color for this label.
     * 
     * @param color
     *            ARGB value for the text
     */
    public void setTextColor(int color) {
        mTextPaint.setColor(color);
        invalidate();
    }
  
    /**
     * @see android.view.View#measure(int, int)
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec),
                measureHeight(heightMeasureSpec));
    }
  
    /**
     * Determines the width of this view
     * 
     * @param measureSpec
     *            A measureSpec packed into an int
     * @return The width of the view, honoring constraints from measureSpec
     */
    private int measureWidth(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
  
        if (specMode == MeasureSpec.EXACTLY) {
            // We were told how big to be
            result = specSize;
        } else {
            // Measure the text
            result = (int) mTextPaint.measureText(mText) + getPaddingLeft()
                    + getPaddingRight();
            if (specMode == MeasureSpec.AT_MOST) {
                // Respect AT_MOST value if that was what is called for by
                // measureSpec
                result = Math.min(result, specSize);
            }
        }
  
        return result;
    }
  
    /**
     * Determines the height of this view
     * 
     * @param measureSpec
     *            A measureSpec packed into an int
     * @return The height of the view, honoring constraints from measureSpec
     */
    private int measureHeight(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
  
        mAscent = (int) mTextPaint.ascent();
        if (specMode == MeasureSpec.EXACTLY) {
            // We were told how big to be
            result = specSize;
        } else {
            // Measure the text (beware: ascent is a negative number)
            result = (int) (-mAscent + mTextPaint.descent()) + getPaddingTop()
                    + getPaddingBottom();
            if (specMode == MeasureSpec.AT_MOST) {
                // Respect AT_MOST value if that was what is called for by
                // measureSpec
                result = Math.min(result, specSize);
            }
        }
        return result;
    }
  
    /**
     * Render the text
     * 
     * @see android.view.View#onDraw(android.graphics.Canvas)
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText(mText, getPaddingLeft(), getPaddingTop() - mAscent,
                mTextPaint);
    }
}

attrs.xml

<?xml version="1.0" encoding="utf-8" ?>
- <!--
  
 Copyright (C) 2007 The Android Open Source Project
  
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at
    
          http://www.apache.org/licenses/LICENSE-2.0
    
     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
  
  -->
- <resources>
- <declare-styleable name="LabelView">
  <attr name="text" format="string" />
  <attr name="textColor" format="color" />
  <attr name="textSize" format="dimension" />
  </declare-styleable>
  </resources>