Android 自定义控件(一)

在开发过程中经常遇到自定义控件,打算自己一边写着博客一边总结一下自定义View 的过程,以便能更好的提高。

新建一个自定义View,继承View,实现父类的构造方法.

public class MyFirstView extends View {

    public MyFirstView(Context context) {
        this(context,null);
    }

    public MyFirstView(Context context, AttributeSet attrs) {
        this(context,attrs,0);
    }

    public MyFirstView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

}

注意更改直接引用的构造方法,以下是三个构造函数使用时机。

①.在代码中直接new一个MyFirstView实例的时候,会调用第一个构造函数.这个没有任何争议.
②.在xml布局文件中调用MyFirstView的时候,会调用第二个构造函数.这个也没有争议.
③.在xml布局文件中调用MyFirstView,并且MyFirstView标签中还有自定义属性时,这里调用的还是第二个构造函数.
也就是说,系统默认只会调用Custom View的前两个构造函数,至于第三个构造函数的调用,通常是我们自己在构造函数中主动调用的(例如,在第二个构造函数中调用第三个构造函数).


自定义View的属性,首先在res/values/  下找到attrs.xml (如果没有可以创建一个), 在里面定义我们的属性和声明我们的整个样式。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyFirstView">
        <attr name="text" format="string" />
        <attr name="textSize" format="dimension"/>
        <attr name="textColor" format="color"/>
    </declare-styleable>

</resources>

然后就是定义属性值了,通过<attr name="textColor" format="color" /> 方式定义属性值,属性名字同样也要起的见名知意,format表示这个属性的值的类型,类型有以下几种:
  reference:引用资源        string:字符串        Color:颜色         boolean:布尔值         dimension:尺寸值        float:浮点型        integer:整型      fraction:百分数             enum:枚举类型            flag:位或运算  


然后在布局中声明我们的自定义View

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:first="http://schemas.android.com/apk/res-auto"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:gravity="center"
              android:layout_height="match_parent">
    <com.shsany.practice.MyFirstView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        first:text="2048"
        first:textSize="20sp"
        android:padding="10dp"
        first:textColor="#000000"/>

</LinearLayout>

其中 xmlns:first="http://schemas.android.com/apk/res-auto" 是引入自己的命名空间,也可以写成xmlns:first="http://schemas.android.com/apk/res/com.shsany.practice"的命名空间,后面的包路径指的是项目的package。

在上面的第三个构造方法里获取MyFirstView 的属性。

public MyFirstView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyFirstView, defStyleAttr, 0);
    if (typedArray != null) {
        mText = typedArray.getString(R.styleable.MyFirstView_text);
        mTextColor = typedArray.getColor(R.styleable.MyFirstView_textColor, Color.RED);
        // 默认设置为16sp        int x = typedArray.getDimensionPixelSize(R.styleable.MyFirstView_textSize, 16);
        mTextSize = DisplayUtil.sptopx(context, (float) x);
    }
    typedArray.recycle();
    /**  绘制文本的宽长  */
    mPaint = new Paint();
    mPaint.setTextSize(mTextSize);
    mBound = new Rect();
    mPaint.getTextBounds(mText, 0, mText.length(), mBound);
}
然后再重写onDraw()方法绘制控件。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (mPaint != null) {
        /**   设置圆形的颜色为红色  */
        mPaint.setColor(Color.RED);
        canvas.drawCircle(getWidth()/2f, getWidth()/2f, getWidth()/2f, mPaint);
        /**   设置文本的颜色 及 位置   这里的x y值是文本左下角点的x y */
        mPaint.setColor(mTextColor);
        canvas.drawText(mText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
    }

}
此时的效果是


系统帮我们测量的高度和宽度都是MATCH_PARNET,当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果,当我们设置为WRAP_CONTENT,或者MATCH_PARENT系统帮我们测量的结果就是MATCH_PARENT的长度。所以,当设置了WRAP_CONTENT时,我们需要自己进行测量,即重写onMesure()方法,
重写之前先了解MeasureSpec的specMode,一共三种类型:
EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
UNSPECIFIED:表示子布局想要多大就多大,很少使用
下面是我们重写onMeasure代码:

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

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    int width;
    int height;
    if (widthMode == MeasureSpec.EXACTLY) {
        /**   如果规定了具体的数值直接等于 */
        width = widthSize;
    } else {
        /**    如果没规定具体的数值,就根据当前文字的宽度加上内边距 就是这个控件的宽度 */
        float textWidth = mBound.width();
        int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
        width = desired;
    }

    if (heightMode == MeasureSpec.EXACTLY) {
        /**   如果规定了具体的数值直接等于 */
        height = heightSize;
    } else {
        /**    如果没规定具体的数值,就根据当前文字的高度加上内边距 就是这个控件的高度 */
        float textHeight = mBound.height();
        int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
        height = desired;
    }
    /**   判断宽高那个大   那个大听那个  */
    if(width>height){
        height=width;
    }else {
        width=height;
    }

    /**
     * 最后调用父类方法,View的大小告诉父布局。
     */
    setMeasuredDimension(width, height);
}
此时运行的结果是


这样,我们的第一个控件已经实现了,下面我们来总结一下自定义View 的步骤:

 1.先自定义一个view,重写其构造方法。

 2.自定义view 的属性,在垢找方法

 3.重写onMeasure(int,int)方法。(该方法可重写可不重写,具体看需求)

 4.重写onDraw(Canvas canvas)方法。

 


猜你喜欢

转载自blog.csdn.net/javasxl/article/details/70254867