Android custom View AirConditionerView hacking
package com.example.arc.view; import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.widget.Toast; /**
* Android custom View AirConditionerView hacking
*
* 声明:
* 本人在知乎看到Android非常漂亮的自定义View文章后,因为对其绘图部分运作机制好
* 奇,于是叫程梦真帮忙一起分析其中代码运作机制,花了两个半小时才将其整体hacking完。
*
* 2016-1-1 深圳 南山平山村 曾剑锋
*
*
* 一、程序来源:
* 1. 这种ui谁能给点思路?
* https://m.zhihu.com/question/38598212#answer-26400956
* 2. github:mutexliu/ZhihuAnswer
* https://github.com/mutexliu/ZhihuAnswer
*
* 二、参考文档:
* 1. 为什么安卓android变量命名多以小写"m"开头 ?
* http://www.01yun.com/mobile_development_question/20130303/194172.html
* 2. 自定义View之onMeasure()
* http://blog.csdn.net/pi9nc/article/details/18764863
* 3. MeasureSpec介绍及使用详解
* http://www.cnblogs.com/slider/archive/2011/11/28/2266538.html
* 4. SweepGradient扫描渲染
* http://blog.csdn.net/q445697127/article/details/7867506
* 5. SweepGradient
* http://developer.android.com/reference/android/graphics/SweepGradient.html
* 6. 覆写onLayout进行layout,含自定义ViewGroup例子
* http://blog.csdn.net/androiddevelop/article/details/8108970
* 7. View.MeasureSpec
* http://developer.android.com/reference/android/view/View.MeasureSpec.html#getMode(int)
*
* 三、绘图的三个步骤:
* 1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec);
* 2. protected void onLayout(boolean changed, int left, int top, int right, int bottom);
* View中不需要实现。
* 3. protected void onDraw(Canvas canvas);
*
*/
public class AirConditionerView extends View {
// 设置当前的温度
// 这个值只能在16-28度之间,下面的private void checkTemperature(float t)方法中有限制
private float mTemperature = 24f;
// 画圆的paint
private Paint mArcPaint;
// 画上线的paint
private Paint mLinePaint;
// 写字的paint
private Paint mTextPaint; // 这里是将控件宽度分为600份,mMinSize代表其中一份
private float mMinSize;
// 设置空心边框的宽度,其实就是圆弧的宽度
private float mGapWidth;
// 内圆半径
private float mInnerRadius;
// 外圆半径
private float mRadius;
// 中线点
private float mCenter; // 圆弧矩形
private RectF mArcRect;
// 渐变渲染
private SweepGradient mSweepGradient; // 接下来的三个构造函数是固定的写法,最后调用自己定义的的init方法
public AirConditionerView(Context context) {
super(context, null);
} public AirConditionerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
} public AirConditionerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
} private void init(){
// Paint.Style.FILL : 填充内部
// Paint.Style.FILL_AND_STROKE : 填充内部和描边
// Paint.Style.STROKE : 仅描边 // 画圆弧的笔
mArcPaint = new Paint();
mArcPaint.setStyle(Paint.Style.STROKE);
// 设置抗锯齿
mArcPaint.setAntiAlias(true); // 画圆弧上的线的笔
mLinePaint = new Paint();
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setAntiAlias(true);
// 设置笔的颜色
mLinePaint.setColor(0xffdddddd); // 写字的笔
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(0xff64646f);
} private void initSize(){
//寬度
mGapWidth = 56*mMinSize;
//内圆半径
mInnerRadius = 180*mMinSize;
mCenter = 300*mMinSize;
//外圆半径
mRadius = 208*mMinSize; // 包含圆弧的矩形
mArcRect = new RectF(mCenter-mRadius, mCenter-mRadius, mCenter+mRadius, mCenter+mRadius); // 设置渐变色、渐变位置
/**
* A subclass of Shader that draws a sweep gradient around a center point.
*
* Parameters
* cx The x-coordinate of the center
* cy The y-coordinate of the center
* colors The colors to be distributed between around the center. There must be at least 2 colors in the array.
* positions May be NULL. The relative position of each corresponding color in the colors array, beginning with 0 and ending with 1.0. If the values are not monotonic, the drawing may produce unexpected results. If positions is NULL, then the colors are automatically spaced evenly.
*/
int[] colors = {
0xFFE5BD7D,0xFFFAAA64,
0xFFFFFFFF, 0xFF6AE2FD,
0xFF8CD0E5, 0xFFA3CBCB,
0xFFBDC7B3, 0xFFD1C299,
0xFFE5BD7D,
};
float[] positions = {0,1f/8,2f/8,3f/8,4f/8,5f/8,6f/8,7f/8,1};
mSweepGradient = new SweepGradient(mCenter, mCenter, colors , positions);
} @Override
protected void onDraw(Canvas canvas) {
/***************** 变色弧形部分 ************************/
// draw arc 設置空心邊框的寬度
// Set the width for stroking. Pass 0 to stroke in hairline mode. Hairlines always draws a single pixel independent of the canva's matrix.
mArcPaint.setStrokeWidth(mGapWidth);
int gapDegree = getDegree();
// 绘制梯度渐变
// Set or clear the shader object.
mArcPaint.setShader(mSweepGradient);
//画渐变色弧形
// Draw the specified arc, which will be scaled to fit inside the specified oval.
// If the start angle is negative or >= 360, the start angle is treated as start angle modulo 360.
// If the sweep angle is >= 360, then the oval is drawn completely. Note that this differs slightly from SkPath::arcTo, which treats the sweep angle modulo 360. If the sweep angle is negative, the sweep angle is treated as sweep angle modulo 360
// The arc is drawn clockwise. An angle of 0 degrees correspond to the geometric angle of 0 degrees (3 o'clock on a watch.)
// Parameters
// oval The bounds of oval used to define the shape and size of the arc
// startAngle Starting angle (in degrees) where the arc begins
// sweepAngle Sweep angle (in degrees) measured clockwise
// useCenter If true, include the center of the oval in the arc, and close it if it is being stroked. This will draw a wedge
// paint The paint used to draw the arc
canvas.drawArc(mArcRect, -225, gapDegree + 225, false, mArcPaint); /***************** 白色弧形部分 ************************/
mArcPaint.setShader(null);
mArcPaint.setColor(Color.WHITE); //画渐变色弧形
canvas.drawArc(mArcRect, gapDegree, 45 - gapDegree, false, mArcPaint); // draw line
/***************** 画线部分 ************************/
mLinePaint.setStrokeWidth(mMinSize*1.5f);
// 将圆等分成120份,每份占360度的3度
for(int i = 0; i<120; i++){
// (75-45)*3 = 30*3 = 90,不绘直线部分占90度,正好符合空白区
if(i<=45 || i >= 75){
float top = mCenter-mInnerRadius-mGapWidth;
// 2度分成15格
if(i%15 == 0){
top = top - 20*mMinSize;
}
// 绘制垂直线
canvas.drawLine(mCenter, mCenter - mInnerRadius, mCenter, top, mLinePaint);
}
/**
* Preconcat the current matrix with the specified rotation.
*
* Parameters
* degrees The amount to rotate, in degrees
* px The x-coord for the pivot point (unchanged by the rotation)
* py The y-coord for the pivot point (unchanged by the rotation)
*
* 坐标系旋转3度,不是已经绘制的图形旋转3度
*/
canvas.rotate(3,mCenter,mCenter);
} // draw text
/***************** 弧形外部显示温度度数文字 部分 ************************/
mTextPaint.setTextSize(mMinSize*30);
mTextPaint.setTextAlign(Paint.Align.CENTER);
for(int i = 16; i<29; i+=2){
/**
* 计算文字在圆弧边缘的位置,用到三角函数计算。
*/
float r = mInnerRadius+mGapWidth + 40*mMinSize;
float x = (float) (mCenter + r*Math.cos((26-i)/2*Math.PI/4));
float y = (float) (mCenter - r*Math.sin((26-i)/2*Math.PI/4));
canvas.drawText(""+i, x, y - ((mTextPaint.descent() + mTextPaint.ascent()) / 2), mTextPaint);
}
} private int getDegree(){
checkTemperature(mTemperature);
return -225 + (int)((mTemperature-16)/12*90+0.5f)*3;
}
/***************** 用来控制事件的三个方法 ************************/
private void checkTemperature(float t){
if(t<16 || t > 28){
throw new RuntimeException("Temperature out of range");
}
} public void setTemperature(float t){
checkTemperature(t);
mTemperature = t;
// To force a view to draw, call invalidate().
invalidate();
} @Override
public boolean onTouchEvent(MotionEvent event) {
// 获取触摸位置
float x = event.getX();
float y = event.getY(); // 计算触摸点到中心点的距离,判断该点是否在圆弧之内
double distance = Math.sqrt((x - mCenter) * (x - mCenter) + (y - mCenter) * (y - mCenter));
if(distance < mInnerRadius || distance > mInnerRadius + mGapWidth){
return false;
}
// 判断是否在空白区
double degree = Math.atan2(-(y-mCenter),x-mCenter);
if(-3*Math.PI/4<degree && degree < -Math.PI/4){
return false;
}
// 计算角度,并转换为对应的温度,设置当前温度,设置温度之后会自动对View进行重绘
if(degree < -3*Math.PI/4){
degree = degree + 2*Math.PI;
}
float t = (float) (26 - degree*8/Math.PI);
setTemperature(t); return true;
}
/***************** 为了获得mMinSize ************************/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int desiredWidth = Integer.MAX_VALUE; /**
* Extracts the mode from the supplied measure specification.
*
* Parameters
* measureSpec the measure specification to extract the mode from
* Returns
* UNSPECIFIED, AT_MOST or EXACTLY
*/
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width;
int height; //Measure Width
if (widthMode == MeasureSpec.EXACTLY) {
//Must be this size
width = widthSize;
//Toast.makeText(getContext(), "MeasureSpec.EXACTLY", 0).show();
} else if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(desiredWidth, widthSize);
//Toast.makeText(getContext(), "MeasureSpec.AT_MOST"+desiredWidth+" "+widthSize, 0).show();
} else {
//Be whatever you want
width = desiredWidth;
//Toast.makeText(getContext(), "MeasureSpec.UNSPECIFIED", 0).show();
}
mMinSize = width/600f; int size = width;
initSize();
//Measure Height
if (heightMode == MeasureSpec.EXACTLY) {
//Must be this size
height = heightSize;
} else if (heightMode == View.MeasureSpec.AT_MOST) {
//Can't be bigger than...
height = Math.min(size, heightSize);
} else {
//Be whatever you want
height = size;
} //MUST CALL THIS
/**
* This method must be called by onMeasure(int, int) to store the measured width and measured height. Failing to do so will trigger an exception at measurement time.
*
* Parameters
* measuredWidth The measured width of this view. May be a complex bit mask as defined by MEASURED_SIZE_MASK and MEASURED_STATE_TOO_SMALL.
* measuredHeight The measured height of this view. May be a complex bit mask as defined by MEASURED_SIZE_MASK and MEASURED_STATE_TOO_SMALL.
*/
setMeasuredDimension(width, height);
} }
Android custom View AirConditionerView hacking的更多相关文章
- Android: Custom View和include标签的区别
Custom View, 使用的时候是这样的: <com.example.home.alltest.view.MyCustomView android:id="@+id/customV ...
- Android Custom View系列《圆形菜单一》
前言 自定义view能够做出很多不同寻常的效果,圆形菜单交互效果不错,目前网上有两个版本,虽然比较庞大,但非常值得研究与学习. radial-menu-widget: https://code.goo ...
- Android 自定义View及其在布局文件中的使用示例
前言: 尽管Android已经为我们提供了一套丰富的控件,如:Button,ImageView,TextView,EditText等众多控件,但是,有时候在项目开发过程中,还是需要开发者自定义一些需要 ...
- Android 自定义view(二) —— attr 使用
前言: attr 在前一篇文章<Android 自定义view -- attr理解>已经简单的进行了介绍和创建,那么这篇文章就来一步步说说attr的简单使用吧 自定义view简单实现步骤 ...
- Android 自定义View及其在布局文件中的使用示例(三):结合Android 4.4.2_r1源码分析onMeasure过程
转载请注明出处 http://www.cnblogs.com/crashmaker/p/3549365.html From crash_coder linguowu linguowu0622@gami ...
- Android 自定义View及其在布局文件中的使用示例(二)
转载请注明出处 http://www.cnblogs.com/crashmaker/p/3530213.html From crash_coder linguowu linguowu0622@gami ...
- Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
Android中View的绘制过程 onMeasure方法简述 附有自定义View例子 Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android fr ...
- [原] Android 自定义View步骤
例子如下:Android 自定义View 密码框 例子 1 良好的自定义View 易用,标准,开放. 一个设计良好的自定义view和其他设计良好的类很像.封装了某个具有易用性接口的功能组合,这些功能能 ...
- Android自定义View
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24252901 很多的Android入门程序猿来说对于Android自定义View ...
随机推荐
- iOS工程预编译文件的创建
在搜索 添加工程名/自己的pch文件名记住加后缀
- JavaWeb-Eclipse的下载和安装
Eclipse下载地址:http://www.eclipse.org/downloads/ Eclipse集成JDK 遇见弹框: 1.这是由于缺少JRE所导致的,Eclipse中带有自己的编译器,因此 ...
- POJ 1989
#include <iostream> #define MAXN 10005 using namespace std; bool mark[MAXN]; int main() { //fr ...
- [C++]iostream的几种输入形式
做ACM题的时候,发现cin并不能满足所有输入要求.比如说: 每行给出一款运动鞋信息,若该款写不打折,则先后给出每种运动鞋单价P,所购买的数量Q:若打折,则先后给出每种运动鞋单价P,所购买的数量Q,折 ...
- DBCP参数介绍
参数分步介绍1)数据库连接相关 username="v10" password="v10" driverClassName="ora ...
- Linux多线程之同步
引言 条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待条件变量的条件成立而挂起(此时不再占用cpu):另一个线程使条件成立(给出条件成立信号).为了防止竞争,条件变 ...
- 正则表达式(RegExp)
正则表达式(RegExp) 如何按一定规则快速查找到需要找寻的内容,js的设计者们给我们提供了一个叫正则表达式(RegExp对象),专门用于处理类似问题. RegExp对象表示正则表达式,它是对字符串 ...
- 路径名称和struts.xml配置不一致导致struts2报404
struts.xml中写的是<result name="...">authorityInterceptor/xxx.jsp</result> 但是实际的文件 ...
- 将web项目deploy到tomcat的方法
如果已经把整个项目发布到tomcat的webapps文件夹下,就不用再配置tomcat的server.xml了(也就是不用配置<Context>节点) 并且,你的项目的WEB-INF/li ...
- testNG参数传递方式
testNG传参数的两种方式(xml文件,@DataProvider) 使用testng.xml设置参数 参数在xml文件中可以在suite级别定义,也可以在test级别定义:testNG会尝试先在包 ...