前言

最近在做项目的时候,经常使用到上面图标带下面文字的显示方式,并不是多复杂的业务,但是在很多需求场景都会用到,因此直接做成了自定义View的方式,同时也总结一下对自定义View的个人浅见。

1.自定义View的意义和实现的方式

让很多常用的UI业务需求,封装成一个View来操作,总比不停的用布局来写方便的多。少用几个ctrl+c/ctrl+v,能有效加快编码和开发效率。

实现的方式本人常用的时是两种:
1.通过继承Layout布局(相对来说简单易实现)
2.通过继承View(需要对onMeasure和onDraw有一定的了解)

另外很重要的一点是自定义View和自定义属性的联合使用,这点在文章最后详解。

下面具体介绍两种方式的实现过程:

继承Layout布局

1.首先你要写一个布局文件,里面包括了一个ImageView展示图片,一个TextView展示文字

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

    <ImageView
        android:id="@+id/top_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"
        android:layout_centerInParent="true"
        android:layout_alignParentTop="true"/>

    <TextView
        android:id="@+id/bottom_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:layout_below="@+id/top_image"
        android:layout_marginTop="5dp"
        android:layout_centerInParent="true" />
</RelativeLayout>

2.写一个继承于对应Layout的自定义类,(注释解释的很清楚,就不过多表述)

public class ImageTextLayoutView extends RelativeLayout {

	private ImageView topImage;
	private TextView bottomText;

	/**
	 * 这个构造方法是在代码中new的时候调用的
	 * @param context
	 */
	public ImageTextLayoutView(Context context) {
		super(context);
	}

	/**
	 * 这个构造方法是在xml文件中初始化调用的
	 * @param context
	 * @param attrs			View的xml属性
	 */

	public ImageTextLayoutView(Context context, @Nullable AttributeSet attrs) {
		super(context, attrs);
		initView(context,attrs);
	}

	/**
	 * 这个方法不常用,有前两个足够了
	 * @param context
	 * @param attrs
	 * @param defStyleAttr		应用到View的主题风格(定义在主题中)
	 */
	public ImageTextLayoutView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
	}


	private void initView(Context context,AttributeSet attrs){
		//获取子控件
		LayoutInflater.from(context).inflate(R.layout.image_text_view_layout,this);
		topImage = findViewById(R.id.top_image);
		bottomText = findViewById(R.id.bottom_text);

		//获取xml中定义的所有属性
		TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.ImageTextLayoutView);

		//获取ImageView相关自定义属性并设置
		int image_width = typedArray.getDimensionPixelSize(R.styleable.ImageTextLayoutView_image_width,30);
		int image_height = typedArray.getDimensionPixelSize(R.styleable.ImageTextLayoutView_image_height,30);
		Drawable drawable = typedArray.getDrawable(R.styleable.ImageTextLayoutView_image_src);
		//topImage.setLayoutParams(new LayoutParams(image_width,image_height));
		drawable.setBounds(0,0,image_width,image_height);
		topImage.setImageDrawable(drawable);

		//获取TextView相关的自定义属性并设置
		String content = typedArray.getString(R.styleable.ImageTextLayoutView_text_content);
		float text_size = typedArray.getDimension(R.styleable.ImageTextLayoutView_text_size,12);
		int text_color = typedArray.getColor(R.styleable.ImageTextLayoutView_text_color, Color.BLACK);
		bottomText.setText(content);
		bottomText.setTextSize(text_size);
		bottomText.setTextColor(text_color);
	}
}

3.最后在xml文件引用

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <com.ctsaing.flyandroid.ImageTextLayoutView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:image_height="40dp"
        app:image_src="@mipmap/ic_launcher"
        app:image_width="40dp"
        app:text_color="#aaff00"
        app:text_size="8sp"
        app:text_content="自定义View"
        android:layout_marginTop="20dp"
        android:layout_marginLeft="20dp"/>

运行效果

Android 自定义view使用 android 自定义view三个方法_android

2.通过继承View的方式

你可以不懂其他任何关于View的方法,但是一定要懂得两个方法:
onMeasure()和onDraw(),这两个方法是实现的核心。
onMeasure():用来测量宽高,整个View的。
onDraw():在这里画内容
再加上画笔Paint,和画布canvas的一些常用属性,就可以画出各种我们想要的。
话不多说,开始上代码:
1.不需要布局,直接继承View

public class ImageTextCustomView extends View {

	private Drawable drawable;//画上方图片
	private String content;// 画下方文字

	//默认的View宽高
	private int viewWidth;
	private int viewHeight;

2.重写onMeasure()和onDraw()方法

@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);

		int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		int heightMoede = MeasureSpec.getMode(heightMeasureSpec);
		int widthSize = MeasureSpec.getSize(widthMeasureSpec);
		int heightSize = MeasureSpec.getSize(heightMeasureSpec);

		//获取到View的整个高度
		viewWidth = xmlImageWidth > textWidth ? xmlImageWidth : textWidth;
		viewHeight = xmlImageHeight + imageAndTextMargin + textHeight;
		viewWidth = Math.max(viewWidth, getSuggestedMinimumWidth());
		viewHeight = Math.max(viewHeight, getSuggestedMinimumHeight());
protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);

		//xml文件中image设置的宽高时,而实际要显示的图片宽高可能要大于设置值;因此要要对bitmap按比例缩放
		Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
		//获得bitmap的宽高
		int bitmapWidth = bitmap.getWidth();
		int bitmapHeight = bitmap.getHeight();
		//计算缩放比列
		float scaleWidth = ((float)xmlImageWidth)/bitmapWidth;
		float scaleHeight = ((float)xmlImageHeight)/bitmapHeight;
		//设置缩放的Matrix参数

Android 自定义view使用 android 自定义view三个方法_自定义_02


自此实现了继承View的方式。

(篇幅有限,不全部上代码了,需要的同学,可以上github下载):

项目地址

3.自定义属性

自定义布局和自定义属性是很难分开的,自定义属性的好处是我们可以再xml文件中直接使用。下面是实现过程:

1.在value文件夹中新建attrs.xml文件

Android 自定义view使用 android 自定义view三个方法_xml_03


2.在attrs文件中声明我们想要的自定义属性

<declare-styleable name="ImageTextCustomView">
        <attr name="imageWidth" format="dimension"/>
        <attr name="imageHeight" format="dimension"/>
        <attr name="imageSrc" format="reference"/>
        <attr name="textSize" format="float|dimension"/>
        <attr name="textColor" format="color|reference"/>
        <attr name="textWidth" format="dimension"/>
        <attr name="textHeight" format="dimension"/>
        <attr name="textContent" format="string"/>
        <attr name="imageAndTextMargin" format="dimension"/>

3.在我们的自定义文件中,通过typedArray取到我们在xml关于自定义View的属性值。

private void initView(Context context, AttributeSet attrs, int defStyleAttr) {
		//先获取xml中的属性值
		TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ImageTextCustomView);
		xmlImageWidth = typedArray.getDimensionPixelSize(R.styleable.ImageTextCustomView_imageWidth, defalutImageWidth);
		xmlImageHeight = typedArray.getDimensionPixelSize(R.styleable.ImageTextCustomView_imageHeight, defalutImageHeight);
		drawable = typedArray.getDrawable(R.styleable.ImageTextCustomView_imageSrc);
		xmlTextColor = typedArray.getColor(R.styleable.ImageTextCustomView_textColor, defalutTextColor);
		xmlTextSize = typedArray.getDimension(R.styleable.ImageTextCustomView_textSize, defalutTextSize);
		content = typedArray.getString(R.styleable.ImageTextCustomView_textContent);
		imageAndTextMargin = typedArray.getDimensionPixelSize(R.styleable.ImageTextCustomView_imageAndTextMargin, 0);
		textHeight = (int) xmlTextSize;
		textPaint.setColor(xmlTextColor);
		textPaint.setTextSize(xmlTextSize);
		textWidth = (int) Layout.getDesiredWidth(content,textPaint);

		typedArray.recycle();
	}

4。使用自定义属性的方式:

<com.ctsaing.flyandroid.ImageTextCustomView
        android:layout_marginTop="50dp"
        android:layout_marginLeft="50dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:imageSrc="@mipmap/ic_launcher"
        app:imageWidth="40dp"
        app:imageHeight="40dp"
        app:imageAndTextMargin="10dp"
        app:textSize="10sp"
        app:textColor="@android:color/holo_red_dark"
        app:textContent="另一个自定义view"/>

上面app: 后面的都是我们在attrs文件中声明的属性值,可以直接这样使用。

4.结语

本文讲述了关于自定义View的简单实现,主要是展示方面的,自定义view的内容还很多,之后再学习再交流。