自定义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(); } }
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories