文章的目的为了记录使用java 进行android app 开发学习的经历。本职为嵌入式软件开发,公司安排开发app,临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 java android app 开发(一)开发环境的搭建-优快云博客
开源 java android app 开发(二)工程文件结构-优快云博客
开源 java android app 开发(三)GUI界面布局和常用组件-优快云博客
开源 java android app 开发(四)GUI界面重要组件-优快云博客
开源 java android app 开发(五)文件和数据库存储-优快云博客
开源 java android app 开发(六)多媒体使用-优快云博客
开源 java android app 开发(七)通讯之Tcp和Http-优快云博客
开源 java android app 开发(八)通讯之Mqtt和Ble-优快云博客
开源 java android app 开发(九)后台之线程和服务-优快云博客
开源 java android app 开发(十)广播机制-优快云博客
开源 java android app 开发(十一)调试、发布-优快云博客
开源 java android app 开发(十二)封库.aar-优快云博客
推荐链接:
开源C# .net mvc 开发(一)WEB搭建_c#部署web程序-优快云博客
开源 C# .net mvc 开发(二)网站快速搭建_c#网站开发-优快云博客
开源 C# .net mvc 开发(三)WEB内外网访问(VS发布、IIS配置网站、花生壳外网穿刺访问)_c# mvc 域名下不可訪問內網,內網下可以訪問域名-优快云博客
开源 C# .net mvc 开发(四)工程结构、页面提交以及显示_c#工程结构-优快云博客
开源 C# .net mvc 开发(五)常用代码快速开发_c# mvc开发-优快云博客
本章节主要内容是如何进行绘图并自定义控件。在Android开发中,经常需要自定义控件来实现特殊功能,本章主要讲如何自定义一个摇杆控件,通过拖动中间的圆圈,实现上下左右位置的输出,可以用到机器人或无人机的控制中。
1.绘图基础
2.摇杆控件的制作
3.效果图
一、绘图
在Android中使用Java进行绘图主要涉及以下几个核心类和概念:
1.1 Canvas类是Android绘图的基础,提供了各种绘制方法,以下为代码
public class CustomView extends View {
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 设置画笔
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(5);
// 绘制矩形
canvas.drawRect(100, 100, 300, 300, paint);
// 绘制圆形
paint.setColor(Color.BLUE);
canvas.drawCircle(200, 200, 50, paint);
// 绘制文本
paint.setColor(Color.BLACK);
paint.setTextSize(40);
canvas.drawText("Hello Android", 50, 50, paint);
}
}
1.2 Paint类控制绘图的样式和颜色,以下为代码
Paint paint = new Paint();
paint.setColor(Color.GREEN); // 设置颜色
paint.setStyle(Paint.Style.STROKE); // 设置填充样式(STROKE, FILL, FILL_AND_STROKE)
paint.setStrokeWidth(10); // 设置线条宽度
paint.setAntiAlias(true); // 开启抗锯齿
paint.setTextSize(30); // 设置文本大小
1.3 绘制基本图形,以下为具体代码
// 绘制直线
canvas.drawLine(startX, startY, endX, endY, paint);
// 绘制矩形
canvas.drawRect(left, top, right, bottom, paint);
// 绘制圆角矩形
canvas.drawRoundRect(left, top, right, bottom, rx, ry, paint);
// 绘制圆形
canvas.drawCircle(centerX, centerY, radius, paint);
// 绘制椭圆
canvas.drawOval(left, top, right, bottom, paint);
// 绘制弧形
canvas.drawArc(left, top, right, bottom, startAngle, sweepAngle, useCenter, paint);
// 绘制路径
Path path = new Path();
path.moveTo(x1, y1);
path.lineTo(x2, y2);
path.quadTo(controlX, controlY, endX, endY);
canvas.drawPath(path, paint);
1.4 创建自定义View,自定义控件可以直接继承自View,以下为典型代码
public class MyCustomView extends View {
private Paint paint;
private Path path;
public MyCustomView(Context context) {
super(context);
init();
}
public MyCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
paint.setAntiAlias(true);
path = new Path();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path, paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.moveTo(x, y);
return true;
case MotionEvent.ACTION_MOVE:
path.lineTo(x, y);
break;
default:
return false;
}
// 重绘视图
invalidate();
return true;
}
}
二、遥杆控件的制作,在app开发中经常需要使用到摇杆控件,比如机器人控制,比如无人机的控制,以下为摇杆控件的具体制作方法。
2.1 创建基础类继承自view,JoystickView.java。主要实现摇杆控件的绘制和事件定义,摇杆通常外围会有底座,中间会有小圆圈代表摇杆,中间用三角形来代表方向。拉动时触发事件,提供回调接口供调用者使用。
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class JoystickView extends View {
// 绘制参数
private Paint basePaint; // 底座画笔
private Paint outerRingPaint; // 外圈浅灰色圆环
private Paint middleRingPaint; // 中间白色圆环
private Paint trianglePaint; // 三角形画笔
private PointF centerPoint; // 中心点
private PointF stickPoint; // 摇杆当前位置
private float baseRadius; // 底座半径
private float stickRadius; // 摇杆半径
private float maxDistance; // 最大移动距离
private Path upTriangle; // 上三角形路径
private Path downTriangle; // 下三角形路径
private Bitmap stickBitmap; // 摇杆图片
private int stickImageResId = R.drawable.arr_fb; // 默认图片资源ID
// 回调接口
public interface OnJoystickMoveListener {
void onValueChanged(float xPercent, float yPercent);
void onReleased();
}
private OnJoystickMoveListener listener;
// 构造方法
public JoystickView(Context context) {
super(context);
init(null);
}
public JoystickView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public JoystickView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(AttributeSet attrs) {
// 从XML属性获取自定义属性
if (attrs != null) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.JoystickView);
stickImageResId = a.getResourceId(R.styleable.JoystickView_stickImage, R.drawable.arr_fb);
a.recycle();
}
// 初始化底座画笔(主灰色)
basePaint = new Paint();
//basePaint.setColor(Color.rgb(150, 150, 150));
basePaint.setColor(Color.rgb(47, 47, 47));
basePaint.setStyle(Paint.Style.FILL);
basePaint.setAntiAlias(true);
// 初始化外圈浅灰色圆环(宽度2)
outerRingPaint = new Paint();
outerRingPaint.setColor(Color.rgb(200, 200, 200));
outerRingPaint.setStyle(Paint.Style.STROKE);
outerRingPaint.setStrokeWidth(6f);
outerRingPaint.setAntiAlias(true);
// 初始化中间白色圆环(宽度3)
middleRingPaint = new Paint();
middleRingPaint.setColor(Color.WHITE);
middleRingPaint.setStyle(Paint.Style.STROKE);
middleRingPaint.setStrokeWidth(9f);
middleRingPaint.setAntiAlias(true);
// 初始化三角形画笔(白色)
trianglePaint = new Paint();
trianglePaint.setColor(Color.WHITE);
trianglePaint.setStyle(Paint.Style.FILL);
trianglePaint.setAntiAlias(true);
//stickBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.arr_fb);
if (stickBitmap != null && !stickBitmap.isRecycled()) {
stickBitmap.recycle();
}
stickBitmap = BitmapFactory.decodeResource(getResources(), stickImageResId);
centerPoint = new PointF();
stickPoint = new PointF();
upTriangle = new Path();
downTriangle = new Path();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 计算中心点
centerPoint.set(w / 2f, h / 2f);
stickPoint.set(centerPoint);
// 计算半径
baseRadius = Math.min(w, h) * 0.4f;
stickRadius = baseRadius * 0.3f;
maxDistance = baseRadius - stickRadius - 20;
// 如果图片太大,可以缩放
if (stickBitmap != null) {
int desiredSize = (int)(stickRadius * 2);
stickBitmap = Bitmap.createScaledBitmap(stickBitmap, desiredSize, desiredSize, true);
}
// 初始化三角形路径
updateTrianglePaths();
}
private void updateTrianglePaths() {
/*
float triangleHeight = stickRadius * 0.4f; // 三角形高度
float triangleBase = (float) (2 * triangleHeight / Math.tan(Math.toRadians(30))); // 计算底边长度(120度角)
float triangleSpacing = stickRadius * 0.4f; // 三角形间距
// 上三角形(向上120度)
upTriangle.reset();
upTriangle.moveTo(stickPoint.x, stickPoint.y - triangleSpacing - triangleHeight);
upTriangle.lineTo(stickPoint.x - triangleBase/2, stickPoint.y - triangleSpacing);
upTriangle.lineTo(stickPoint.x + triangleBase/2, stickPoint.y - triangleSpacing);
upTriangle.close();
// 下三角形(向下120度)
downTriangle.reset();
downTriangle.moveTo(stickPoint.x, stickPoint.y + triangleSpacing + triangleHeight);
downTriangle.lineTo(stickPoint.x - triangleBase/2, stickPoint.y + triangleSpacing);
downTriangle.lineTo(stickPoint.x + triangleBase/2, stickPoint.y + triangleSpacing);
downTriangle.close();
*/
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 1. 绘制底座主圆
canvas.drawCircle(centerPoint.x, centerPoint.y, baseRadius, basePaint);
// 2. 绘制外圈浅灰色圆环(最外层)
canvas.drawCircle(centerPoint.x, centerPoint.y, baseRadius - 3f, outerRingPaint);
// 3. 绘制中间白色圆环
canvas.drawCircle(centerPoint.x, centerPoint.y, baseRadius - 9f, middleRingPaint);
// 4. 绘制摇杆图片(替换原来的圆圈)
if (stickBitmap != null) {
canvas.drawBitmap(stickBitmap,
stickPoint.x - stickBitmap.getWidth()/2,
stickPoint.y - stickBitmap.getHeight()/2,
null);
} else {
// 如果图片加载失败,绘制默认圆圈
Paint stickPaint = new Paint();
stickPaint.setColor(Color.rgb(220, 220, 220));
stickPaint.setStyle(Paint.Style.FILL);
stickPaint.setAntiAlias(true);
canvas.drawCircle(stickPoint.x, stickPoint.y, stickRadius, stickPaint);
}
// 5. 绘制三角形(白色)
canvas.drawPath(upTriangle, trianglePaint);
canvas.drawPath(downTriangle, trianglePaint);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
// 回收Bitmap资源
if (stickBitmap != null && !stickBitmap.isRecycled()) {
stickBitmap.recycle();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
float dx = touchX - centerPoint.x;
float dy = touchY - centerPoint.y;
float distance = (float) Math.sqrt(dx * dx + dy * dy);
if (distance <= maxDistance) {
stickPoint.set(touchX, touchY);
} else {
float ratio = maxDistance / distance;
stickPoint.set(
centerPoint.x + dx * ratio,
centerPoint.y + dy * ratio
);
}
updateTrianglePaths();
float xPercent = (stickPoint.x - centerPoint.x) / maxDistance;
float yPercent = (stickPoint.y - centerPoint.y) / maxDistance;
if (listener != null) {
listener.onValueChanged(xPercent, -yPercent);
}
invalidate();
return true;
case MotionEvent.ACTION_UP:
stickPoint.set(centerPoint);
updateTrianglePaths();
invalidate();
if (listener != null) {
listener.onReleased();
}
return true;
}
return super.onTouchEvent(event);
}
public void setOnJoystickMoveListener(OnJoystickMoveListener listener) {
this.listener = listener;
}
}
2.2 添加属性文件res\values\attrs.xml文件,当外部调用希望通过界面文件在初始化就传入参数的时候,需要添加属性文件。
<resources>
<declare-styleable name="JoystickView">
<attr name="stickImage" format="reference" />
</declare-styleable>
</resources>
2.3 activity_first.xml文件代码,通过设置新建属性可以设置摇杆中心的图片,app:stickImage="@drawable/arr_lr"
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#FF2F2F2F"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="5"
android:text="" />
<com.hy.ble.send.JoystickView
android:id="@+id/joystickLeft"
android:layout_width="220dp"
android:layout_height="200dp"
app:stickImage="@drawable/arr_fb"
android:layout_centerInParent="true"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="5"
android:text="" />
<com.hy.ble.send.JoystickView
android:id="@+id/joystickRight"
android:layout_width="200dp"
android:layout_height="200dp"
app:stickImage="@drawable/arr_lr"
android:layout_centerInParent="true"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="5"
android:text="" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:background="@drawable/bottom"
></LinearLayout>
</LinearLayout>
2.3 mainactivity.java的调用
package com.hy.ble.send;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothGattServerCallback;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.ParcelUuid;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.tbruyelle.rxpermissions2.RxPermissions;
import com.trello.rxlifecycle2.android.ActivityEvent;
import com.trello.rxlifecycle2.components.support.RxAppCompatActivity;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import butterknife.ButterKnife;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.Nullable;
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
// 在Activity中初始化两个JoystickView
JoystickView joystickLeft = findViewById(R.id.joystickLeft);
JoystickView joystickRight = findViewById(R.id.joystickRight);
// 为左摇杆设置监听
joystickLeft.setOnJoystickMoveListener(new JoystickView.OnJoystickMoveListener() {
@Override
public void onValueChanged(float xPercent, float yPercent) {
// 这里可以添加控制逻辑,例如:
// - 控制机器人移动
// - 控制游戏角色
// - 控制无人机等
// 示例:根据摇杆位置控制电机
if (xPercent > 0.5f) {
//向右
} else if (xPercent < -0.5f) {
// 向左转
}
if (yPercent > 0.5f) {
//前进
} else if (yPercent < -0.5f) {
// 后退
}
Log.d("Joystick", "Left - X: " + xPercent + ", Y: " + yPercent);
}
@Override
public void onReleased() {
// 处理左摇杆释放
Log.d("JoystickLeft", "Left released");
}
});
// 为右摇杆设置监听
joystickRight.setOnJoystickMoveListener(new JoystickView.OnJoystickMoveListener() {
@Override
public void onValueChanged(float xPercent, float yPercent) {
// 这里可以添加控制逻辑,例如:
// - 控制机器人移动
// - 控制游戏角色
// - 控制无人机等
// 示例:根据摇杆位置控制电机
if (xPercent > 0.5f) {
// 向右转
} else if (xPercent < -0.5f) {
// 向左转
}
if (yPercent > 0.5f) {
// 前进
} else if (yPercent < -0.5f) {
// 后退
}
Log.d("Joystick", "right - X: " + xPercent + ", Y: " + yPercent);
}
@Override
public void onReleased() {
// 处理左摇杆释放
Log.d("JoystickRight", "right released");
}
});
}
}
3.效果图