Android本身会给我们提供一些基本的view控件,但是当我们要想做出绚丽的效果时,单靠这些基础的控件就显得力不从心了,就需要我们自定义view了,大部分开发者都会对自定义view有种恐惧感,确实,自定一个view有一定的难度,它需要我们对view事件分发体系 、view工作原理有一定了解,当然了,你对这些基础知识不怎么了解,你按照例子照搬照抄有时也能达到目的,但那可能是你自定义的view实现的功能逻辑比较简单,如果逻辑复杂的话,照猫画虎可就不是一种可取的方法了,所以建议不熟悉view事件分发体系 、view工作原理的开发者,最好还是多了解一下,这些原理看似底层,但是对于实际开发中处理遇到的问题会有很多帮助的。接下来我们就开始自定义view的入门级学习吧!
自定义view一般分为以下四种情况:
- 直接继承View
- 直接继承ViewGroup
- 继承特定的ViewGroup(如LinearLayout)
继承特定的View(如TextView,ImageView)
下面我们就针对这四种情况分别说说需要注意的问题吧!
① 直接继承View的自定义view,当你将view的宽或高设置为wrap_content的时候,其实相当于将其设置为match_parent(具体原理涉及到view的measure过程),我们需要手动的处理这种情况,给定一个默认值;padding属性将会失效,需要你手动处理,margin属性不会失效的,因为margin属性是由父view管理实现的。
②直接继承ViewGroup的自定义view,它跟上面的情况有同样的问题,额外的,它还需要对子view的margin和padding属性手动实现支持。
③继承特定的ViewGroup的自定义view,它与第二种方法相比,没有那些需要额外处理的问题。一般来说,继承特定的ViewGroup都能实现直接继承ViewGroup所实现的功能,只不过第二种方式更接近底层。我个人还是推荐这种方式,毕竟需要你处理的事情少了,出错的概率也会相应的降低。
④继承特定的View,也没有上面说的那些问题我们需要额外处理的问题,这种就更简单了,我们需要的就是重写view的方法,来完成我们自定义的功能逻辑。
下面我们拿一个具体的例子来说明一下,自定义一个圆形view,并支持自定义view的自定义属性:
circlr_view_attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleView">
<attr name="background_color" format="color" />
</declare-styleable>
</resources>
text.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal">
<com.zb.viewevent.CircleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
app:background_color="#ff0000" />
</LinearLayout>
CircleView:
package com.zb.viewevent;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
public class CircleView extends View {
private Context context;
private Paint paint;
private int backgroundColor;
public CircleView(Context context) {
super(context);
init(context);
}
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
backgroundColor = typedArray.getColor(R.styleable.CircleView_background_color, Color.parseColor("#000000"));
init(context);
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
backgroundColor = typedArray.getColor(R.styleable.CircleView_background_color, Color.parseColor("#000000"));
init(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//默认宽高值(px)
int defaultWidthSize = 200;
int defaultHeightSize = 200;
//MeasureSpec.AT_MOST 对应于xml文件中的wrap_content
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(defaultWidthSize, defaultHeightSize);
} else if (widthMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(defaultWidthSize, heightSize);
} else if (heightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSize, defaultHeightSize);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
int realWidth = getWidth() - paddingLeft - paddingRight;
int realHeight = getHeight() - paddingTop - paddingBottom;
//以最小值作为圆的半径
int r = Math.min(realWidth, realHeight);
canvas.drawCircle(paddingLeft + realWidth, paddingTop + realHeight, r, paint);
}
public void init(Context context) {
this.context = context;
paint = new Paint();
paint.setColor(backgroundColor);
}
}
一个支持wrap_content、padding属性和自定义属性的自定义view就完成了,虽然实现的功能逻辑简单,但是,我们要明白一点的就是,自定义view有无数种,只需要掌握了实现思路,那么问题就会迎刃而解了。
实现复杂的自定义还需走很长路,需要我们一步一步慢慢往前走。