Xamarin.Android 自定义 View

如有问题, 欢迎交流: 1092417123@qq.com


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 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. 使用

Generic example:

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

Is used in following way in .axml

<my.namespace.MyCustomView />

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

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

这里写图片描述

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值