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自定义控件之自定义组合控件(三),常言 ...
随机推荐
- token接口的测法
接口一般都有权限的校验,一般是需要登录后才可以调用 对于接口的认证,一般通过两种方式来实现1.校验用户请求中是否包含某项指定的cookie2.校验用户的请求的header中是否包含某项指定的字段(to ...
- Idea Live Templates
常用live templates 模板 注释 : * * @param $params$ * @return $return$ * $date$ $time$ chiyuanzhen743 */ lo ...
- C++ 学习笔记之——字符串和字符串流
1. 字符数组 字符数组,也就是存放字符类型数据的数组,只不过字符数组的结尾必须是 '\0'.C++ 已经提供了一些字符串处理函数,这些函数被封装在头文件 和 <string.h> 中. ...
- oracle数据库之PL/SQL 流程控制语句
介绍 PL/SQL 的流程控制语句, 包括如下三类: 1.控制语句: IF 语句 2.循环语句: LOOP 语句, EXIT 语句 3.顺序语句: GOTO 语句, NULL 语句 一 条件语句 IF ...
- Word Ladder Problem (DFS + BFS)
Given two words (beginWord and endWord), and a dictionary's word list, find the length of shortest t ...
- eg_2
2. 编写一个程序,输出在一个字符串中,指定的字符串出现的次数 第一种方法: public class Test { public static void main(String[] args) { ...
- 利用SqlServer的作业定时清除过期数据
有时候我们的数据库中可能会有那么些存放动态数据的表,比如一些每天定时发出的消息通知信息等数据.这些数据我们只需要临时保存,一些老旧的数据需要定时去清除掉,不然时间一长的话单表数据堆积非常严重.导致数据 ...
- 浅谈 Vue v-model指令的实现原理 - 如何利用v-model设计自定义的表单组件
原文请点击此链接 链接1 http://www.7zhang.com/index/cms/read/id/234515.html 链接2 http://blog.csdn.net/yangbing ...
- 细说匿名内部类引用方法局部变量时为什么需要声明为final
一.前言 在研究公司某个项目的源码,看到前人使用了挺多内部类,内部类平时我用的比较多的是匿名内部类,平时用的多的是匿名内部类,其他形式的用的比较少,然后我就有个疑惑:到底内部类是基于什么样的考虑,才让 ...
- dubbo+zk+maven的那点事
1.服务端建议使用xml方式进行服务暴露,可读性更高: 2.消费端不能直接引入service模块,而是通过引入service-api模块来使用服务端的服务,因为这不是单体应用: 题外话:dubbo是一 ...