您的位置:首页 > 移动开发 > Android开发

Android 自定义View(一) 介绍和一个简单TextView显示

2016-03-03 19:41 525 查看
转载注明出处:/article/7603128.html

意义

首先知道自定义View的意义,就是Android自身提供的控件已经满足不了我们的需求的时候就会进行继承View来进行自定义View的代码编写,实现需求。

需求

定义需求,是何种,例如 下拉刷新上拉更多的列表 , 看具体展示内容,可继承 GridView 或者 ListView 。

步骤

一般来说,只需要了解最基本的自定义方法即可。

下面介绍几个最普遍的方法:

OnMeasure (测量控件宽高的方法。)

OnDraw (绘制控件内容的方法)

OnLayout (此方法继承ViewGroup用的较多,用于摆放Group里子view的位置)

正题

定义自定义字节属性,此属性是在布局文件xml里View的属性。

在View的构造方法中获取属性。

重写onMeasure方法

重写OnDraw方法

这四个步骤是最常见的步骤。

来看一段ImageView的onMeasure方法。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
resolveUri();
int w;
int h;

// Desired aspect ratio of the view's contents (not including padding)
float desiredAspect = 0.0f;

// We are allowed to change the view's width
boolean resizeWidth = false;

// We are allowed to change the view's height
boolean resizeHeight = false;

final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

if (mDrawable == null) {
// If no drawable, its intrinsic size is 0.
mDrawableWidth = -1;
mDrawableHeight = -1;
w = h = 0;
} else {
w = mDrawableWidth;
h = mDrawableHeight;
if (w <= 0) w = 1;
if (h <= 0) h = 1;

// We are supposed to adjust view bounds to match the aspect
// ratio of our drawable. See if that is possible.
if (mAdjustViewBounds) {
resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;

desiredAspect = (float) w / (float) h;
}
}

int pleft = mPaddingLeft;
int pright = mPaddingRight;
int ptop = mPaddingTop;
int pbottom = mPaddingBottom;

int widthSize;
int heightSize;

if (resizeWidth || resizeHeight) {
/* If we get here, it means we want to resize to match the
drawables aspect ratio, and we have the freedom to change at
least one dimension.
*/

// Get the max possible width given our constraints
widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);

// Get the max possible height given our constraints
heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);

if (desiredAspect != 0.0f) {
// See what our actual aspect ratio is
float actualAspect = (float)(widthSize - pleft - pright) /
(heightSize - ptop - pbottom);

if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {

boolean done = false;

// Try adjusting width to be proportional to height
if (resizeWidth) {
int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +
pleft + pright;

// Allow the width to outgrow its original estimate if height is fixed.
if (!resizeHeight && !mAdjustViewBoundsCompat) {
widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);
}

if (newWidth <= widthSize) {
widthSize = newWidth;
done = true;
}
}

// Try adjusting height to be proportional to width
if (!done && resizeHeight) {
int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
ptop + pbottom;

// Allow the height to outgrow its original estimate if width is fixed.
if (!resizeWidth && !mAdjustViewBoundsCompat) {
heightSize = resolveAdjustedSize(newHeight, mMaxHeight,
heightMeasureSpec);
}

if (newHeight <= heightSize) {
heightSize = newHeight;
}
}
}
}
} else {
/* We are either don't want to preserve the drawables aspect ratio,
or we are not allowed to change view dimensions. Just measure in
the normal way.
*/
w += pleft + pright;
h += ptop + pbottom;

w = Math.max(w, getSuggestedMinimumWidth());
h = Math.max(h, getSuggestedMinimumHeight());

widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
}

setMeasuredDimension(widthSize, heightSize);
}


里面两种情况,判断width和height的mode是否为 MeasureSpec.EXACTLY,当width 和 height 其中一个为MeasureSpec.EXACTLY的时候进入情况一也就是
if (resizeWidth || resizeHeight)
如果不是的情况下,进入情况2
else
段。最后将得到的width 和 height 值使用 默认方法
setMeasuredDimension
传入值。

自定义属性值

在values 文件夹里的 任一xml的
<resources>
里加上

declare-styleable
标签。记得加上属性
name


然后就可以在里面定义属于你自己的自定义属性值

example:

<attr name="customsText" format="string" />
<attr name="customsSize" format="dimension" />

<declare-styleable name="FirstView">
<attr name="customsText"  />
<attr name="customsColor" format="color" />
<attr name="customsSize"  />
</declare-styleable>


关于declare-styleable里的 attr标签里的属性 format可以参考

declare-styleable中format详解 里的内容;

注意事项

关于自定义View在布局xml里的应用

跟你的工具有关,

在布局xml里最外层的控件都会有这个属性值
xmlns:android="http://schemas.android.com/apk/res/android"
这个属性值就是获取Android本身自带的xml属性值。
xmlns:android
这个android就是属性值开头例如:
android:width
等。

正常添加自己定义的属性值都是
xmlns:xxxx="http://schemas.android.com/apk/res/包名


如eclipse 如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/viewdemo.cifer.com.myview"
android:layout_width="match_parent"
android:layout_height="match_parent">
<viewdemo.cifer.com.myview.view.FirstView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
custom:customsText="3712"
android:padding="10dp"
custom:customsColor="#ff0000"
android:layout_centerInParent="true"
custom:customsSize="40sp" />
</RelativeLayout>


不过在Android Studio里这样写,会导致程序crash。需要将获取自定义属性的地址改成
xmlns:custom="http://schemas.android.com/apk/res-auto"
即可,所有的自定义属性都会出来再
custom:
里。

example

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<viewdemo.cifer.com.myview.view.FirstView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
custom:customsText="3712"
android:padding="10dp"
custom:customsColor="#ff0000"
android:layout_centerInParent="true"
custom:customsSize="40sp" />
</RelativeLayout>


构造方法

这里没什么说的,唯一需要注意的是构造方法的前两个的引用不是
super
而是
this
;

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

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

public FirstView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.FirstView, defStyleAttr, 0);
//在这里有两种获取方法,我都列出来,我自己用的第二种,可以方便控制是否有此属性,进行获取.注:构造方法只会调用一次。
int n = typedArray.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.FirstView_customsText:

break;
case R.styleable.FirstView_customsColor:
// 默认颜色设置为黑色

break;
case R.styleable.FirstView_customsSize:
// 默认设置为16sp,TypeValue也可以把sp转化为px

break;

}
}

try {
mTitleText = typedArray.getString(R.styleable.FirstView_customsText);
mTitleTextColor = typedArray.getColor(R.styleable.FirstView_customsColor, Color.BLACK);
mTitleTextSize = typedArray.getDimensionPixelSize(R.styleable.FirstView_customsSize, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
} catch (Exception e) {
e.printStackTrace();
} finally {
typedArray.recycle();
}

/**
* 获得绘制文本的宽和高
*/
mPaint = new Paint();
mPaint.setTextSize(mTitleTextSize);
mPaint.setColor(mTitleTextColor);
mBound = new Rect();
mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
}


注意一下,在构造方法里获得的属性值是布局文件xml里,可以在View类里写入方法修改如

public void setText(String text){
this.mTitleText = text;
postInvalidate();//invalidate();
}


关于View类自带方法的
postInvalidate() 和 invalidate()
的区别可以参考android中Invalidate和postInvalidate的区别

重写onMeasure方法

上面亮出了ImageView的onMeasure方法,觉得很复杂,正常来说一般不过几十行代码,就是计算内容的width和height值,与系统默认的width 和 height值,进行比较,根据布局文件xml里
width/height
的设置取最小值or最大值。

onMeasure获取到他的MeasureSpec的模式。具体的内容可以查看MeasureSpec介绍 的内容。

看一下简单的重写onMeasure方法

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = 0;
int height = 0;

/**
* 设置宽度
*/
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
switch (specMode) {
case MeasureSpec.EXACTLY:// 明确指定了
width = getPaddingLeft() + getPaddingRight() + specSize;
break;
case MeasureSpec.AT_MOST:// 一般为WARP_CONTENT
width = getPaddingLeft() + getPaddingRight() + mBound.width();
break;
}

/**
* 设置高度
*/
specMode = MeasureSpec.getMode(heightMeasureSpec);
specSize = MeasureSpec.getSize(heightMeasureSpec);
switch (specMode) {
case MeasureSpec.EXACTLY:// 明确指定了
height = getPaddingTop() + getPaddingBottom() + specSize;
break;
case MeasureSpec.AT_MOST:// 一般为WARP_CONTENT
height = getPaddingTop() + getPaddingBottom() + mBound.height();
break;
}

setMeasuredDimension(width, height);
}


onDraw

这里较为重要,因为界面的呈现效果,和界面美化,动画全在这里做,也是更新最频繁的方法。

简单的onDraw方法

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(Color.BLACK);
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);

mPaint.setColor(mTitleTextColor);
canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
}


更多方法

onFinishInflate() 当View中所有的子控件 均被映射成xml后触发

onSizeChanged(int, int, int, int)   改变大小时调用。 参数分别为 width ,height , oldWidth , oldHeight

onTrackballEvent(MotionEvent)   轨迹球事件

onTouchEvent(MotionEvent)   触屏事件

onFocusChanged(boolean, int, android.graphics.Rect)  当View获取 或失去焦点时触发

onWindowFocusChanged(boolean)    当view被附着到一个窗口时触发

onDetachedFromWindow() 当view离开附着的窗口时触发,该方法和  onAttachedToWindow() 是相反的。

onWindowVisibilityChanged(int) 当窗口中包含的可见的view发生变化时触发

以及常见的
onKeyDown(int, KeyEvent)

onKeyUp(int, KeyEvent)


更多资料参考

Android.View绘制流程 来自 - WPJY

Android View绘制流程及invalidate()等方法分析 来自- qinjuning

Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析 来自 - 老罗(罗升阳)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: