在做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个参数都是整形是出于效率的考虑,所以经常要做的就是对其解码=>

  1. int specMode = MeasureSpec.getMode(measureSpec);
  2. int specSize = MeasureSpec.getSize(measureSpec);
  1. 依据specMode的值,(MeasureSpec有3种模式分别是UNSPECIFIED, EXACTLY和AT_MOST)
  2. 如果是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模式表示你没有指定大小。

  3. 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,使用裁剪还是滚动等技术。

  4. @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 自定义控件---圆形方向盘的更多相关文章

  1. Android自定义控件系列之应用篇——圆形进度条

    一.概述 在上一篇博文中,我们给大家介绍了Android自定义控件系列的基础篇.链接:http://www.cnblogs.com/jerehedu/p/4360066.html 这一篇博文中,我们将 ...

  2. Android自定义控件之自定义属性

    前言: 上篇介绍了自定义控件的基本要求以及绘制的基本原理,本篇文章主要介绍如何给自定义控件自定义一些属性.本篇文章将继续以上篇文章自定义圆形百分比为例进行讲解.有关原理知识请参考Android自定义控 ...

  3. Android自定义控件之基本原理

    前言: 在日常的Android开发中会经常和控件打交道,有时Android提供的控件未必能满足业务的需求,这个时候就需要我们实现自定义一些控件,今天先大致了解一下自定义控件的要求和实现的基本原理. 自 ...

  4. Android自定义控件之日历控件

      标签: android 控件 日历 应用 需求 2015年09月26日 22:21:54 25062人阅读 评论(109) 收藏 举报 分类: Android自定义控件系列(7) 版权声明:转载注 ...

  5. Android自定义控件之基本原理(一)

    前言: 在日常的Android开发中会经常和控件打交道,有时Android提供的控件未必能满足业务的需求,这个时候就需要我们实现自定义一些控件,今天先大致了解一下自定义控件的要求和实现的基本原理. 自 ...

  6. Android自定义控件之自定义属性(二)

    前言: 上篇介绍了自定义控件的基本要求以及绘制的基本原理,本篇文章主要介绍如何给自定义控件自定义一些属性.本篇文章将继续以上篇文章自定义圆形百分比为例进行讲解.有关原理知识请参考Android自定义控 ...

  7. Android自定义控件View(三)组合控件

    不少人应该见过小米手机系统音量控制UI,一个圆形带动画效果的音量加减UI,效果很好看.它是怎么实现的呢?这篇博客来揭开它的神秘面纱.先上效果图 相信很多人都知道Android自定义控件的三种方式,An ...

  8. Android自定义控件View(二)继承控件

    在前一篇博客中学习了Android自定义控件View的流程步骤和注意点,不了解的童鞋可以参考Android自定义控件View(一).这一节开始学习自定义控件View(二)之继承系统已有的控件.我们来自 ...

  9. Android自定义控件之自定义ViewGroup实现标签云

    前言: 前面几篇讲了自定义控件绘制原理Android自定义控件之基本原理(一),自定义属性Android自定义控件之自定义属性(二),自定义组合控件Android自定义控件之自定义组合控件(三),常言 ...

随机推荐

  1. Machine Learning笔记整理 ------ (五)决策树、随机森林

    1. 决策树 一般的,一棵决策树包含一个根结点.若干内部结点和若干叶子结点,叶子节点对应决策结果,其他每个结点对应一个属性测试,每个结点包含的样本集合根据属性测试结果被划分到子结点中,而根结点包含样本 ...

  2. 转战Java~

    记得16年5月份开始学的Java,当时就是为了学Hadoop才学的Java基础,之后Hadoop没学成,倒是学了Java Web的东西,当时就是玩玩,然后弄了个WeChat后台,就完事了.然后就又回到 ...

  3. struts2 result type属性说明

    首先看一下在struts-default.xml中对于result-type的定义: <result-types><result-type name="chain" ...

  4. Python3 数据类型-集合

    在Python中集合set是基本数据类型的一种,它有可变集合(set)和不可变集合(frozenset)两种.创建集合set.集合set添加.集合删除.交集.并集.差集的操作都是非常实用的方法. 集合 ...

  5. windows编程入门最重要的

    要入门 Windows 编程,最重要的不是阅读什么教材,使用什么工具,而是先必须把以下几个对于初学者来说非常容易困惑的重要概念搞清楚: 1. 文字的编码和字符集.这部分需要掌握 ANSI 模式和 Un ...

  6. Python—列表(一个“打了激素”的数组)

    我们在C语言中会使用数组来将一大堆数据类型一样的数据挨个摆在一起,但是数组有一个基本的要求,就是数据类型必须是一致的,我们知道Python的变量由于没有数据类型,也就是说Python没有数组这一概念, ...

  7. Thunder团队第三周 - Scrum会议4

    Scrum会议4 小组名称:Thunder 项目名称:i阅app Scrum Master:邹双黛 工作照片: 参会成员: 王航:http://www.cnblogs.com/wangh013/ 李传 ...

  8. lintcode-6-合并排序数组

    合并排序数组 合并两个排序的整数数组A和B变成一个新的数组. 样例 给出A=[1,2,3,4],B=[2,4,5,6],返回 [1,2,2,3,4,4,5,6] 挑战 你能否优化你的算法,如果其中一个 ...

  9. iOS奔溃日志信息统计使用笔记

    1.Bugly的集成很简单,直接一个pod就可以搞定 pod 'Bugly' 2.在官网上注册账号 3.初始化SDK 导入头文件 在工程的AppDelegate.m文件导入头文件 #import &l ...

  10. <Android>日期,时间选择对话框

    a)         调用Activity的onCreateDialog()方法创建对话框 b)        分别在OnDateSetListener的onDateSet()方法和OnTimeSet ...