首先感谢
http://blog.csdn.net/lmj623565791/article/details/24252901
http://blog.csdn.net/linghu_java/article/details/8532931
http://blog.csdn.net/xuwei527/article/details/11993103
文章的引导
首先是自定义属性文件:/res/values/attrs.xml
<resources>
<attr name="titleText" format="string"></attr> //公共的属性名称 (就算在不同的<span style="font-family: Arial, Helvetica, sans-serif;"><declare-styleable ....></...>中也不能有同名的属性定义)</span>
<!--
<attr name="titleTextColor" format="color">@android:color/black</attr>
<attr name="titleTextSize" format="dimension" >18sp</attr>
-->
<declare-styleable name="CustomTitleView">
<attr name="titleText" /> //包含的公共的属性定义
<attr name="titleTextColor" format="color" />
<attr name="titleTextSize" format="dimension" />
</declare-styleable>
<declare-styleable name="TitleLayout">
<attr name="borderWidth" format="dimension"></attr>
<attr name="borderColor" format="color"></attr>
<attr name="titleColor" format="color"></attr>
<attr name="titleText"></attr>
</declare-styleable>
</resources>
布局文件中要使用自定义属性时需要加 xmlns:custom="http://schemas.android.com/apk/res/应用包名“
自定义View
继承了View或者View的子类后,系统会提示需要实现三个构造方法中的一个,3个都实现,默认情况下(非用户new)会调用public TitleFrameLayout(Context context, AttributeSet attrs)这个构造方法,若要在某个构造方法中去读取自定义属性值,则其他的构造方法需要调用这个构造方法,一般含有自定义属性的,都是在含有2个或含有3个参数的构造方法中读取自定义属性值。
构造方法取值
public TitleFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
Log.d("view test", "new 2");
setWillNotDraw(false); //若继承的ViewGroup及其子类,则需要设置该方法,否则不会调用onDraw(...)
TypedArray arr = context.obtainStyledAttributes(attrs,
R.styleable.TitleLayout);//获得属性数组
titleText = arr.getString(R.styleable.TitleLayout_titleText);//根据attrs.xml中定义的属性组名和属性名获取相应的值
titleColor = arr.getColor(R.styleable.TitleLayout_titleColor,
Color.BLACK);//获取颜色值,第二个参数为默认
borderColor = arr.getColor(R.styleable.TitleLayout_borderColor,
Color.BLACK);
borderWidth = arr.getDimensionPixelSize(
R.styleable.TitleLayout_borderWidth, (int) TypedValue
.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 18,
getResources().getDisplayMetrics()));//TypedValue.applyDimension(....)方法会根据第二个参数<span style="white-space:pre"> </span>//所给定的值使用第一个参数(即dip,sp)转换,第三个参数(根<span style="white-space:pre"> </span>//据屏幕分辨率等)进行缩放,从而得到给定的值在该分辨率下所<span style="white-space:pre"> </span>//显示出来的实际值
arr.recycle();//完成读取值后,需要调用此方法
mPaint = new Paint();//这个对象可以管理之后绘图要用到的颜色、字体、字体大小等信息
titlePaint = new TextPaint();//专门用来处理文字绘制的Paint
titlePaint.setTextSize(TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 20, getResources()
.getDisplayMetrics()));//设置文本字体大小
titlePaint.density = getResources().getDisplayMetrics().density;//设置屏幕密度,用来后面计算文本所占的高宽面积
titlePaint.setColor(titleColor);//设置绘制颜色
}
@Override
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
FontMetrics fm = titlePaint.getFontMetrics();//得到文本的字体相关属性
Log.d("view test", fm.ascent + " a-d " + fm.descent);
float textHeight = fm.descent - fm.ascent;//文本高度
float textWidth = Layout.getDesiredWidth(titleText, titlePaint);//文本宽度
float textStartX = getWidth() * 0.2f;
float textEndX = textStartX + textWidth;
float startY = textHeight * 2 / 3 + 0.5f;
mPaint.setColor(borderColor);
canvas.drawLine(0, startY, textStartX - 1, startY, mPaint);//绘制一根直线,这里的x/y坐标为相对本控件的x/y坐标,即左上角坐标为(0,0)
canvas.drawLine(textEndX + 1, startY, getWidth() - 1, startY, mPaint);
canvas.drawLine(0, startY, 0, getHeight() - 1, mPaint);
canvas.drawLine(0, getHeight() - 1, getWidth() - 1, getHeight() - 1,
mPaint);
canvas.drawLine(getWidth() - 1, startY, getWidth() - 1,
getHeight() - 1, mPaint);
canvas.drawText(titleText, textStartX, textHeight, titlePaint);//绘制文本
}
另外,若是继承ViewGroup及其子类,也许还需要控制其中子视图的绘制位置,需要重写onLayout(...)方法
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
FontMetrics fm = titlePaint.getFontMetrics();
float textHeight = fm.descent - fm.ascent;
Log.d("view test", "height - " + textHeight);
layoutChildren(left, (int) (top + textHeight), right, bottom, true);
// super.onLayout(changed, left, (int) (top + textHeight), right,
// bottom);
Log.d("view test", "onlayout - " + changed + " - " + left + " - "
+ top + " - " + right + " - " + bottom);
}
void layoutChildren(int left, int top, int right, int bottom,
boolean forceLeftGravity) {//拷贝FrameLayout中的方法,主要用来绘制子视图
final int count = getChildCount();
final int parentLeft = 1;
final int parentRight = right - left - 1;
final int parentTop = top;
final int parentBottom = bottom - top - 1;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = Gravity.TOP | Gravity.START;
}
// final int layoutDirection =
// mMarginParams.getLayoutDirection();
// final int absoluteGravity =
// Gravity.getAbsoluteGravity(gravity,
// layoutDirection);
final int verticalGravity = gravity
& Gravity.VERTICAL_GRAVITY_MASK;
//
// switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
// case Gravity.CENTER_HORIZONTAL:
// childLeft = parentLeft + (parentRight - parentLeft - width)
// / 2 + lp.leftMargin - lp.rightMargin;
// break;
// case Gravity.RIGHT:
// if (!forceLeftGravity) {
// childLeft = parentRight - width - lp.rightMargin;
// break;
// }
// case Gravity.LEFT:
// default:
// childLeft = parentLeft + lp.leftMargin;
// }
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height)
/ 2 + lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
child.layout(parentLeft, childTop, parentLeft + width - 1,
childTop + height - 1);
}
}
}