自定义View(一)

自定义View学习

Posted by donnieSky on April 17, 2016

自定义View(一)

  • 继承View类的自定义控件,核心步骤分别为尺寸测量onMeasure与绘制onDraw。因为View类型的子类也是视图树的叶子节点,因此它只负责绘制好自身的内容即可,而这两步就是完成它职责的所有工作。

自定义View实践

这里我们来简单的实现一个显示图片的View:

步骤一:自定义View的属性

  • values/attr.xml中定义我们自定义View所需要的属性,这里我们给自定义View一个名为src的整型属性,通过这个属性我们可以为我们的自定义View设置图片的资源id,attr.xml内容如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SimpleImageView">
        <attr name="src" format="integer"/>
    </declare-styleable>
</resources>

步骤二:核心代码实现

  • 具体代码如下:
public class SimpleImageView extends View{

    /**
     * 画笔
     */
    private Paint mBitmapPaint;

    /**
     * 图片
     */
    private Drawable mDrawable;

    /**
     * View的宽度
     */
    private int mWidth;

    /**
     * View的高度
     */
    private int mHeight;

    public SimpleImageView(Context context) {
        super(context,null);
    }

    public SimpleImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //根据属性初始化
        initAttrs(attrs);
        //初始化画笔
        mBitmapPaint = new Paint();
        mBitmapPaint.setAntiAlias(true);
    }

    private void initAttrs(AttributeSet attrs){
        if (attrs != null){
            TypedArray array = null;
            try {
                array = getContext().obtainStyledAttributes(attrs, R.styleable.SimpleImageView);
                mDrawable = array.getDrawable(R.styleable.SimpleImageView_src);
                //测量Drawable对象的宽、高
                measureDrawable();
            }finally {
                if (array != null){
                    array.recycle();
                }
            }
        }
    }

    private void measureDrawable(){
        if (mDrawable == null){
            throw new RuntimeException("drawable 不能为空!");
        }
        mWidth = mDrawable.getIntrinsicWidth();
        mHeight = mDrawable.getIntrinsicHeight();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(mWidth,mHeight);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mDrawable == null){
            return;
        }

        //绘制图片
        canvas.drawBitmap(ImageUtils.drawableToBitmap(mDrawable),getLeft(),getTop(),mBitmapPaint);
    }
}

这里我们创建了一个继承自View的SimpleImageView类,在构造函数中我们会获取该控件的属性,并且初始化要绘制的图片和画笔。

initAttrs函数中,我们首先读取SimpleImageView的属性集TypedArray,再从该对象中读取SimpleImageView_src属性值,该属性是一个drawable的资源id值,然后我们根据这个id从该TypedArray对象中获取该id对应的drawable;最后measureDrawable函数测量该图片mDrawable的大小。

我们在SimpleImageView中定义了两个字段mWidthmHeight分别表示SimpleImageView视图的宽度和高度。从measureDrawable函数我们可以看出,我们通过在xml文件中指定资源id对应的drawable得到图片的高度和宽度,并把它们当做SimpleImageView的宽和高,也就是说图片有多大,SimpleImageView就有多大。当SimpleImageView被加载时,首先会调用onMeasure函数测量SimpleImageView的大小,然后再将图片绘制出来。

步骤三:使用SimpleImageView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:img="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <blog.donnie.demoprojects.ui.widget.SimpleImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        img:src="@mipmap/ic_launcher"
        />
        
    <!--
    在使用自定义属性时,我们需要将该属性所在的命名空间引入到xml中,
	命名空间实际上就是该工程的应用包名。因为自定义的属性集最终会编译为R类,
	R类的完整路径是应用的包名.R,格式如下:
    xmlns:名字="http://schemas.android.com/apk/res/应用包名"
    其中res-auto的效果是一样的,系统会自动帮我们导应用包名。
    -->

</LinearLayout>

运行示例代码,效果如下图:

img

总结

  1. 自定义属性,在values/attrs.xml中定义属性集;
  2. 继承自View创建自定义控件;
  3. 在代码中读取自定义属性,初始化视图;
  4. 测量视图大小;
  5. 绘制视图内容;
  6. 在xml中引入命名控件,设置属性。