Android自定义view之环形等待控件的实现

本篇博客参考鸿洋_大神的《Android 自定义View (三) 圆环交替 等待效果》


拖了这么久才开始更新csdn,着实是懒到家了,写这篇博客的目的就是为了帮助更多的android入门开发者更多的了解自定义控件,毕竟自定义控件对新手来说还是比较神秘的,多说无益,直接上图:



以上就是今天我们要实现的效果,乍一看是不是觉得高端大气上档次,完全没有什么头绪怎么去实现这么“高端”的东西。还会不定时的反问自己可以吗?对,你可以的。让我们一起来学习如何写这样的控件吧。


【前言】自定义view 的几个步骤

  1. 自定义view的属性
  2. 在view 的构造方法中获取我们自定义的属性的值
  3. 重写onMeasure方法(有时不需要重写这个方法)
  4. 重写onDraw方法

【正文】

项目结构图奉上:



我们先来分析一下这个控件。这个控件主要有两种颜色,加载的速度,圆环的宽度,好像也没有其他属性值了。

1.自定义属性:attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <attr name="firstColor" format="color"/>
    <attr name="secondColor" format="color"/>
    <attr name="circleWidth" format="dimension"/>
    <attr name="speed" format="integer"/>

    <declare-styleable name="CustomProgressbar">
        <attr name="firstColor"/>
        <attr name="secondColor"/>
        <attr name="circleWidth"/>
        <attr name="speed"/>
    </declare-styleable>

</resources>

这里的format主要常见的有以下几种属性:reference 、color、boolean、dimension、float、integer、string、fraction、enum、flag。更多用法以及如何在初始化时获取相应的值,可以百度,这里不是我们的重点。

2.在构造方法中获取我们自定义的属性:CustomProgressbar.java

	// 设置第一圈颜色
	private int mFirstColor=Color.GREEN;
	// 设置第二圈颜色
	private int mSecondColor=Color.RED;
	// 设置圈的宽度
	private int mCircleWidth=20;
	// 设置颜色填充画笔
	private Paint mPaint;
	// 设置当前进度
	private int mProgress;
	// 设置当前进度加载速度
	private int speed=20;
	// 是否开始下一个
	private boolean isNext = false;

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

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

	/**
	 * 必要的初始化,获取一些自定义的值
	 * 
	 * @param context
	 * @param attrs
	 * @param defStyle
	 */
	public CustomProgressbar(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		// 获取自定义的属性集合
		TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomProgressbar, defStyle, 0);
		// 获取自定义属性的个数
		int n = array.getIndexCount();
		Log.i("test", "自定义属性的个数:" + n);
		// 遍历属性值
		for (int i = 0; i < n; i++) {
			int attr = array.getIndex(i);
			Log.i("test", "自定义的属性为:"+attr);
			switch (attr) {
			case R.styleable.CustomProgressbar_firstColor:
				// 获取第一圈颜色值
				mFirstColor = array.getColor(attr, Color.GREEN);
				break;
			case R.styleable.CustomProgressbar_secondColor:
				// 获取第一圈颜色值
				mSecondColor = array.getColor(attr, Color.RED);
				break;
			case R.styleable.CustomProgressbar_circleWidth:
				// 设置默认圈的宽度为20px
				mCircleWidth = array.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 20, getResources().getDisplayMetrics()));
				break;
			case R.styleable.CustomProgressbar_speed:
				// 获取默认加载速度
				speed = array.getInt(attr, 20);
				break;
			}
		}
		// 回收
		array.recycle();
		mPaint = new Paint();
		// 绘图线程 此线程为耗时线程,放在子线程中执行,防止主线程的卡顿
		new Thread() {
			public void run() {
				while (true) {
					mProgress++;
					if (mProgress == 360) {
						mProgress = 0;
						// 如果没有开始下一个,则设置isNext为true
						if (!isNext) {
							isNext = true;
						} else {
							isNext = false;
						}
					}
					// 刷新UI
					// postInvalidate()此方法可以直接在UI线程调用,invalidate()则需要在handler中进行调用
					postInvalidate();
					try {
						Thread.sleep(speed);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}.start();
	}

很多童鞋肯定会问,为什么这个操作要放在thread里进行操作?重写view是一个耗时操作,所以我们这里就直接放在子线程里进行重绘了,防止程序卡死。还有一个需要注意的就是postInvalidate()方法。postInvalidate()此方法可以直接在UI线程调用,invalidate()则需要在handler中进行调用,想要了解更多的童鞋也可以自己百度两者之间的区别。


3.重写onMeasure方法:本例中没有用到重写onMeasure方法,此处就不贴代码了。

4.重写onDraw方法,进行view 的绘制:CustomProgressbar.java

@Override
	protected void onDraw(Canvas canvas) {
		// 获取圆心的x坐标
		int center = getWidth() / 2;
		// 获取圆的半径
		int radius = center - mCircleWidth / 2;
		// 设置填充的宽度
		mPaint.setStrokeWidth(mCircleWidth);
		mPaint.setAntiAlias(true);
		// 设置填充的style
		mPaint.setStyle(Paint.Style.STROKE);
		// new RectF(left, top, right, bottom) 为距离x轴,y轴之间的距离
		// 定义rect的形状
		RectF f = new RectF(center - radius, center - radius, center + radius, center + radius);
		if (!isNext) {
			// 第一圈颜色完整,第二圈颜色跑
			mPaint.setColor(mFirstColor);// 设置画笔颜色
			// 画出圆环
			canvas.drawCircle(center, center, radius, mPaint);
			// 设置圆环颜色
			mPaint.setColor(mSecondColor);
			/*
			 * public void drawArc(RectF oval, float startAngle, float sweepAngle,
			 * boolean useCenter, Paint paint) oval :指定圆弧的外轮廓矩形区域。 startAngle:
			 * 圆弧起始角度,单位为度。 sweepAngle: 圆弧扫过的角度,顺时针方向,单位为度。 useCenter:
			 * 如果为True时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形。 paint: 绘制圆弧的画板属性,如颜色,是否填充等。
			 */
			canvas.drawArc(f, -90, mProgress, false, mPaint);
		} else {
			// 第一圈颜色完整,第二圈颜色跑
			mPaint.setColor(mSecondColor);// 设置画笔颜色
			// 画出圆环
			canvas.drawCircle(center, center, radius, mPaint);
			// 设置圆环颜色
			mPaint.setColor(mFirstColor);
			/*
			 * public void drawArc(RectF oval, float startAngle, float sweepAngle,
			 * boolean useCenter, Paint paint) oval :指定圆弧的外轮廓矩形区域。 startAngle:
			 * 圆弧起始角度,单位为度。 sweepAngle: 圆弧扫过的角度,顺时针方向,单位为度。 useCenter:
			 * 如果为True时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形。 paint: 绘制圆弧的画板属性,如颜色,是否填充等。
			 */
			canvas.drawArc(f, -90, mProgress, false, mPaint);
		}
	}

这里关于drawArc的方法,我已经加了详细的注释,不明白的同学可以自己百度一下。


由于上面贴的是片段代码,这里给出CustomProgressbar.java的全部代码:

package com.beyole.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.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;

import com.beyole.circlewaitting.R;

public class CustomProgressbar extends View {

	// 设置第一圈颜色
	private int mFirstColor=Color.GREEN;
	// 设置第二圈颜色
	private int mSecondColor=Color.RED;
	// 设置圈的宽度
	private int mCircleWidth=20;
	// 设置颜色填充画笔
	private Paint mPaint;
	// 设置当前进度
	private int mProgress;
	// 设置当前进度加载速度
	private int speed=20;
	// 是否开始下一个
	private boolean isNext = false;

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

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

	/**
	 * 必要的初始化,获取一些自定义的值
	 * 
	 * @param context
	 * @param attrs
	 * @param defStyle
	 */
	public CustomProgressbar(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		// 获取自定义的属性集合
		TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomProgressbar, defStyle, 0);
		// 获取自定义属性的个数
		int n = array.getIndexCount();
		Log.i("test", "自定义属性的个数:" + n);
		// 遍历属性值
		for (int i = 0; i < n; i++) {
			int attr = array.getIndex(i);
			Log.i("test", "自定义的属性为:"+attr);
			switch (attr) {
			case R.styleable.CustomProgressbar_firstColor:
				// 获取第一圈颜色值
				mFirstColor = array.getColor(attr, Color.GREEN);
				break;
			case R.styleable.CustomProgressbar_secondColor:
				// 获取第一圈颜色值
				mSecondColor = array.getColor(attr, Color.RED);
				break;
			case R.styleable.CustomProgressbar_circleWidth:
				// 设置默认圈的宽度为20px
				mCircleWidth = array.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 20, getResources().getDisplayMetrics()));
				break;
			case R.styleable.CustomProgressbar_speed:
				// 获取默认加载速度
				speed = array.getInt(attr, 20);
				break;
			}
		}
		// 回收
		array.recycle();
		mPaint = new Paint();
		// 绘图线程 此线程为耗时线程,放在子线程中执行,防止主线程的卡顿
		new Thread() {
			public void run() {
				while (true) {
					mProgress++;
					if (mProgress == 360) {
						mProgress = 0;
						// 如果没有开始下一个,则设置isNext为true
						if (!isNext) {
							isNext = true;
						} else {
							isNext = false;
						}
					}
					// 刷新UI
					// postInvalidate()此方法可以直接在UI线程调用,invalidate()则需要在handler中进行调用
					postInvalidate();
					try {
						Thread.sleep(speed);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}.start();
	}

	@Override
	protected void onDraw(Canvas canvas) {
		// 获取圆心的x坐标
		int center = getWidth() / 2;
		// 获取圆的半径
		int radius = center - mCircleWidth / 2;
		// 设置填充的宽度
		mPaint.setStrokeWidth(mCircleWidth);
		mPaint.setAntiAlias(true);
		// 设置填充的style
		mPaint.setStyle(Paint.Style.STROKE);
		// new RectF(left, top, right, bottom) 为距离x轴,y轴之间的距离
		// 定义rect的形状
		RectF f = new RectF(center - radius, center - radius, center + radius, center + radius);
		if (!isNext) {
			// 第一圈颜色完整,第二圈颜色跑
			mPaint.setColor(mFirstColor);// 设置画笔颜色
			// 画出圆环
			canvas.drawCircle(center, center, radius, mPaint);
			// 设置圆环颜色
			mPaint.setColor(mSecondColor);
			/*
			 * public void drawArc(RectF oval, float startAngle, float sweepAngle,
			 * boolean useCenter, Paint paint) oval :指定圆弧的外轮廓矩形区域。 startAngle:
			 * 圆弧起始角度,单位为度。 sweepAngle: 圆弧扫过的角度,顺时针方向,单位为度。 useCenter:
			 * 如果为True时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形。 paint: 绘制圆弧的画板属性,如颜色,是否填充等。
			 */
			canvas.drawArc(f, -90, mProgress, false, mPaint);
		} else {
			// 第一圈颜色完整,第二圈颜色跑
			mPaint.setColor(mSecondColor);// 设置画笔颜色
			// 画出圆环
			canvas.drawCircle(center, center, radius, mPaint);
			// 设置圆环颜色
			mPaint.setColor(mFirstColor);
			/*
			 * public void drawArc(RectF oval, float startAngle, float sweepAngle,
			 * boolean useCenter, Paint paint) oval :指定圆弧的外轮廓矩形区域。 startAngle:
			 * 圆弧起始角度,单位为度。 sweepAngle: 圆弧扫过的角度,顺时针方向,单位为度。 useCenter:
			 * 如果为True时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形。 paint: 绘制圆弧的画板属性,如颜色,是否填充等。
			 */
			canvas.drawArc(f, -90, mProgress, false, mPaint);
		}
	}
}


自定义的控件已经定义结束,接下来就是如何调用我们写的view了,首先,在我们的主布局文件:activity_main.xml中进行引用:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:beyole="http://schemas.android.com/apk/res/com.beyole.circlewaitting"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.beyole.view.CustomProgressbar
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="20dp"
        beyole:circleWidth="20dp"
        beyole:firstColor="#D4F668"
        beyole:secondColor="#2F9DD2"
        beyole:speed="20" />

</RelativeLayout>

这里注意,我们定义了自己的命名控件,也就是
 xmlns:beyole="http://schemas.android.com/apk/res/com.beyole.circlewaitting"
这里的com.beyole.circlewaitting就是我们应用程序的包名。如何知道我们应用程序的包名?直接在AndroidManifest.xml文件中


主布局文件里面没有更改内容:MainActivity.java

package com.beyole.circlewaitting;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}
}


写到这里是不是不难了,其实代码也就这么一点点,只要多写多练,就一定会熟能生巧的。

github地址:https://github.com/xuejiawei/beyole_android_CircleWaitting

csdn源码下载地址:http://download.csdn.net/detail/xuejiawei123/9174395


  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值