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

自定义View入门

2016-02-24 22:31 525 查看

一、自定义View步骤

在res/values/目录下新建attrs.xml文件,在该文件中定义和我们自定义View相关的属性。

在构造函数中获取自定义的属性

重写onMeasure()方法获测量自定义View的大小

重写onDraw()方法绘制自定义View显示在界面上

二、下面一步一步的演示上面的步骤

先上效果图:

             


1、自定义属性

自定义属性时,declare-styleable 后的name可以任意定义,只是为了区分,默认情况下该name为自定义View的类名,当然,这里也最好使用类名,这样容易辨别。

<resources>
<declare-styleable name="CustomProgressBar">
<!--圆圈内的文本-->
<attr name="progressText" format="string"/>
<!--圆圈内文本大小-->
<attr name="progressTextSize" format="dimension"/>
<!--圆圈内文本颜色-->
<attr name="progressTextColor" format="color"/>
<!--圆圈底层颜色-->
<attr name="firstColor" format="color"/>
<!--圆圈第二层颜色-->
<attr name="secondColor" format="color"/>
<!--圆圈宽度-->
<attr name="progressWidth" format="float"/>
<!--圆圈的内径-->
<attr name="progressInnerRadius" format="float"/>
<!--最大进度-->
<attr name="max" format="integer"/>
<!--当前进度-->
<attr name="progress" format="integer"/>
</declare-styleable>
</resources>


2、获取自定义属性

在自定义View的构造函数中获取自定义属性,一定要有AttributeSet attrs的构造函数,因为获取自定义属性全靠它了,如果仅仅是一个只有context的构造函数是不能获取得到的,所以这里一定要注意了。

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

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

public CustomProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttributes(context, attrs, defStyleAttr);
mPaint = new Paint();
}
/**
* 初始化自定义属性
*
* @param context      上下文
* @param attrs        属性集合
* @param defStyleAttr 自定义属性样式
*/
private void initAttributes(Context context, AttributeSet attrs, int defStyleAttr) {
if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomProgressBar, defStyleAttr, 0);
try {
//第一层颜色
mFirstColor = a.getColor(R.styleable.CustomProgressBar_firstColor, mDefaultFirstColor);
//第二层颜色
mSecondColor = a.getColor(R.styleable.CustomProgressBar_secondColor, mDefaultSecondColor);
//圆圈内部显示的文本
mProgressText = a.getString(R.styleable.CustomProgressBar_progressText);
//圆圈宽度
mProgressWidth = a.getFloat(R.styleable.CustomProgressBar_progressWidth, mDefaultProgressWidth);
//圆圈内径
mProgressInnerRadius = a.getFloat(R.styleable.CustomProgressBar_progressInnerRadius, mDefaultInnerRadius);
//圆圈内文本颜色
mProgressTextColor = a.getColor(R.styleable.CustomProgressBar_progressTextColor, mDefaultProgressTextColor);
//圆圈内文本字体大小
mProgressTextSize = a.getDimension(R.styleable.CustomProgressBar_progressTextSize, mDefaultProgressTextSize);
//最大进度
max = a.getInt(R.styleable.CustomProgressBar_max, 0);
//当前进度
progress = a.getInt(R.styleable.CustomProgressBar_progress, 0);
} finally {
a.recycle();
}
}
}
4000
>

3、重写onMeasure()方法

其实这一步不是必须的,因为如果在xml文件中给定的layout_width和layout_height为确定的值的话,如固定的dp或者match_parent,那么onMeasure是不需要重写的,如果是wrap_content的话,默认为父容器剩余空间的最大值,除非是当没有给该View一个确定的大小的时候,重写该方法是有意义的,当用户使用该View时是用的是wrap_content时可以计算一个默认的大小出来。建议还是重写该方法,将多种情况都考虑进来。

    /**
* 测量组件的大小,设置的组件大小为wrapcontent的时候,组件的大小由半径决定
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//测量组件的宽度
if (widthMode == MeasureSpec.EXACTLY) {
//如果是精准测量,即在使用的时候给的大小为match_parent或者是一个固定大小
mWidth = widthSize;
} else {
int desire = (int) (mProgressInnerRadius * 2 //圆圈的内部直径
+ mProgressWidth * 2); // 圆圈的左边宽度和右边的宽度的和
if (widthMode == MeasureSpec.AT_MOST) { //wrap_content
mWidth = Math.min(desire, widthSize);
}
}

//测量组件的高度
if (heightMode == MeasureSpec.EXACTLY) {
mHeight = heightSize;
} else {
int desire = (int) ( mProgressInnerRadius * 2 //圆圈的内部直径
+ mProgressWidth * 2); // 圆圈的左边宽度和右边的宽度的和
if (heightMode == MeasureSpec.AT_MOST) { //wrap_content
mHeight = Math.min(desire, heightSize);
}
}

//保存测量的大小
setMeasuredDimension(mWidth, mHeight);
}


4、重写ondraw()方法

该方法是用来绘制View在界面上显示的,绘图当然和Canvas这个类密不可分了。后面会单独出一篇文章来写canvas这个类的用法。

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画第一层圆
int length = Math.min(mWidth,mHeight);
mPaint.setColor(mFirstColor);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mProgressWidth);
mPaint.setAntiAlias(true);
mPaint.setFilterBitmap(true);
canvas.drawCircle(mWidth / 2, mHeight / 2, length / 2 - mProgressWidth / 2, mPaint);

//画第二层圆弧
mPaint.setColor(mSecondColor);
RectF oval = new RectF();
oval.left = mWidth/2 - length/2 + mProgressWidth/2;
oval.top = mHeight/2 - length/2 + mProgressWidth/2;
oval.right = mWidth/2 + length/2 - mProgressWidth/2;
oval.bottom = mHeight/2 + length/2 - mProgressWidth/2;
int angle;
if (max <= 0 || progress < 0) {
angle = 0;
}else {
angle = (int) (progress / max * 360);
}
canvas.drawArc(oval, -90, angle, false, mPaint);

//画圆圈内的文本
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setColor(mProgressTextColor);
mPaint.setTextSize(mProgressTextSize);
mPaint.setStyle(Paint.Style.FILL);
if (!TextUtils.isEmpty(mProgressText)) {
//如果有文本
canvas.drawText(mProgressText, mWidth / 2, mHeight / 2 + mTextRect.height() / 2, mPaint);
}
}


在画圆弧的时候使用到类RectF这个类,该类表示一个方形,可以确定一个图形的范围,在android所有的图形都是在一个方形中确定的。

通过以上4个步骤,我们的这个自定义View就定义完成了,下面来演示如何使用。

三、使用自定义View

使用自定义View时和正常使用android内置View一样,但是值得注意的是这里需要写类的全名,即需要加上包名才可以,否则会报错。如果需要使用到自定义属性,那么就需要定义一个命名空间,不能使用android这个命名空间,因为这个是android自己定义的命名空间,我们自定义的属性没有在该命名空间中,需要重新定义一个命名空间。如何定义一个命名空间呢?复制”xmlns:android=”http://schemas.android.com/apk/res/android”这一个话,将xmln:android中的”android”修改为任意一个名称,不能是android,此处我修改为custom,这里custom就是我自定义的一个命名空间,当然还需要将末尾的res/android修改为res-auto。

<?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">

<com.taiyang.commonui.view.CustomProgressBar
android:id="@+id/progressbar"
android:layout_width="200dp"
android:layout_height="300dp"
android:layout_centerInParent="true"
custom:firstColor="#000088"
custom:max="100"
custom:progressTextColor="#567890"
custom:progressTextSize="30sp"
custom:progressWidth="40"
custom:secondColor="#ff0000"/>
</RelativeLayout>


以上使用我们自定义的View就完成了,后面也会逐步深入的讲解如何自定义View和自定义ViewGroup

下面将会附上该自定义View的完整源码

package com.taiyang.commonui.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

import com.taiyang.commonui.R;

/**
* Created by taiyang5946 on 2016/1/10.
* 这是一个圆形的进度条
*/
public class CustomProgressBar extends View {

private final String TAG = "CustomProgressBar";

/**圆圈第一层默认颜色*/
private int mDefaultFirstColor = Color.BLACK;
/**圆圈第二层默认颜色*/
private int mDefaultSecondColor = Color.CYAN;
/**圆圈内径默认半径为30,单位为:dp*/
private float mDefaultInnerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30,
getResources().getDisplayMetrics());
/** 圆圈默认宽度为4,单位为:dp*/
private float mDefaultProgressWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4,
getResources().getDisplayMetrics());
/**圆圈内文本默认颜色*/
private int mDefaultProgressTextColor = Color.BLUE;
/** 圆圈内文本默认字体大小*/
private float mDefaultProgressTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14,
getResources().getDisplayMetrics());
/**圆圈第一层颜色*/
private int mFirstColor;
/**圆圈第二层颜色*/
private int mSecondColor;
/**圆圈内部显示文字*/
private String mProgressText;
/**圆圈宽度*/
private float mProgressWidth;
/**圆圈内径*/
private float mProgressInnerRadius;
/**圆圈内文本的颜色*/
private int mProgressTextColor;
/**圆圈内文本的字体大小*/
private float mProgressTextSize;
/**该组件的宽度 */
private int mWidth;
/**该组件的高度*/
private int mHeight;
/**画笔*/
private Paint mPaint;
/**圆圈内的文本约束*/
private Rect mTextRect;
/**最大进度*/
private float max;
/**当前进度 */
private int progress;

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

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

public CustomProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttributes(context, attrs, defStyleAttr);
mPaint = new Paint();
}

/**
* 测量圆圈内文本的约束,即宽和高
*/
private void initProgressTextSize() {
//如果有文本就进行测量
if (!TextUtils.isEmpty(mProgressText)) {
mPaint.setTextSize(mProgressTextSize);
mTextRect = new Rect();
//文本的宽度约束就在textRect中
mPaint.getTextBounds(mProgressText, 0, mProgressText.length(), mTextRect);
}
}

/**
* 初始化自定义属性
*
* @param context      上下文
* @param attrs        属性集合
* @param defStyleAttr 自定义属性样式
*/
private void initAttributes(Context context, AttributeSet attrs, int defStyleAttr) {
if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomProgressBar, defStyleAttr, 0);
try {
//第一层颜色
mFirstColor = a.getColor(R.styleable.CustomProgressBar_firstColor, mDefaultFirstColor);
//第二层颜色
mSecondColor = a.getColor(R.styleable.CustomProgressBar_secondColor, mDefaultSecondColor);
//圆圈内部显示的文本
mProgressText = a.getString(R.styleable.CustomProgressBar_progressText);
//圆圈宽度
mProgressWidth = a.getFloat(R.styleable.CustomProgressBar_progressWidth, mDefaultProgressWidth);
//圆圈内径
mProgressInnerRadius = a.getFloat(R.styleable.CustomProgressBar_progressInnerRadius, mDefaultInnerRadius);
//圆圈内文本颜色
mProgressTextColor = a.getColor(R.styleable.CustomProgressBar_progressTextColor, mDefaultProgressTextColor);
//圆圈内文本字体大小
mProgressTextSize = a.getDimension(R.styleable.CustomProgressBar_progressTextSize, mDefaultProgressTextSize);
//最大进度
max = a.getInt(R.styleable.CustomProgressBar_max, 0);
//当前进度
progress = a.getInt(R.styleable.CustomProgressBar_progress, 0);
} finally {
a.recycle();
}
}
}

/**
* 测量组件的大小,设置的组件大小为wrapcontent的时候,组件的大小由半径决定
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//测量组件的宽度
if (widthMode == MeasureSpec.EXACTLY) {
//如果是精准测量,即在使用的时候给的大小为match_parent或者是一个固定大小
mWidth = widthSize;
} else {
int desire = (int) (mProgressInnerRadius * 2 //圆圈的内部直径
+ mProgressWidth * 2); // 圆圈的左边宽度和右边的宽度的和
if (widthMode == MeasureSpec.AT_MOST) { //wrap_content
mWidth = Math.min(desire, widthSize);
}
}

//测量组件的高度
if (heightMode == MeasureSpec.EXACTLY) {
mHeight = heightSize;
} else {
int desire = (int) ( mProgressInnerRadius * 2 //圆圈的内部直径
+ mProgressWidth * 2); // 圆圈的左边宽度和右边的宽度的和
if (heightMode == MeasureSpec.AT_MOST) { //wrap_content
mHeight = Math.min(desire, heightSize);
}
}

//保存测量的大小
setMeasuredDimension(mWidth, mHeight);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画第一层圆
int length = Math.min(mWidth,mHeight);
mPaint.setColor(mFirstColor);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mProgressWidth);
mPaint.setAntiAlias(true);
mPaint.setFilterBitmap(true);
canvas.drawCircle(mWidth / 2, mHeight / 2, length / 2 - mProgressWidth / 2, mPaint);

//画第二层圆弧
mPaint.setColor(mSecondColor);
RectF oval = new RectF();
oval.left = mWidth/2 - length/2 + mProgressWidth/2;
oval.top = mHeight/2 - length/2 + mProgressWidth/2;
oval.right = mWidth/2 + length/2 - mProgressWidth/2;
oval.bottom = mHeight/2 + length/2 - mProgressWidth/2;
int angle;
if (max <= 0 || progress < 0) {
angle = 0;
}else {
angle = (int) (progress / max * 360);
}
canvas.drawArc(oval, -90, angle, false, mPaint);

//画圆圈内的文本
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setColor(mProgressTextColor);
mPaint.setTextSize(mProgressTextSize);
mPaint.setStyle(Paint.Style.FILL);
if (!TextUtils.isEmpty(mProgressText)) {
//如果有文本
canvas.drawText(mProgressText, mWidth / 2, mHeight / 2 + mTextRect.height() / 2, mPaint);
/* if ((mTextRect.width() * mTextRect.width() + mTextRect.height() * mTextRect.height())
<  //勾股定理
length * length) {
//如果文本被约在圆圈内
canvas.drawText(mProgressText, mWidth / 2, mHeight / 2, mPaint);
} else {
TextPaint textPaint = new TextPaint();
String text = TextUtils.ellipsize(mProgressText,
textPaint,
mProgressInnerRadius * 2,
TextUtils.TruncateAt.MARQUEE).toString();
canvas.drawText(text, mWidth / 2, mHeight / 2, mPaint);
}*/
}
}

public String getProgressText() {
return mProgressText;
}

/**
* 设置圆圈内的文本
* @param progressText
*/
public void setProgressText(String progressText) {
mProgressText = progressText;
//测量圆圈内文本的大小,即宽和高
initProgressTextSize();
postInvalidate();
}

public void setMax(int max) {
this.max = max;
postInvalidate();
}

public void setProgress(int progress) {
this.progress = progress;
postInvalidate();
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android