Xamarin.Android 自定义 View

如有问题, 欢迎交流: [email protected]


Github: https://github.com/aixiaozi/CustomView/tree/master

Known Issue:

york:image="@drawable/Icon"

这个暂时不能实现,有空的话会去 fix


10/4/2018, fix this issue: https://www.jianshu.com/p/7ad527ed6daa


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中进行使用,不同的是我们自定义属性的命名空间是不同的,我们需要在布局的根节点或自定义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. 使用

Generic example:

namespace My.Namespace
{
    class MyCustomView : View
    {
    }
}

Is used in following way in .axml


Do mind case, namespace in .axml is lowercase, classname is same case as C# declaration.


Xamarin.Android 自定义 View_第1张图片

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

你可能感兴趣的:(xamarin)