开源 java android app 开发(十三)绘图定义控件、摇杆控件的制作
文章的目的为了记录使用java 进行android app 开发学习的经历。本职为嵌入式软件开发,公司安排开发app,临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 java android app 开发(一)开发环境的搭建-CSDN博客
开源 java android app 开发(二)工程文件结构-CSDN博客
开源 java android app 开发(三)GUI界面布局和常用组件-CSDN博客
开源 java android app 开发(四)GUI界面重要组件-CSDN博客
开源 java android app 开发(五)文件和数据库存储-CSDN博客
开源 java android app 开发(六)多媒体使用-CSDN博客
开源 java android app 开发(七)通讯之Tcp和Http-CSDN博客
开源 java android app 开发(八)通讯之Mqtt和Ble-CSDN博客
开源 java android app 开发(九)后台之线程和服务-CSDN博客
开源 java android app 开发(十)广播机制-CSDN博客
开源 java android app 开发(十一)调试、发布-CSDN博客
开源 java android app 开发(十二)封库.aar-CSDN博客
推荐链接:
开源C# .net mvc 开发(一)WEB搭建_c#部署web程序-CSDN博客
开源 C# .net mvc 开发(二)网站快速搭建_c#网站开发-CSDN博客
开源 C# .net mvc 开发(三)WEB内外网访问(VS发布、IIS配置网站、花生壳外网穿刺访问)_c# mvc 域名下不可訪問內網,內網下可以訪問域名-CSDN博客
开源 C# .net mvc 开发(四)工程结构、页面提交以及显示_c#工程结构-CSDN博客
开源 C# .net mvc 开发(五)常用代码快速开发_c# mvc开发-CSDN博客
本章节主要内容是如何进行绘图并自定义控件。在Android开发中,经常需要自定义控件来实现特殊功能,本章主要讲如何自定义一个摇杆控件,通过拖动中间的圆圈,实现上下左右位置的输出,可以用到机器人或无人机的控制中。
1.绘图基础
2.摇杆控件的制作
3.效果图
一、绘图
在Android中使用Java进行绘图主要涉及以下几个核心类和概念:
1.1 Canvas类是Android绘图的基础,提供了各种绘制方法,以下为代码
public class CustomView extends View {@Overrideprotected 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();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.drawPath(path, paint);}@Overridepublic 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();}@Overrideprotected 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();*/}@Overrideprotected 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);}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();// 回收Bitmap资源if (stickBitmap != null && !stickBitmap.isRecycled()) {stickBitmap.recycle();}}@Overridepublic 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"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="200dp"android:orientation="horizontal"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="5"android:text="" /><com.hy.ble.send.JoystickViewandroid:id="@+id/joystickLeft"android:layout_width="220dp"android:layout_height="200dp"app:stickImage="@drawable/arr_fb"android:layout_centerInParent="true"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="5"android:text="" /><com.hy.ble.send.JoystickViewandroid:id="@+id/joystickRight"android:layout_width="200dp"android:layout_height="200dp"app:stickImage="@drawable/arr_lr"android:layout_centerInParent="true"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="5"android:text="" /></LinearLayout><LinearLayoutandroid: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 {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_first);// 在Activity中初始化两个JoystickViewJoystickView joystickLeft = findViewById(R.id.joystickLeft);JoystickView joystickRight = findViewById(R.id.joystickRight);// 为左摇杆设置监听joystickLeft.setOnJoystickMoveListener(new JoystickView.OnJoystickMoveListener() {@Overridepublic 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);}@Overridepublic void onReleased() {// 处理左摇杆释放Log.d("JoystickLeft", "Left released");}});// 为右摇杆设置监听joystickRight.setOnJoystickMoveListener(new JoystickView.OnJoystickMoveListener() {@Overridepublic 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);}@Overridepublic void onReleased() {// 处理左摇杆释放Log.d("JoystickRight", "right released");}});}
}
3.效果图