android 自定义控件---圆形方向盘
在做Android平台开发的时候,经常会遇到安卓原生控件无法满足需求的情况,安卓允许开发者去继承已经存在的控件或者实现你自己的控件。
先来看一下效果图
采用直接集成View类,重写onDrow方法绘制。
下面附上主要代码。
1 新建一个类CircleView 继承自View
package com.lennon.view; import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* 自定义圆形的方向布局
*
* @author 樊列龙
* @since 2014-06-07
*/
public class CircleView extends View { private int circleWidth = 100; // 圆环直径
private int circleColor = Color.argb(150, 255, 0, 0);
private int innerCircleColor = Color.rgb(0, 150, 0);
private int backgroundColor = Color.rgb(255, 255, 255);
private Paint paint = new Paint();
int center = 0;
int innerRadius = 0;
private float innerCircleRadius = 0;
private float smallCircle = 10;
public Dir dir = Dir.UP; public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
} public CircleView(Context context) {
super(context); // paint = new Paint();
} public CircleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); } @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec); int measuredHeight = measureHeight(heightMeasureSpec);
int measuredWidth = measureWidth(widthMeasureSpec); setMeasuredDimension(measuredWidth, measuredHeight); center = getWidth() / 2;
innerRadius = (center - circleWidth / 2 - 10);// 圆环
innerCircleRadius = center / 3;
this.setOnTouchListener(onTouchListener);
} /**
* 测量宽度
*
* @param measureSpec
* @return
*/
private int measureWidth(int measureSpec) {
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec); int result = 0; if (specMode == MeasureSpec.AT_MOST) {
result = getWidth();
} else if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
}
return result;
} /**
* 测量高度
*
* @param measureSpec
* @return
*/
private int measureHeight(int measureSpec) { int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec); int result = 0; if (specMode == MeasureSpec.AT_MOST) { result = specSize;
} else if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
}
return result;
} /**
* 开始绘制
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas); initBackGround(canvas);
drawDirTriangle(canvas, dir); } /**
* 绘制方向小箭头
*
* @param canvas
*/
private void drawDirTriangle(Canvas canvas, Dir dir) {
paint.setColor(innerCircleColor);
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.FILL); switch (dir) {
case UP:
drawUpTriangle(canvas);
break;
case DOWN:
drawDownTriangle(canvas);
break;
case LEFT:
drawLeftTriangle(canvas);
break;
case RIGHT:
drawRightTriangle(canvas);
break;
case CENTER:
invalidate();
break;
default:
break;
} paint.setColor(backgroundColor); canvas.drawCircle(center, center, smallCircle, paint);
// canvas.drawText(text, center, center+40, paint); } /**
* 绘制向右的小箭头
*
* @param canvas
*/
private void drawRightTriangle(Canvas canvas) {
Path path = new Path();
path.moveTo(center, center);
double sqrt2 = innerCircleRadius / Math.sqrt(2);
double pow05 = innerCircleRadius * Math.sqrt(2);
path.lineTo((float) (center + sqrt2), (float) (center - sqrt2));
path.lineTo((float) (center + pow05), center);
path.lineTo((float) (center + sqrt2), (float) (center + sqrt2));
canvas.drawPath(path, paint);
paint.setColor(backgroundColor);
canvas.drawLine(center, center, center + innerCircleRadius, center, paint); drawOnclikColor(canvas, Dir.RIGHT);
} /**
* 绘制想左的小箭头
*
* @param canvas
*/
private void drawLeftTriangle(Canvas canvas) {
Path path = new Path();
path.moveTo(center, center);
double sqrt2 = innerCircleRadius / Math.sqrt(2);
double pow05 = innerCircleRadius * Math.sqrt(2);
path.lineTo((float) (center - sqrt2), (float) (center - sqrt2));
path.lineTo((float) (center - pow05), center);
path.lineTo((float) (center - sqrt2), (float) (center + sqrt2));
canvas.drawPath(path, paint); paint.setColor(backgroundColor);
canvas.drawLine(center, center, center - innerCircleRadius, center, paint); drawOnclikColor(canvas, Dir.LEFT); } /**
* 绘制向下的小箭头
*
* @param canvas
*/
private void drawDownTriangle(Canvas canvas) {
Path path = new Path();
path.moveTo(center, center);
double sqrt2 = innerCircleRadius / Math.sqrt(2);
double pow05 = innerCircleRadius * Math.sqrt(2);
path.lineTo((float) (center - sqrt2), (float) (center + sqrt2));
path.lineTo(center, (float) (center + pow05));
path.lineTo((float) (center + sqrt2), (float) (center + sqrt2));
canvas.drawPath(path, paint); paint.setColor(backgroundColor);
canvas.drawLine(center, center, center, center + innerCircleRadius, paint); drawOnclikColor(canvas, Dir.DOWN);
} /**
* 点击的时候绘制黑色的扇形
*
* @param canvas
* @param dir
*/
private void drawOnclikColor(Canvas canvas, Dir dir) {
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(100);
switch (dir) {
case UP:
canvas.drawArc(new RectF(center - innerRadius, center - innerRadius, center + innerRadius, center
+ innerRadius), 225, 90, false, paint);
break;
case DOWN:
canvas.drawArc(new RectF(center - innerRadius, center - innerRadius, center + innerRadius, center
+ innerRadius), 45, 90, false, paint);
break;
case LEFT:
canvas.drawArc(new RectF(center - innerRadius, center - innerRadius, center + innerRadius, center
+ innerRadius), 135, 90, false, paint);
break;
case RIGHT:
canvas.drawArc(new RectF(center - innerRadius, center - innerRadius, center + innerRadius, center
+ innerRadius), -45, 90, false, paint);
break; default:
break;
} paint.setStyle(Paint.Style.FILL);
} /**
* 绘制像向上的箭头
*
* @param canvas
*/
private void drawUpTriangle(Canvas canvas) {
Path path = new Path();
path.moveTo(center, center);
double sqrt2 = innerCircleRadius / Math.sqrt(2);
double pow05 = innerCircleRadius * Math.sqrt(2); path.lineTo((float) (center - sqrt2), (float) (center - sqrt2));
path.lineTo(center, (float) (center - pow05));
path.lineTo((float) (center + sqrt2), (float) (center - sqrt2));
canvas.drawPath(path, paint); paint.setColor(backgroundColor);
canvas.drawLine(center, center, center, center - innerCircleRadius, paint); drawOnclikColor(canvas, Dir.UP);
} /**
* 绘制基本的背景, 这包括了三个步骤:1.清空画布 2.绘制外圈的圆 3.绘制内圈的圆
*
* @param canvas
*/
private void initBackGround(Canvas canvas) {
clearCanvas(canvas);
drawBackCircle(canvas);
drawInnerCircle(canvas); } /**
* 绘制中心白色小圆
*
* @param canvas
*/
private void drawInnerCircle(Canvas canvas) {
paint.setColor(innerCircleColor);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(1);
canvas.drawCircle(center, center, innerCircleRadius, paint);
} /**
* 绘制背景的圆圈和隔线
*
* @param canvas
*/
private void drawBackCircle(Canvas canvas) {
paint.setColor(circleColor);
paint.setStrokeWidth(circleWidth);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(center, center, innerRadius, paint); // 绘制圆圈 paint.setColor(backgroundColor);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(4);
canvas.drawLine(center, center, 0, 0, paint);
canvas.drawLine(center, center, center * 2, 0, paint);
canvas.drawLine(center, center, 0, center * 2, paint);
canvas.drawLine(center, center, center * 2, center * 2, paint); } /**
* 清空画布
*
* @param canvas
*/
private void clearCanvas(Canvas canvas) {
canvas.drawColor(backgroundColor);
} OnTouchListener onTouchListener = new OnTouchListener() { @Override
public boolean onTouch(View view, MotionEvent event) {
Dir tmp = Dir.UNDEFINE;
if ((tmp = checkDir(event.getX(), event.getY())) != Dir.UNDEFINE) {
dir = tmp;
invalidate();
}
return true;
} /**
* 检测方向
*
* @param x
* @param y
* @return
*/
private Dir checkDir(float x, float y) {
Dir dir = Dir.UNDEFINE; if (Math.sqrt(Math.pow(y - center, 2) + Math.pow(x - center, 2)) < innerCircleRadius) {// 判断在中心圆圈内
dir = Dir.CENTER;
System.out.println("----中央");
} else if (y < x && y + x < 2 * center) {
dir = Dir.UP;
System.out.println("----向上");
} else if (y < x && y + x > 2 * center) {
dir = Dir.RIGHT;
System.out.println("----向右");
} else if (y > x && y + x < 2 * center) {
dir = Dir.LEFT;
System.out.println("----向左");
} else if (y > x && y + x > 2 * center) {
dir = Dir.DOWN;
System.out.println("----向下");
} return dir;
} }; /**
* 关于方向的枚举
*
* @author Administrator
*
*/
public enum Dir {
UP, DOWN, LEFT, RIGHT, CENTER, UNDEFINE
} }
2 在activity_main.xml中引用CircleView类
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical"> <com.lennon.view.CircleView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/cv" /> </LinearLayout> </RelativeLayout>
好了 可以直接运行处结果了!
下面对上述代码做一些说明:
主要的自定义控件的方法有:
1.有些基本功能原生控件都能提供,所以这个时候你只需要继承并对控件进行扩展。通过重写它的事件,onDraw,但是始终都保持都父类方法的调用。 2.组合控件 就是通过合并几个控件的功能来生成一个控件。 3.完完整整创建一个新的控件。
我们这里实现的是一个完全自定义的控件,通常是继承View或者SurfaceView ,View类提供一个Canvas(画布)和一系列的画的方法,还有Paint(画笔)。使用它们去创建一个自定义的UI。你可以重写事件,包括屏幕接触或者按键按下等等,用来提供与用户交互。
1.如果你不需要快速重画和3D图像的效果,那么让View作为父类提供一个轻量级的解决方案。
2.如若不然,就需要使用SurfaceView作为父类,这样你就可以提供一个后台线程去画和使用OPENGL去实现你的图像。这个就相对重量级了,如果你的视图需要经常更新,然后由需要显示比较复杂的图像信息(尤其是在游戏和3D可视化),SurfaceView将是更好的选择。
使用这这方式一般你需要重写2个方法:
1.onMeasure
什么是onMeasure?
下面转载一段文章:
View在屏幕上显示出来要先经过measure(计算)和layout(布局).
1、什么时候调用onMeasure方法?
当控件的父元素正要放置该控件时调用.父元素会问子控件一个问题,“你想要用多大地方啊?”,然后传入两个参数——widthMeasureSpec和heightMeasureSpec.这两个参数指明控件可获得的空间以及关于这个空间描述的元数据.
更好的方法是你传递View的高度和宽度到setMeasuredDimension方法里,这样可以直接告诉父控件,需要多大地方放置子控件.
widthMeasureSpec和heightMeasureSpec这2个参数都是整形是出于效率的考虑,所以经常要做的就是对其解码=>
- int specMode = MeasureSpec.getMode(measureSpec);
- int specSize = MeasureSpec.getSize(measureSpec);
- 依据specMode的值,(MeasureSpec有3种模式分别是UNSPECIFIED, EXACTLY和AT_MOST)
- 如果是AT_MOST,specSize 代表的是最大可获得的空间;
如果是EXACTLY,specSize 代表的是精确的尺寸;如果是UNSPECIFIED,对于控件尺寸来说,没有任何参考意义。
2、那么这些模式和我们平时设置的layout参数fill_parent, wrap_content有什么关系呢?
经过代码测试就知道,当我们设置width或height为fill_parent时,容器在布局时调用子 view的measure方法传入的模式是EXACTLY,因为子view会占据剩余容器的空间,所以它大小是确定的。
而当设置为 wrap_content时,容器传进去的是AT_MOST, 表示子view的大小最多是多少,这样子view会根据这个上限来设置自己的尺寸。当子view的大小设置为精确值时,容器传入的是EXACTLY, 而MeasureSpec的UNSPECIFIED模式表示你没有指定大小。
- View的onMeasure方法默认行为是当模式为UNSPECIFIED时,设置尺寸为mMinWidth(通常为0)或者背景drawable的最小尺寸,当模式为EXACTLY或者AT_MOST时,尺寸设置为传入的MeasureSpec的大小。
有个观念需要纠正的是,fill_parent应该是子view会占据剩下容器的空间,而不会覆盖前面已布局好的其他view空间,当然后面布局子 view就没有空间给分配了,所以fill_parent属性对布局顺序很重要。以前所想的是把所有容器的空间都占满了,难怪google在2.2版本里 把fill_parent的名字改为match_parent.在两种情况下,你必须绝对的处理这些限制。在一些情况下,它可能会返回超出这些限制的尺寸,在这种情况下,你可以让父元素选择如何对待超出的View,使用裁剪还是滚动等技术。
- @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measuredHeight = measureHeight(heightMeasureSpec);
int measuredWidth = measureWidth(widthMeasureSpec);setMeasuredDimension(measuredHeight, measuredWidth); // 记住这句可不能省。 }
private int measureHeight(int measureSpec) { int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec);
// Default size if no limits are specified. int result = 500;
if (specMode == MeasureSpec.AT_MOST) { // Calculate the ideal size of your // control within this maximum size. // If your control fills the available // space return the outer bound. result = specSize; } else if (specMode == MeasureSpec.EXACTLY) { // If your control can fit within these bounds return that value. result = specSize; } return result; }
private int measureWidth(int measureSpec) { // 代码基本类似measureHeight }
2 onDraw
使用Canvas进行图形的绘制
本文参考了:
1.http://my.oschina.net/wangjunhe/blog/99764
2.http://blog.csdn.net/ethan_xue/article/details/7313575
代码下载地址
http://download.csdn.net/detail/csulennon/7462169
android 自定义控件---圆形方向盘的更多相关文章
- Android自定义控件系列之应用篇——圆形进度条
一.概述 在上一篇博文中,我们给大家介绍了Android自定义控件系列的基础篇.链接:http://www.cnblogs.com/jerehedu/p/4360066.html 这一篇博文中,我们将 ...
- Android自定义控件之自定义属性
前言: 上篇介绍了自定义控件的基本要求以及绘制的基本原理,本篇文章主要介绍如何给自定义控件自定义一些属性.本篇文章将继续以上篇文章自定义圆形百分比为例进行讲解.有关原理知识请参考Android自定义控 ...
- Android自定义控件之基本原理
前言: 在日常的Android开发中会经常和控件打交道,有时Android提供的控件未必能满足业务的需求,这个时候就需要我们实现自定义一些控件,今天先大致了解一下自定义控件的要求和实现的基本原理. 自 ...
- Android自定义控件之日历控件
标签: android 控件 日历 应用 需求 2015年09月26日 22:21:54 25062人阅读 评论(109) 收藏 举报 分类: Android自定义控件系列(7) 版权声明:转载注 ...
- Android自定义控件之基本原理(一)
前言: 在日常的Android开发中会经常和控件打交道,有时Android提供的控件未必能满足业务的需求,这个时候就需要我们实现自定义一些控件,今天先大致了解一下自定义控件的要求和实现的基本原理. 自 ...
- Android自定义控件之自定义属性(二)
前言: 上篇介绍了自定义控件的基本要求以及绘制的基本原理,本篇文章主要介绍如何给自定义控件自定义一些属性.本篇文章将继续以上篇文章自定义圆形百分比为例进行讲解.有关原理知识请参考Android自定义控 ...
- Android自定义控件View(三)组合控件
不少人应该见过小米手机系统音量控制UI,一个圆形带动画效果的音量加减UI,效果很好看.它是怎么实现的呢?这篇博客来揭开它的神秘面纱.先上效果图 相信很多人都知道Android自定义控件的三种方式,An ...
- Android自定义控件View(二)继承控件
在前一篇博客中学习了Android自定义控件View的流程步骤和注意点,不了解的童鞋可以参考Android自定义控件View(一).这一节开始学习自定义控件View(二)之继承系统已有的控件.我们来自 ...
- Android自定义控件之自定义ViewGroup实现标签云
前言: 前面几篇讲了自定义控件绘制原理Android自定义控件之基本原理(一),自定义属性Android自定义控件之自定义属性(二),自定义组合控件Android自定义控件之自定义组合控件(三),常言 ...
随机推荐
- visionpro吧-百度贴吧
Halcon,Visionpro视频教程,观看下载地址:http://www.211code.com
- Matlab提速方法
1. 向量化. 尽量少用for循环. 2. 循环竖着走比横着走快. 3. 内置函数也有优化的空间 不少内置函数都有大量的error check.直接用profiler找出真正干活的.不少内置函数在网上 ...
- leetcode个人题解——#24 Swap Nodes in Pairs
因为不太熟悉链表操作,所以解决方法烦了点,空间时间多有冗余. 代码中l,r分别是每一组的需要交换的左右指针,temp是下一组的头指针,用于交换后链接:res是交换后的l指针,用于本组交换后尾指针在下一 ...
- 四、oracle 用户管理二
一.使用profile管理用户口令概述:profile是口令限制,资源限制的命令集合,当建立数据库时,oracle会自动建立名称为default的profile.当建立用户没有指定profile选项时 ...
- Rightmost Digit(最后一位数字)
Description Given a positive integer N, you should output the most right digit of N^N. Input The ...
- java多线程三之线程协作与通信实例
多线程的难点主要就是多线程通信协作这一块了,前面笔记二中提到了常见的同步方法,这里主要是进行实例学习了,今天总结了一下3个实例: 1.银行存款与提款多线程实现,使用Lock锁和条件Condition. ...
- object-oriented 第二次作业(2)
面向对象程序设计自学计划 由于我的英文实在是很差,所以我就没有去考虑看英文的课程视频.网络上的课程有很多,什么学校的也有,一开始我不知道该如何开始选择课程.感觉每个都还可以.后来在群里的看到别人推荐的 ...
- JDK版本Java SE、Java EE、Java ME的区别
想在win7 X64上搭建JAVA开发环境来着(只是尝试下),打开JAVA 官网下载JDK,发现好多版本懵了,百度了下找到这些版本的区别,故有了下文 1.JAVA SE Java2平台标准版(Java ...
- C# 4 中使用迭代器的等待任务
介绍 可能你已经阅读 C#5 关于 async 和 await 关键字以及它们如何帮助简化异步编程的,可惜的是在升级VS2010后短短两年时间,任然没有准备好升级到VS2012,在VS2010和C#4 ...
- VS2010中的sln,suo分别是什么含义
我们通过双击.sln加载出我们的工程. Visual Studio.NET采用两种文件类型(.sln和.suo)来存储特定于解决方案的设置,它们总称为解决方案文件.为解决方案资源管理器提供显示管理文件 ...