Xamarin.Android 自定义 View

来源:互联网 发布:java线程安全集合类 编辑:程序博客网 时间:2024/06/03 21:54

1. 重要步骤

  • 绘图,通过重写OnDraw方法控制View的渲染效果

  • 交互,重写OnTouchEvent方法实现与用户的交互

  • 测量,重写OnMeasure测量控件显示位置

  • 属性,attrs.xml中自定义控件的属性,通过TypedArray读取属性

  • 保存状态,避免配置改变时丢失View的状态,重写OnSaveInstanceState和OnRestoreInstanceState方法保存、恢复状态

接下来通过一个例子详细的介绍一下如何自定义View,实现一个图片➕文字说明的控件

2. 构造方法

class TitleImageView:View{    public TitleImageView (Context context) : this (context, null)    {    }    public TitleImageView (Context context, IAttributeSet attrs) : this (context, attrs, 0)    {    }    public TitleImageView (Context context, IAttributeSet attrs, int defStyle) : base (context, attrs, defStyle)    {    }}

3.通过 Xml 自定义 View 属性

Resources/values下新建attrs.xml文件,在attrs中定义属性和声明样式。确定我们能在xml中定义的属性,然后写如下定义:

<?xml version="1.0" encoding="utf-8"?>  <resources>      <attr name="titleText" format="string" />      <attr name="titleTextSize" format="dimension" />      <attr name="titleTextColor" format="color" />      <attr name="image" format="reference" />      <attr name="imageScaleType">          <enum name="fillXY" value="0" />          <enum name="center" value="1" />      </attr>      <declare-styleable name="TitleImageView">          <attr name="titleText" />          <attr name="titleTextSize" />          <attr name="titleTextColor" />          <attr name="image" />          <attr name="imageScaleType" />      </declare-styleable>  </resources>

另外一种写法:

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="TitleImageView">        <attr name="titleText" format="string" />        <attr name="titleTextSize" format="dimension" />        <attr name="titleTextColor" format="color" />        <attr name="image" format="reference" />        <attr name="imageScaleType">            <enum name="fillXY" value="0" />            <enum name="center" value="1" />        </attr>    </declare-styleable></resources>

定义了自定义属性,我们就可以在xml中进行使用,不同的是我们自定义属性的命名空间是不同的,我们需要在布局的根节点或自定义View中加上定义命名空间才能使用自定义属性

xmlns:app="http://schemas.android.com/apk/res-auto"

format是指该属性的取值类型:string,color,demension,integer,enum,reference,float,boolean,fraction,flag

具体介绍参考:http://www.jb51.net/article/40069.htm

4.借助TypedArray类提取我们定义的属性,编写类属性可以通过代码设置View属性

class TitleImageView:View{    private Bitmap image;    private ImageScale imageScaleType;    private string titleText;    private Color titleTextColor;    private int titleTextSize;    public Bitmap Image {        get {             return image;        }        set {             image = value;            Invalidate ();            RequestLayout ();        }    }    public ImageScale ImageScaleType {        get {             return imageScaleType;        }        set {             imageScaleType = value;            Invalidate ();        }    }    public string TitleText {        get{             return titleText;        }        set{             titleText = value;            Invalidate ();            RequestLayout ();        }    }    public Color TitleTextColor {        get {             return titleTextColor;        }        set {             titleTextColor = value;            Invalidate ();        }    }    public int TitleTextSize {        get{             return titleTextSize;        }        set{             titleTextSize = value;            Invalidate ();            RequestLayout ();        }    }    public enum ImageScale    {        FillXY,        Center    }    public TitleImageView (Context context) : this (context, null)    {    }    public TitleImageView (Context context, IAttributeSet attrs) : this (context, attrs, 0)    {    }    public TitleImageView (Context context, IAttributeSet attrs, int defStyle) : base (context, attrs, defStyle)    {        TypedArray typedArray = Context.Theme.ObtainStyledAttributes (attrs, Resource.Styleable.TitleImageView, defStyle, 0);        int count = typedArray.IndexCount;        try {            for (int i = 0; i < count; i++) {                int index = typedArray.GetIndex (i);                switch (index) {                case Resource.Styleable.TitleImageView_image:                    image = BitmapFactory.DecodeResource (Resources, typedArray.GetResourceId (index, 0));                    break;                case Resource.Styleable.TitleImageView_imageScaleType:                    imageScaleType = (ImageScale)typedArray.GetInt (index, 0);                    break;                case Resource.Styleable.TitleImageView_titleText:                    titleText = typedArray.GetString (index);                    break;                case Resource.Styleable.TitleImageView_titleTextColor:                    titleTextColor = typedArray.GetColor (index, Color.Black);                    break;                case Resource.Styleable.TitleImageView_titleTextSize:                    //获取尺寸三个方法的介绍:http://my.oschina.net/ldhy/blog/496420                    titleTextSize = typedArray.GetDimensionPixelSize (index, (int)TypedValue.ApplyDimension (ComplexUnitType.Sp, 16, Resources.DisplayMetrics));                    break;                default:                    break;                }            }        } catch (System.Exception ex) {            throw ex;        } finally {            typedArray.Recycle ();        }    }}

代码中View的属性发生改变时我们需要进行重绘和重新布局。所以在属性赋值时调用了Invalidate(重新绘制OnDraw)和RequestLayout(重新布局OnLayout)方法.

5. 计算视图宽高

重写OnMeasure方法,按照用户定义的宽度高度进行绘制,View会先做一次测量,计算出自己占用多大的面积

protected override void OnMeasure (int widthMeasureSpec, int heightMeasureSpec){    base.OnMeasure (widthMeasureSpec, heightMeasureSpec);    //计算宽度 以图片宽度作控件宽度    int minWidth = PaddingLeft + PaddingRight + image.Width;    var width = ResolveSizeAndState (minWidth, widthMeasureSpec, 0);    //计算高度    int minHeight = PaddingBottom + PaddingTop + image.Height + textBound.Height ();    var height = ResolveSizeAndState (minHeight, heightMeasureSpec, 0);    // 测量完成后必须调用setMeasuredDimension方法    SetMeasuredDimension (width, height);}

ResolveSizeAndState方法返回一个合适的尺寸,只要将测量模式和我们计算的宽度高度传进去即可,该方法在新的api中才有,无法兼容3.0以下,我们可以根据源码定义自己的ResolveSizeAndState方法:

private int ResolveSizeAndState(int size, int measureSpec, int childMeasuredState) {    int result = size;    int specMode = MeasureSpec.GetMode(measureSpec);    int specSize =  MeasureSpec.GetSize(measureSpec);    switch (specMode) {    case MeasureSpecMode.Unspecified:        result = size;        break;    case MeasureSpecMode.AtMost:        if (specSize < size) {            result = specSize | View.MeasuredStateTooSmall;        } else {            result = size;        }        break;    case MeasureSpecMode.Exactly:        result = specSize;        break;    }    return result | (childMeasuredState&View.MeasuredStateMask);}

6. 初始化画笔

在构造函数中初始化一个Paint和两个Rect

rect = new Rect (); //图片位置paint = new Paint ();paint.TextSize = TitleTextSize;paint.Color = titleTextColor;textBound = new Rect ();//底部说明文字位置  // 计算了描绘字体需要的范围  paint.GetTextBounds (titleText, 0, titleText.Length, textBound);

7. OnDraw

重写OnDraw方法,根据定义的属性绘制图形。在参数canvas上绘制我们希望的View样式

protected override void OnDraw (Canvas canvas){    base.OnDraw (canvas);    rect.Left = PaddingLeft;      rect.Right = Width - PaddingRight;      rect.Top = PaddingTop;      rect.Bottom = Height - PaddingBottom;     paint.TextSize = TitleTextSize;    paint.Color = titleTextColor;    paint.SetStyle (Paint.Style.Fill);    //当前设置的宽度小于字体需要的宽度,将字体改为xxx...     if (textBound.Width () > Width) {        TextPaint paint = new TextPaint (this.paint);          string msg = TextUtils.Ellipsize (titleText, paint, (float)Width - PaddingLeft - PaddingRight, TextUtils.TruncateAt.End);          canvas.DrawText (msg, PaddingLeft, Height - PaddingBottom, paint);      } else {        canvas.DrawText (titleText, Width / 2 - textBound.Width () / 2, Height - PaddingBottom, paint);    }    //取消使用掉的部分      rect.Bottom -= textBound.Height ();    if (imageScaleType == ImageScale.FillXY) {        canvas.DrawBitmap (image, null, rect, paint);    } else {        rect.Left = Width / 2 - image.Width / 2;        rect.Right = Width / 2 + image.Width / 2;        rect.Top = (Height - textBound.Height ()) / 2 - image.Height / 2;          rect.Bottom = (Height - textBound.Height ()) / 2 + image.Height / 2;          canvas.DrawBitmap (image, null, rect, paint);     }}

8. 使用

<view.TitleImageView      xmlns:app="http://schemas.android.com/apk/res-auto"   android:layout_width="100dp"     android:layout_height="200dp"     android:layout_margin="10dp"     android:padding="10dp"     myue:image="@mipmap/icon"     myue:imageScaleType="fillXY"     myue:titleText="hello andorid ! "     myue:titleTextColor="#ff0000"     myue:titleTextSize="30sp" />

这里写图片描述

参考链接:http://www.xamarin.xyz/2016/05/26/custom-view/

原创粉丝点击