demo下载地址:http://download.csdn.net/detail/github_33304260/9860547
#相关链接:
Android的JNI【实战教程】1⃣️–java和c/c++的那些事
Android的JNI【实战教程】2⃣️–AS下NDK环境配置及第一个工程
Android的JNI【实战教程】3⃣️–Java调用C代码
Android的JNI【实战教程】4⃣️–C调用Java代码
Android的JNI【实战教程】5⃣️—Android Studio 2.2 以上 NDK开发
#原理
温控计主要是通过C语言获取设备的温度数据(我们这里写个随机数模拟获取设备温度),然后我们JAVA通过JNI获取到C中的数据,然后在Android界面以某种试展现出来。
在Java程序执行的时候,若在某个类中调用了native方法,则虚拟机会通过JNI来转调用库文件中的C语言代码。提示:C代码最终是在Linux进程中执行的,而不是在虚拟机中。
#效果
demo下载地址:http://download.csdn.net/detail/github_33304260/9860547
效果如图所示:
这里做的是C++代码产生一个0~90的随机数。
#代码
##C代码
下面的代码非常简单,定时2秒产生一个随机数 。
#include <jni.h>
#include <string>
#include <unistd.h>
#include <stdlib.h>
extern "C"
JNIEXPORT jint JNICALL
Java_pressure_libin_com_pressure_MainActivity_getPressureFromJNI(JNIEnv *env, jobject instance) {
while (1) {
sleep(2); //休眠两秒
int pressure = rand()%90;
printf("当前锅炉压力值是:%d \n", pressure);
return pressure;
}
}
##JAVA代码
代码也非常的简单,就是通过Timer产生一个定时任务,去定时读取温度(C++模拟的)数值,通过handler接收读取到的数值进行展示。
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
private Timer timer;
private TimerTask task;
//接收消息并展示到界面
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what == 1){
cv.setCurrentDegree((int)msg.obj);
}
}
};
private CircleView cv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
cv = (CircleView) findViewById(R.id.cv);
//定时器,每两秒读取一次C++产生的随机数
timer = new Timer();
task = new TimerTask() {
@Override
public void run() {
Message msg = new Message();
msg.obj = getPressureFromJNI();
msg.what = 1;
handler.sendMessage(msg);
}
};
timer.schedule(task,100,2000);
}
/**
* 从JNI获取检测数据
* @return 返回检测到的数据
*/
public native int getPressureFromJNI();
}
##自定义View
本节的重点不是自定义View,所以这里就不详细讲解啦,感兴趣的可以自己google。附上代码:
public class CircleView extends View {
private Paint linePaint;
private Paint circlePaint;
private Paint textPaint;//刻度值
private Paint centerTextPaint;//中间的温度值
private Paint indicatorPaint;//指示器
private RectF mRectF;
private int circleWidth = 60; //空心圆的宽度
private int viewWidth, viewHeight; //画布的宽高
private int minCount = 0; // 最小度数
private int maxCount = 90; // 最大度数
private float centerX, centerY;
private int defaultValue;
int mCenter = 0;// 圆的半径
int mRadius = 0;
private SweepGradient mSweepGradient;
private int scanDegree = 0;//最高温度和最低温度扫描角度
private int currentScanDegree = 0;//当前温度扫过的角度
int currentDegree = 0;//当前温度
int screenWidth, screenHeight;
public CircleView(Context context) {
super(context);
Log.e("My----->", "1");
initPaint();
}
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
screenWidth = MeasureUtil.getScreenWidth(context);
screenHeight = MeasureUtil.getScreenHeight(context);
Log.e("My----->", "2 " + screenWidth + " " + screenHeight);
initPaint();
}
private void initPaint() {
linePaint = new Paint();
linePaint.setColor(Color.CYAN);
linePaint.setStyle(Paint.Style.FILL);
linePaint.setAntiAlias(true);
linePaint.setStrokeWidth(1.0f);
textPaint = new Paint();
textPaint.setColor(Color.BLACK);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setAntiAlias(true);
textPaint.setTextSize(30);
centerTextPaint = new Paint();
centerTextPaint.setColor(Color.BLUE);
centerTextPaint.setTextAlign(Paint.Align.CENTER);
centerTextPaint.setAntiAlias(true);
centerTextPaint.setTextSize(80);
circlePaint = new Paint();
circlePaint.setColor(Color.WHITE);
circlePaint.setAntiAlias(true);
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setStrokeCap(Paint.Cap.ROUND);//实现末端圆弧
circlePaint.setStrokeWidth(circleWidth);
indicatorPaint = new Paint();
indicatorPaint.setColor(0xFFF7F709);
indicatorPaint.setAntiAlias(true);
indicatorPaint.setStyle(Paint.Style.FILL);
// 着色的共有270度,这里设置了12个颜色均分360度s
int[] colors = {0xFFD52B2B, 0xFFf70101, 0xFFFFFFFF, 0xFFFFFFFF,
0xFF6AE2FD, 0xFF8CD0E5, 0xFFA3CBCB, 0xFFD1C299, 0xFFE5BD7D,
0xFFAA5555, 0xFFBB4444, 0xFFC43C3C};
mCenter = screenWidth / 2;
mRadius = screenWidth / 2 - 100;
// 渐变色
mSweepGradient = new SweepGradient(mCenter, mCenter, colors, null);
// 构建圆的外切矩形
mRectF = new RectF(mCenter - mRadius, mCenter - mRadius, mCenter
+ mRadius, mCenter + mRadius);
}
@Override
protected void onDraw(Canvas canvas) {
// TODO 自动生成的方法存根
super.onDraw(canvas);
circlePaint.setShader(null);
canvas.drawArc(mRectF, 135, 270, false, circlePaint);
Log.e("scan--1-->", scanDegree + "");
// TODO: 2017/5/10 扫过度数
//重新赋值了,所以手指滑动没有显示
scanDegree = getCurrentDegree() * 3;
// 设置画笔渐变色
circlePaint.setShader(mSweepGradient);
canvas.drawArc(mRectF, 135, (float) scanDegree, false, circlePaint);
int insideIndicator = mRadius - circleWidth;//离圆环的距离
currentScanDegree = getCurrentDegree() * 3;
canvas.drawText((getCurrentDegree()) + "℃", mCenter, mCenter, centerTextPaint);//全温度盘时,把currentScanDegree改为scanDegree,中间的值就会跟着改变了
Log.e("指示角度----》", currentScanDegree + "");
if (currentScanDegree <= 45) {//第三象限
canvas.drawCircle((float) (mCenter - insideIndicator * Math.sin(Math.PI * (currentScanDegree + 45) / 180)), (float) (mCenter + insideIndicator * Math.cos(Math.PI * (currentScanDegree + 45) / 180)), 10, indicatorPaint);
} else if (45 < currentScanDegree && currentScanDegree <= 135) {//第二象限
canvas.drawCircle((float) (mCenter - insideIndicator * Math.cos(Math.PI * (currentScanDegree - 45) / 180)), (float) (mCenter - insideIndicator * Math.sin(Math.PI * (currentScanDegree - 45) / 180)), 10, indicatorPaint);
} else if (135 < currentScanDegree && currentScanDegree <= 225) {//第一象限
canvas.drawCircle((float) (mCenter + insideIndicator * Math.sin(Math.PI * (currentScanDegree - 135) / 180)), (float) (mCenter - insideIndicator * Math.cos(Math.PI * (currentScanDegree - 135) / 180)), 10, indicatorPaint);
} else if (225 < currentScanDegree && currentScanDegree <= 270) {//第四象限
canvas.drawCircle((float) (mCenter + insideIndicator * Math.cos(Math.PI * (currentScanDegree - 225) / 180)), (float) (mCenter + insideIndicator * Math.sin(Math.PI * (currentScanDegree - 225) / 180)), 10, indicatorPaint);
}
for (int i = 0; i < 120; i++) {
if (i <= 45 || i >= 75) {
canvas.drawLine(mCenter, mCenter - mRadius - 30, mCenter,
mCenter - mRadius + 30, linePaint);
}
canvas.rotate(3, mCenter, mCenter);
}
// x代表文字的x轴距离圆心x轴的距离 因为刚好是45度,所以文字x轴值和y值相等
int x = 0;
// 三角形的斜边
int c = mRadius + 60 / 2 + 40;// 40代表这个字距离圆外边的距离
// 因为是每45度写一次文字,故根据到圆心的位置,利用三角形的公式,可以算出文字的坐标值
x = (int) Math.sqrt((c * c / 2));
canvas.drawText("0", mCenter - x, mCenter + x, textPaint);
canvas.drawText("15", mCenter - c, mCenter, textPaint);
canvas.drawText("30", mCenter - x, mCenter - x, textPaint);
canvas.drawText("45", mCenter, mCenter - c, textPaint);
canvas.drawText("60", mCenter + x, mCenter - x, textPaint);
canvas.drawText("75", mCenter + c, mCenter, textPaint);
canvas.drawText("90", mCenter + x, mCenter + x, textPaint);
Log.e("TAG", "calculateX(mRadius,50) ====== " + calculateX(mRadius, 50));
}
/**
* 设置当前温度
*
* @param currentDegree
*/
public void setCurrentDegree(int currentDegree) {
this.currentDegree = currentDegree;
invalidate();
}
public int getCurrentDegree() {
return currentDegree;
}
// 因为自定义的空间的高度设置的是wrap_content,所以我们必须要重写onMeasure方法去测量高度,否则布局界面看不到
// 其他控件(被覆盖)
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}
/**
* 测量宽度
*
* @param widthMeasureSpec
* @return
*/
private int measureWidth(int widthMeasureSpec) {
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
// 默认宽高;
defaultValue = screenWidth;
switch (mode) {
case MeasureSpec.AT_MOST:
// 最大值模式 当控件的layout_Width或layout_height属性指定为wrap_content时
Log.e("cmos---->", "size " + size + " screenWidth " + screenWidth);
size = Math.min(defaultValue, size);
break;
case MeasureSpec.EXACTLY:
// 精确值模式
// 当控件的android:layout_width=”100dp”或android:layout_height=”match_parent”时
break;
default:
size = defaultValue;
break;
}
defaultValue = size;
return size;
}
/**
* 测量高度
*
* @param heightMeasureSpec
* @return
*/
private int measureHeight(int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
switch (mode) {
case MeasureSpec.AT_MOST:
// 最大值模式 当控件的layout_Width或layout_height属性指定为wrap_content时
Log.e("cmos---->", "size " + size + " screenHeight " + screenHeight);
size = Math.min(screenHeight / 2, size);
break;
case MeasureSpec.EXACTLY:
// 精确值模式
// 当控件的android:layout_width=”100dp”或android:layout_height=”match_parent”时
break;
default:
size = defaultValue;
break;
}
return size;
}
/**
* 根据半径和角度计算x坐标
*/
private float calculateX(float r, double angle) {
angle = angle * ((2 * Math.PI) / 360);
Log.e(TAG, "angle = " + angle + ",Math.sin(angle) = " + Math.sin(angle));
double x = r * Math.sin(angle);
double xFinal = centerX + x;
return (float) xFinal;
}
/**
* 根据半径和角度计算y坐标
*/
private float calculateY(float r, double angle) {
angle = angle * ((2 * Math.PI) / 360);
Log.e(TAG, "angle = " + angle + ",Math.cos(angle) = " + Math.cos(angle));
double y = r * Math.cos(angle);
double yFinal = centerY - y;
return (float) yFinal;
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
viewWidth = getWidth();
viewHeight = getHeight();
centerX = viewWidth / 2f;
centerY = viewHeight / 2f;
}
}
扫码关注公众号“伟大程序猿的诞生“,更多干货新鲜文章等着你~
公众号回复“资料获取”,获取更多干货哦~
有问题添加本人微信号“fenghuokeji996” 或扫描博客导航栏本人二维码