自定义控件入门学习之Helloworld

       从今天开始就要学习自定义控件的使用了,以后看到什么酷炫的效果都会试着写一写实现的,不然还是要先学会基础的使用才行,作为一个新手第一步肯定是照着别人的敲了,所以先找了一篇相对简单的自定义控件的实现的文章,http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2013/0308/994.html 

    首先在res/values下面建立一个attrs.xml文件

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

蛋疼的eclipse对于declare-styleable,attr没有代码提示,开始还以为敲错了,这是自定义textview的一篇文章,我们先设置三个变量text,textColor,textSize,后面的format是属性,总共有10种之多,用的比较多的 reference(引用系统资源),color(颜色),boolean(true或false),dimension(sp,dp之类的),integer(整数),string(字符串),float(浮点数),fraction(百分比),enum(枚举),flag(位或运算)

  接下来就建立一个类继承view,并实现构造方法,分为1、2、3, 3种构造方法,这里用到了attribute属性,我们实现其中两种,当然3个都写也没问题。在构造函数中我们获取属性,并设置给画笔,调用onMeasure和onDraw方法。

public class LabelView extends View {

	private Paint mPaint;
	private String mText;
	private int mAscent;

	public LabelView(Context context) {
		super(context, null);
	}

	public LabelView(Context context, AttributeSet attrs) {
		super(context, attrs);
		mPaint = new Paint();
		mPaint.setAntiAlias(true);
		setPadding(10, 10, 10, 10);
		TypedArray array = context.obtainStyledAttributes(attrs,
				R.styleable.LabelView);
		CharSequence s = array.getString(R.styleable.LabelView_text);
		if (s != null) {
			mText = s.toString();
		}

		int color = array.getColor(R.styleable.LabelView_textColor, 0xFF000000);
		mPaint.setColor(color);

		int textSize = array.getDimensionPixelOffset(R.styleable.LabelView_textSize, 0);
		if (textSize > 0) {
			mPaint.setTextSize(textSize);
		}
		requestLayout();
		invalidate();
		array.recycle();

	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		setMeasuredDimension(measureWidth(widthMeasureSpec),
				measureHeight(heightMeasureSpec));
	}

	private int measureWidth(int measureSpec) {
		int result = 0;
		int specMode = MeasureSpec.getMode(measureSpec);
		int specSize = MeasureSpec.getSize(measureSpec);
		if (specMode == MeasureSpec.EXACTLY) {
			result = specSize;
		} else {
			result = (int) (mPaint.measureText(mText) + getPaddingLeft() + getPaddingRight());
			if (specMode == MeasureSpec.AT_MOST) {
				result = Math.min(result, specSize);
			}
		}
		return result;
	}

	private int measureHeight(int measureSpec) {
		int result = 0;
		int specMode = MeasureSpec.getMode(measureSpec);
		int specSize = MeasureSpec.getSize(measureSpec);

		mAscent = (int) mPaint.ascent();
		if (specMode == MeasureSpec.EXACTLY) {
			result = specSize;
		} else {
			result = (int) (-mAscent + mPaint.descent()) + getPaddingTop()
					+ getPaddingBottom();
			if (specMode == MeasureSpec.AT_MOST) {
				result = Math.min(result, specSize);
			}
		}
		Log.i("---onmeasure",
				"ascent:" + mAscent + " descent:" + mPaint.descent());
		return result;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		canvas.drawText(mText, getPaddingLeft(), getPaddingTop() - mAscent,
				mPaint);
		Log.i("---ondraw", "left:" + getPaddingLeft() + " top:"
				+ getPaddingTop());
	}

}


在构造函数中初始化画笔,并将设置好的属性赋给画笔,其中的R.styleable.LabelView_text是系统自己将declare-styleable的name加上下面text的属性name拼接起来,为了区分各个attr属性。

    画笔对象mPaint的setTextSize中的参数是px,而不是sp,但是我们有方法getDimensionPixelOffset,其实还有另外两个方法getDimension,getDimensionPixelSize,区别就是getDimensionPixelOffset和getDimension一个返回int,一个返回float,并且在dp,sp时会乘以density,在px时不会乘,而getDimensionPixelSize无论什么情况都会乘以density,然后返回一个int值。

    继续往下走有两个方法requestLayout和invalidate,前者会导致调用onMeasure和onLayout方法,但不会onDraw,后者用来刷新View,更新UI,显示新的绘制的界面。


    然后我们看到一行代码array.recycle(); 那这行代码是干什么的呢?我也没有弄懂,在stackoverflow上有一个回答

http://stackoverflow.com/questions/13805502/why-should-a-typedarray-be-recycled

上面说recycle的源码是

/**
 * Give back a previously retrieved StyledAttributes, for later re-use.
 */
public void recycle() {
    synchronized (mResources.mTmpValue) {
        TypedArray cached = mResources.mCachedStyledAttributes;
        if (cached == null || cached.mData.length < mData.length) {
            mXml = null;
            mResources.mCachedStyledAttributes = this;
        }
    }
}


</pre><span style="font-size:14px;">看代码是有cache缓存的,但是我自己打开源码</span><p><pre name="code" class="java">public void recycle() {
        if (mRecycled) {
            throw new RuntimeException(toString() + " recycled twice!");
        }

        mRecycled = true;

        // These may have been set by the client.
        mXml = null;
        mTheme = null;

        mResources.mTypedArrayPool.release(this);
    }


这个先放着吧,以后知道了再回来补充。

onMeasure中使用了一个方法setMeasuredDimension,这个方法至关重要,看super中的方法

  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

也是默认的使用了这个方法,设置宽和高,如果没有这个方法就会报错。

再看一下下面的两个方法,一个是ascent,一个是descent,在网上找了一张下面的图


首先有一个基线baseLine,而ascent是红线到绿线的距离,是一个负数,descent是红线到下面的黄线的距离,是正数。

measureHeight中的result,就是ascent的绝对值加上descent加上 上和下的padding就是高度,宽度就是measureText()加上左右的padding,measureText()返回的是text的宽度,下面是源码

/**
     * Return the width of the text.
     *
     * @param text  The text to measure. Cannot be null.
     * @return      The width of the text
     */
    public float measureText(String text) {
        if (text == null) {
            throw new IllegalArgumentException("text cannot be null");
        }

        if (text.length() == 0) {
            return 0f;
        }

        if (!mHasCompatScaling) {
            return (float) Math.ceil(native_measureText(text, mBidiFlags));
        }
        final float oldSize = getTextSize();
        setTextSize(oldSize*mCompatScaling);
        float w = native_measureText(text, mBidiFlags);
        setTextSize(oldSize);
        return (float) Math.ceil(w*mInvCompatScaling);
    }

设置了高度和宽度后,在onDraw中进行绘制,canvas.drawText()是从text的左上角开始绘制的,第二个参数是x,第三个参数是左上角的y

然后在布局中就可以调用了

首先加入命名空间

 xmlns:app="http://schemas.android.com/apk/res/com.example.customview"

app是名字,com.examlpe.customview是包名

  <com.example.customview.LabelView
        android:id="@+id/label1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#000000"
        android:layout_centerInParent="true"
        app:text="helloworld"
        app:textColor="#FF0000"
        app:textSize="26sp" />

运行效果如下








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值