自定义view详解

来源:互联网 发布:淘宝开店 如何面试店铺 编辑:程序博客网 时间:2024/06/15 15:22

建议先看官方文档,中文版点这里,说明非常全面。

自定义View一般步骤大概如下:

  1. 自定义view的属性
  2. 在View的构造方法中获得自定义的属性值,为绘图作准备
  3. 重写onMesure,获取view的大小
  4. 重写onDraw,在初始化时绘图以及view发生改变时的重绘。

1、自定义View的属性

首先在res/values/ 下建立一个attrs.xml , 在里面定义我们的属性和声明我们的整个样式。

<resources>
<declare-styleable name="PieChart">
<attr name="showText" format="boolean" />
<attr name="labelPosition" format="enum">
<enum name="left" value="0"/>
<enum name="right" value="1"/>
</attr>
</declare-styleable>
</resources>

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res/com.example.customviews">
 
<com.example.customviews.charting.PieChart
custom:showText="true"
custom:labelPosition="left" />
 
</LinearLayout>

包名一定要写全。

请注意,如果你的view是一个inner class,你必须指定这个view的outer class。同样的,如果PieChart有一个inner class叫做PieView。为了使用这个类中自设的属性,你应该使用com.example.customviews.charting.PieChart$PieView.

2.在View的构造方法中,获得我们的自定义的样式

必须提供一个能够获取Context和作为属性的AttributeSet对象的构造函数,获取属性。
当view从XML布局中创建了之后,XML标签中所有的属性都从资源包中读取出来并作为一个AttributeSet传递给view的构造函数。

尽管可以从AttributeSet中直接读取数值,可是这样做有些弊端:

  • 拥有属性的资源并没有经过解析
  • Styles并没有运用上

Android资源编译器帮你做了许多工作来使调用obtainStyledAttributes()更简单。对res目录里的每一个资源,自动生成的R.java文件定义了存放属性ID的数组和常量,常量用来索引数组中每个属性。你可以使用这些预先定义的常量来从TypedArray中读取属性。
将AttributeSet传递给obtainStyledAttributes()方法。这个方法传回了一个TypedArray数组,包含了已经解除引用和样式化的值。

public PieChart(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.PieChart,
0, 0);
 
try {
mShowText = a.getBoolean(R.styleable.PieChart_showText, false);
mTextPos = a.getInteger(R.styleable.PieChart_labelPosition, 0);
} finally {
a.recycle();
}
}

清注意TypedArray对象是一个共享资源,必须被在使用后进行回收

3.重写onMesure函数

这个方法的参数是View.MeasureSpec,它会告诉你的view的父控件的大小。那些值被包装成int类型,你可以使用静态方法来获取其中的信息。

MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。specMode一共有三种类型,如下所示:

  1. EXACTLY
    表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

  2. AT_MOST
    表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

  3. UNSPECIFIED
    表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Try for a width based on our minimum
int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
int width = resolveSizeAndState(minw, widthMeasureSpec, 1);
 
// Whatever the width ends up being, ask for a height that would let the pie get as big as it can
int minh = MeasureSpec.getSize(width) - (int)mTextWidth + getPaddingBottom() + getPaddingTop();
int height = resolveSizeAndState(MeasureSpec.getSize(width) - (int)mTextWidth, heightMeasureSpec, 0);
 
setMeasuredDimension(width , height);
}

另一个例子:

@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
{
mPaint.setTextSize(mTitleTextSize);
mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds);
float textWidth = mBounds.width();
int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
width = desired;
}
 
if (heightMode == MeasureSpec.EXACTLY)
{
height = heightSize;
} else
{
mPaint.setTextSize(mTitleTextSize);
mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds);
float textHeight = mBounds.height();
int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
height = desired;
}
setMeasuredDimension(width, height);
}

需要注意的是,在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是0。

4、重写onDraw函数

在你调用任何绘制方法之前,你需要创建一个Paint对象。
注意:刚开始就创建对象是一个重要的优化技巧。Views会被频繁的重新绘制,初始化许多绘制对象需要花费昂贵的代价。在onDraw方法里面创建绘制对象会严重影响到性能并使得你的UI显得卡顿。

  • 绘制文字使用drawText()。指定字体通过调用setTypeface(), 通过setColor()来设置文字颜色.
  • 绘制基本图形使用drawRect(), drawOval(), drawArc(). 通过setStyle()来指定形状是否需要filled, outlined.
  • 绘制一些复杂的图形,使用Path类. 通过给Path对象添加直线与曲线, 然后使用drawPath()来绘制图形. 和基本图形一样,paths也可以通过setStyle来设置是outlined, filled, both.
  • 通过创建LinearGradient对象来定义渐变。调用setShader()来使用LinearGradient。
  • 通过使用drawBitmap来绘制图片.
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
 
canvas.drawOval(
mShadowBounds,
mShadowPaint
);
 
canvas.drawText(mData.get(mCurrentItem).mLabel, mTextX, mTextY, mTextPaint);
 
for (int i = 0; i < mData.size(); ++i) {
Item it = mData.get(i);
mPiePaint.setShader(it.mShader);
canvas.drawArc(mBounds,
360 - it.mEndAngle,
it.mEndAngle - it.mStartAngle,
true, mPiePaint);
}
 
canvas.drawLine(mTextX, mPointerY, mPointerX, mPointerY, mTextPaint);
canvas.drawCircle(mPointerX, mPointerY, mPointerSize, mTextPaint);
}
0 0