Android项目实战(二十六):蓝牙连接硬件设备开发规范流程
前言:
最近接触蓝牙开发,主要是通过蓝牙连接获取传感器硬件设备的数据,并进行处理。
网上学习一番,现整理出一套比较标准的 操作流程代码。
如果大家看得懂,将来只需要改下 硬件设备的MAC码 和 改下对接收数据的处理 即可。 一切都是套路~~~
现在以一个小型项目需求来学习Android蓝牙编程
需求: 通过蓝牙获取硬件数据,并显示在一个随数据即时变化的动态折线图中。
实现思路:
(1) 配对蓝牙设备
(2) 连接蓝牙设备 ,根据MAC地址,代码中修改
(3) 接收数据
(4) 处理数据 ,根据硬件厂商提供给你的数据转换公式,在BluetoothService类中 修改
(5) 传数据给折线图,展现实时变化
-----------------------------------------------------------------------
蓝牙知识了解:
(1)、MAC地址:每个设备都有全球唯一的,根据此MAC地址判断蓝牙设备
(2)、蓝牙传输数据,通常一秒钟会传输很多个包,每个包的数据情况如下:
此时,这个包有11个字节,0x55 是首码,通常通过他来判断一个包的开始
SUM是验证码,会有一套公式来计算,判断当前包是不是一个有效的完整的包
中间的即是数据,然后硬件方面会给我们一套计算公式,可以以此获取我们要的数据。
当然每个硬件的包的数据大小都是不同的,有的可能有21个字节,每个硬件的数据的计算方式也不想同
代码实现:
一共就三部分,因为代码篇幅可能较大,不适合一段段代码讲解,直接贴出整个代码。所有的解释都在注释当中。
其中:
(1)、红色部分是需要大家根据个人硬件情况进行修改的
(2)、紫色部分是根据个人数据情况添加删除修改的。
一:MainActivity
public class MainActivity extends Activity {private BluetoothService mBluetoothService; //自定义蓝牙服务类private BluetoothAdapter mBluetoothAdapter;private String mConnectedDeviceName = null; //连接设备的名称//默认是1,因为程序启动时首先会连接一个蓝牙private int current_pos = 1;//hanlder消息标识 message.whatpublic static final int MESSAGE_STATE_CHANGE = 1; // 状态改变public static final int MESSAGE_READ = 2; // 读取数据public static final int MESSAGE_WRITE = 3; // 给硬件传数据,暂不需要,看具体需求public static final int MESSAGE_DEVICE_NAME = 4; // 设备名字public static final int MESSAGE_TOAST = 5; // Toast//传感器 ,这里默认同时需要和三个硬件连接,分别设置id 1,2,3进行区分,demo中实际只用到 MAGIKARE_SENSOR_DOWN = 1//可以根据情况自行添加删除public static final int MAGIKARE_SENSOR_UP = 2;public static final int MAGIKARE_SENSOR_DOWN = 1;public static final int MAGIKARE_SENSOR_CENTER = 3;public static float[] m_receive_data_up; //传感器的数据public static float[] m_receive_data_down; //传感器的数据 ,demo中我们只需要这一个,因为只有一个硬件设备,public static float[] m_receive_data_center; //传感器的数据 @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//获取蓝牙适配器mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();// 1、判断设备是否支持蓝牙功能if (mBluetoothAdapter == null) {//设备不支持蓝牙功能Toast.makeText(this, "当前设备不支持蓝牙功能!", Toast.LENGTH_SHORT).show();return;}// 2、打开设备的蓝牙功能if (!mBluetoothAdapter.isEnabled()) {boolean enable = mBluetoothAdapter.enable(); //返回值表示 是否成功打开了蓝牙设备if (enable) {Toast.makeText(this, "打开蓝牙功能成功!", Toast.LENGTH_SHORT).show();} else {Toast.makeText(this, "打开蓝牙功能失败,请到'系统设置'中手动开启蓝牙功能!", Toast.LENGTH_SHORT).show();return;}}// 3、创建自定义蓝牙服务对象if (mBluetoothService == null) {mBluetoothService = new BluetoothService(MainActivity.this, mHandler);}if (mBluetoothService != null) {//根据MAC地址远程获取一个蓝牙设备,这里固定了,实际开发中,需要动态设置参数(MAC地址)BluetoothDevice sensor_down = mBluetoothAdapter.getRemoteDevice("20:16:06:15:78:76");if (sensor_down != null) {//成功获取到远程蓝牙设备(传感器),这里默认只连接MAGIKARE_SENSOR_DOWN = 1这个设备 mBluetoothService.connect(sensor_down, MAGIKARE_SENSOR_DOWN);}}}private Handler mHandler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(Message msg) {switch (msg.what){case MESSAGE_READ:try {String str=msg.getData().getString("index");int index=Integer.valueOf(str);switch (index){//获取到蓝牙传输过来的数据case MAGIKARE_SENSOR_UP:m_receive_data_up=msg.getData().getFloatArray("Data");break;//实际只用到这个case ,因为demo只连接了一个硬件设备case MAGIKARE_SENSOR_DOWN:m_receive_data_down=msg.getData().getFloatArray("Data");break;case MAGIKARE_SENSOR_CENTER:m_receive_data_center=msg.getData().getFloatArray("Data");break;}} catch (Exception e) {// TODO: handle exception }break;case MESSAGE_STATE_CHANGE: // 连接状态switch (msg.arg1) {case BluetoothService.STATE_CONNECTED:break;case BluetoothService.STATE_CONNECTING:break;case BluetoothService.STATE_LISTEN:break;case BluetoothService.STATE_NONE:break;}break;case MESSAGE_DEVICE_NAME:mConnectedDeviceName = msg.getData().getString("device_name");Log.i("bluetooth","成功连接到:"+mConnectedDeviceName);Toast.makeText(getApplicationContext(),"成功连接到设备" + mConnectedDeviceName,Toast.LENGTH_SHORT).show();break;case MESSAGE_TOAST:int index=msg.getData().getInt("device_id");Toast.makeText(getApplicationContext(),msg.getData().getString("toast"), Toast.LENGTH_SHORT).show();//当失去设备或者不能连接设备时,重新连接Log.d("Magikare","当失去设备或者不能连接设备时,重新连接"); //重新连接硬件设备if(mBluetoothService!=null){switch (index) {case MAGIKARE_SENSOR_DOWN: //根据你的硬件的MAC地址写参数,每一个硬件设备都有一个MAC地址,此方法是根据MAC地址得到蓝牙设备BluetoothDevice sensor_down = mBluetoothAdapter.getRemoteDevice("20:16:06:15:78:76");if (sensor_down != null)mBluetoothService.connect(sensor_down, MAGIKARE_SENSOR_DOWN);break;case MAGIKARE_SENSOR_UP:BluetoothDevice sensor_up = mBluetoothAdapter.getRemoteDevice(""); //参数写你这个设备的MAC码if (sensor_up != null)mBluetoothService.connect(sensor_up, MAGIKARE_SENSOR_UP);break;case MAGIKARE_SENSOR_CENTER:BluetoothDevice center = mBluetoothAdapter.getRemoteDevice(""); //参数写你这个设备的MAC码if (center != null)mBluetoothService.connect(center, MAGIKARE_SENSOR_CENTER);break;}}break;}return false;}});public synchronized void onResume() {super.onResume();if (mBluetoothService != null) {if (mBluetoothService.getState() == BluetoothService.STATE_NONE) {mBluetoothService.start();}}}@Overridepublic void onDestroy() {super.onDestroy();if (mBluetoothService != null) mBluetoothService.stop();} // 硬件通过蓝牙传输的byte类型已经转换为float类型,并且通过handler传输到 m_receive_data_down[]数组中,一下操作是获取这个数据,根据个人情况使用//获取角度public float[] GetAngle(int index){float[] angles=new float[3];if(m_receive_data_up==null||m_receive_data_down==null){return angles;}switch (index){case MAGIKARE_SENSOR_DOWN:angles[0]=m_receive_data_down[6];angles[1]=m_receive_data_down[7];angles[2]=m_receive_data_down[8];break;case MAGIKARE_SENSOR_UP:angles[0]=m_receive_data_up[6];angles[1]=m_receive_data_up[7];angles[2]=m_receive_data_up[8];Log.d("安卓 Up 角度",angles[0]+","+angles[1]+","+angles[2]);break;}return angles;}//获取角速度public static float[] GetAngleSpeed(int index){float [] anglespeed=new float[3];if(m_receive_data_down==null){return anglespeed;}switch (index){case MAGIKARE_SENSOR_DOWN:anglespeed[0]=m_receive_data_down[3];anglespeed[1]=m_receive_data_down[4];anglespeed[2]=m_receive_data_down[5];break;case MAGIKARE_SENSOR_UP:anglespeed[0]=m_receive_data_up[3];anglespeed[1]=m_receive_data_up[4];anglespeed[2]=m_receive_data_up[5];break;}return anglespeed;}public float[] GetQuaternion(int index){float[] quaternion=new float[4];if(m_receive_data_down==null){return quaternion;}switch (index){case MAGIKARE_SENSOR_DOWN:quaternion[0]=m_receive_data_down[23];quaternion[1]=m_receive_data_down[24];quaternion[2]=m_receive_data_down[25];quaternion[3]=m_receive_data_down[26];Log.i("saveinfo","m_receive_data_down23"+m_receive_data_down[23]);Log.i("saveinfo","m_receive_data_down24"+m_receive_data_down[24]);Log.i("saveinfo","m_receive_data_down25"+m_receive_data_down[25]);Log.i("saveinfo","m_receive_data_down26"+m_receive_data_down[26]);break;case MAGIKARE_SENSOR_UP:quaternion[0]=m_receive_data_up[23];quaternion[1]=m_receive_data_up[24];quaternion[2]=m_receive_data_up[25];quaternion[3]=m_receive_data_up[26];break;case MAGIKARE_SENSOR_CENTER:quaternion[0]=m_receive_data_center[23];quaternion[1]=m_receive_data_center[24];quaternion[2]=m_receive_data_center[25];quaternion[3]=m_receive_data_center[26];}return quaternion;}}
二、BluetoothService
public class BluetoothService { private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");private Context context;//蓝牙适配器private BluetoothAdapter mAdapter;private Handler mHandler;//当前传感器设备的个数,即要开启的线程个数,用于设置线程数组的大小//这里默认为1,因为我们目前只需要和一个传感器连接, 比如:你要连接两个硬件设备,那就设置值为2,这样就会开启两个线程,分别去执行想要操作public static final int SENSEOR_NUM=1;private AcceptThread mAcceptThread;// 请求连接的监听进程private ConnectThread mConnectThread;// 连接一个设备的进程public ConnectedThread[] mConnectedThread=new ConnectedThread[SENSEOR_NUM];// 已经连接之后的管理进程private int mState;// 当前状态// 指明连接状态的常量public static final int STATE_NONE = 0; //没有连接public static final int STATE_LISTEN = 1; //等待连接public static final int STATE_CONNECTING = 2; //正在连接public static final int STATE_CONNECTED = 3; //已经连接public BluetoothService(Context context, Handler mHandler) {this.context = context;this.mHandler = mHandler;mAdapter = BluetoothAdapter.getDefaultAdapter();//获取蓝牙适配器mState = STATE_NONE ; //当前连接状态:未连接 }// 参数 index 是 硬件设备的id ,随便设的,目的在于当 同时连接多个硬件设备的时候,根据此id进行区分public synchronized void connect(BluetoothDevice device, int index) {//连接一个蓝牙时,将该设备 的蓝牙连接线程关闭,如果有的话//demo 就只有一个硬件设备,默认该设备id 取值index=1;if (mConnectedThread[index-1] != null) {mConnectedThread[index-1].cancel();mConnectedThread[index-1]=null;}mConnectThread=new ConnectThread(device,index);mConnectThread.start();setState(STATE_CONNECTING);}private class ConnectThread extends Thread{private final BluetoothSocket mmSocket;private final BluetoothDevice mmDevice;private int index;public ConnectThread(BluetoothDevice device,int index) {mmDevice = device;this.index=index;BluetoothSocket tmp = null;try {tmp = device.createRfcommSocketToServiceRecord(MY_UUID);// Get a BluetoothSocket for a connection with the given BluetoothDevice }catch (IOException e) {}mmSocket = tmp;}public void run() {setName("ConnectThread");//当连接成功,取消蓝牙适配器搜索蓝牙设备的操作,因为搜索操作非常耗时mAdapter.cancelDiscovery();// Always cancel discovery because it will slow down a connectiontry {mmSocket.connect();// This is a blocking call and will only return on a successful connection or an exception }catch (IOException e) {connectionFailed(this.index);try {mmSocket.close();} catch (IOException e2) {}BluetoothService.this.start();// 引用来说明要调用的是外部类的方法 runreturn;}synchronized (BluetoothService.this) {// Reset the ConnectThread because we're donemConnectThread = null;}connected(mmSocket, mmDevice,index);// Start the connected thread }public void cancel() {try {mmSocket.close();} catch (IOException e) {}}}class ConnectedThread extends Thread{private BluetoothSocket mmSocket;private InputStream mmInStream;private OutputStream mmOutStream;private int index;private Queue<Byte> queueBuffer = new LinkedList<Byte>();private byte[] packBuffer = new byte[11];//构造方法public ConnectedThread(BluetoothSocket socket,int index) {mmSocket = socket;InputStream tmpIn = null;OutputStream tmpOut = null;this.index=index;// Get the BluetoothSocket input and output streamstry {tmpIn = socket.getInputStream();tmpOut = socket.getOutputStream();} catch (IOException e) {}mmInStream = tmpIn;mmOutStream = tmpOut;}// 数组大小看你的数据需求,这里存的是你处理蓝牙传输来的字节数据之后实际要用到的数据private float [] fData=new float[31];@Overridepublic void run() {byte[] tempInputBuffer = new byte[1024];int acceptedLen = 0; //记录每次读取数据的数据长度byte sHead;long lLastTime = System.currentTimeMillis(); //获取开始时间while(true){try {acceptedLen = mmInStream.read(tempInputBuffer);//返回接收的长度//从缓冲区中读取数据for (int i = 0; i < acceptedLen; i++) {queueBuffer.add(tempInputBuffer[i]);}// 这里需要按个人硬件数据的情况自行修改了// 如果你的硬件蓝牙传输 一个包有11个字节,那queueBuffer.size()>=11// 如果你的硬件蓝牙传输 一个包有21个字节,那queueBuffer.size()>=21while (queueBuffer.size()>=11){//返回队首并删除,判断队首是不是0x55,如果不是,说明不是一个包的数据,跳过,//注意这里的0x55是你的包的首字节 if (queueBuffer.poll()!=0x55)continue;// 进入到这里,说明得到一个包的数据了,然后就要根据个人硬件的数据情况,将byte类型的数据转换为float类型的数据sHead = queueBuffer.poll(); //返回队首并删除 // 现在得到的就是你数据部分了,如果有9位字节代表数据,j<9 ,如果有19位字节代表数据,j<19 //将字节数组存到packBuffer[]数据中,用于byte-->float数据的转换 for (int j = 0; j < 9; j++) {packBuffer[j] = queueBuffer.poll();}switch (sHead) {//case 0x52://角速度fData[3] = ((((short) packBuffer[1]) << 8) | ((short) packBuffer[0] & 0xff)) / 2000;fData[4] = ((((short) packBuffer[3]) << 8) | ((short) packBuffer[2] & 0xff)) / 200;fData[5] = ((((short) packBuffer[5]) << 8) | ((short) packBuffer[4] & 0xff)) / 200;fData[17] = ((((short) packBuffer[7]) << 8) | ((short) packBuffer[6] & 0xff)) / 100.0f;break;case 0x53://角度fData[6] = ((((short) packBuffer[1]) << 8) | ((short) packBuffer[0] & 0xff)) / 180;fData[7] = ((((short) packBuffer[3]) << 8) | ((short) packBuffer[2] & 0xff)) / 180;fData[8] = ((((short) packBuffer[5]) << 8) | ((short) packBuffer[4] & 0xff)) / 180;fData[17] = ((((short) packBuffer[7]) << 8) | ((short) packBuffer[6] & 0xff)) / 100.0f;break;case 0x59://四元数fData[23] = ((((short) packBuffer[1]) << 8) | ((short) packBuffer[0] & 0xff)) / 327.0f;fData[24] = ((((short) packBuffer[3]) << 8) | ((short) packBuffer[2] & 0xff)) /327.0f;fData[25] = ((((short) packBuffer[5]) << 8) | ((short) packBuffer[4] & 0xff)) /327.0f;fData[26] = ((((short) packBuffer[7]) << 8) | ((short) packBuffer[6] & 0xff)) /327.0f;break;}}long lTimeNow = System.currentTimeMillis(); // 获取收据转换之后的时间// 如果数据处理后的时间 与 接收到数据的时间 的时间差>80 则发送消息传输数据,// 这个时间需要看你硬件一秒钟发送的包的个数if (lTimeNow - lLastTime > 80) {lLastTime = lTimeNow;Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_READ);Bundle bundle = new Bundle();bundle.putString("index",String.valueOf(this.index));bundle.putFloatArray("Data", fData);msg.setData(bundle);mHandler.sendMessage(msg);}} catch (IOException e) {connectionLost(this.index);e.printStackTrace();}}}public void cancel() {try {mmSocket.close();} catch (IOException e) {}}}//连接失败private void connectionFailed(int index) {setState(STATE_LISTEN);// Send a failure message back to the ActivityMessage msg = mHandler.obtainMessage(MainActivity.MESSAGE_TOAST);Bundle bundle = new Bundle();bundle.putString("toast", "未能连接设备"+index);bundle.putInt("device_id",index);msg.setData(bundle);mHandler.sendMessage(msg);}// 连接丢失private void connectionLost(int index) {setState(STATE_LISTEN);Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_TOAST);Bundle bundle = new Bundle();bundle.putString("toast", "设备丢失"+index);bundle.putInt("device_id",index);msg.setData(bundle);mHandler.sendMessage(msg);}//用于 蓝牙连接的Activity onResume()方法public synchronized void start() {// Cancel any thread attempting to make a connectionif (mConnectThread != null) {mConnectThread.cancel();mConnectThread = null;}if (mAcceptThread == null) {mAcceptThread = new AcceptThread();mAcceptThread.start();}setState(STATE_LISTEN);}public synchronized void connected(BluetoothSocket socket,BluetoothDevice device,int index) {Log.d("MAGIKARE","连接到线程"+index);// Cancel the thread that completed the connectionif (mConnectThread != null) {mConnectThread.cancel();mConnectThread = null;}// Cancel the accept thread because we only want to connect to one deviceif (mAcceptThread != null) {mAcceptThread.cancel();mAcceptThread = null;}// Start the thread to manage the connection and perform transmissionsmConnectedThread[index-1] = new ConnectedThread(socket,index);mConnectedThread[index-1].start();// Send the name of the connected device back to the UI ActivityMessage msg = mHandler.obtainMessage(MainActivity.MESSAGE_DEVICE_NAME);Bundle bundle = new Bundle();bundle.putString("device_name", device.getName()+" "+index);msg.setData(bundle);mHandler.sendMessage(msg);setState(STATE_CONNECTED);}private synchronized void setState(int state) {mState = state;// Give the new state to the Handler so the UI Activity can updatemHandler.obtainMessage(MainActivity.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();}private class AcceptThread extends Thread {// The local server socketprivate final BluetoothServerSocket mmServerSocket;//private int index;public AcceptThread() {BluetoothServerSocket tmp = null;// this.index=index;// Create a new listening server sockettry {tmp = mAdapter.listenUsingRfcommWithServiceRecord("BluetoothData", MY_UUID);}catch (IOException e) {}mmServerSocket = tmp;}public void run() {new Thread(new Runnable() {@Overridepublic void run() {}}).start();}public void cancel() {try {if(mmServerSocket!=null) {mmServerSocket.close();}}catch (IOException e) {}}}public synchronized int getState() {return mState;}public synchronized void stop() {if (mConnectedThread != null) {for(int i=0;i<mConnectedThread.length;i++){mConnectedThread[i].cancel();}mConnectedThread = null;}if (mAcceptThread != null) {mAcceptThread.cancel();mAcceptThread = null;}setState(STATE_NONE);} }
三、自定义即时变化的折线图:
public class MyView extends View {/*http://www.cnblogs.com/aibuli/p/950c34f2bc0d02cbd290dd6a8339d42a.html*///坐标轴原点的位置private int xPoint=60;private int yPoint=260;//刻度长度private int xScale=8; //8个单位构成一个刻度private int yScale=40;//x与y坐标轴的长度private int xLength=580;private int yLength=480;private int MaxDataSize=xLength/xScale; //横坐标 最多可绘制的点private List<Float> data=new ArrayList<Float>(); //存放 纵坐标 所描绘的点private String[] yLabel=new String[yLength/yScale]; //Y轴的刻度上显示字的集合private Handler mh=new Handler(){public void handleMessage(android.os.Message msg) {if(msg.what==0){ //判断接受消息类型MyView.this.invalidate(); //刷新View }};};public MyView(Context context, AttributeSet attrs) {super(context, attrs);for (int i = 0; i <yLabel.length; i++) {yLabel[i]=(i+1)+"M/s";}new Thread(new Runnable() {@Overridepublic void run() {while(true){ //在线程中不断往集合中增加数据try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}if(data.size()>MaxDataSize){ //判断集合的长度是否大于最大绘制长度data.remove(0); //删除头数据 }// 这里得到蓝牙设备得到的数据float[] floats = MainActivity.GetAngleSpeed(1);data.add(floats[0]);mh.sendEmptyMessage(0); //发送空消息通知刷新 }}}).start();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);Paint paint=new Paint();paint.setStyle(Paint.Style.STROKE);paint.setAntiAlias(true);paint.setColor(Color.RED);//绘制Y轴canvas.drawLine(xPoint, yPoint-yLength, xPoint, yPoint, paint);//绘制Y轴左右两边的箭头canvas.drawLine(xPoint, yPoint-yLength, xPoint-3,yPoint-yLength+6, paint);canvas.drawLine(xPoint, yPoint-yLength, xPoint+3,yPoint-yLength+6, paint);//Y轴上的刻度与文字for (int i = 0; i * yScale< yLength; i++) {canvas.drawLine(xPoint, yPoint-i*yScale, xPoint+5, yPoint-i*yScale, paint); //刻度canvas.drawText(yLabel[i], xPoint-50, yPoint-i*yScale, paint);//文字 }//X轴canvas.drawLine(xPoint, yPoint, xPoint+xLength, yPoint, paint);//如果集合中有数据if(data.size()>1){for (int i = 1; i < data.size(); i++) { //依次取出数据进行绘制canvas.drawLine(xPoint+(i-1)*xScale, yPoint-data.get(i-1)*yScale, xPoint+i*xScale, yPoint-data.get(i)*yScale, paint);}}} }
相关知识:浅谈Bluetooth蓝牙开发
有问题欢迎留言交流!
转载于:https://www.cnblogs.com/xqxacm/p/5909716.html
Android项目实战(二十六):蓝牙连接硬件设备开发规范流程相关推荐
- Android项目实战(十六):QQ空间实现(一)—— 展示说说中的评论内容并有相应点击事件...
大家都玩QQ空间客户端,对于每一个说说,我们都可以评论,那么,对于某一条评论: 白雪公主 回复 小矮人 : 你们好啊~ 我们来分析一下: 1.QQ空间允许我们 点击 回复人和被回复人的名字就可以进入对 ...
- Android 项目必备(十六)--> 手机号 验证码 密码
文章目录 手机号 验证码 密码 在我们的项目中,获取验证码.手机号验证以及密码输入是很常见的小功能. 手机号 1. 布局文件 <EditTextandroid:id="@+id/et_ ...
- 应用程序框架实战二十六:查询对象
信息系统的查询需求千变万化,在仓储中为每个查询需求创建一个特殊方法,将导致大量乏味而臃肿的接口. 一种更加可行的办法是,在应用层服务中描述查询需求,并通过仓储执行查询. 为了能够更好的描述查询需求,可 ...
- Spring boot 项目(二十六)——集成elasticsearch实现简单的书籍搜索
前期准备 本地安装好es:安装教程 项目结构 代码编写 1.pom文件 <dependencies><!--springboot通用--><dependency>& ...
- Android项目实战(十五):自定义不可滑动的ListView和GridView
不可滑动的ListView (RecyclweView类似) public class NoScrollListView extends ListView {public NoScrollListVi ...
- (转载)Android项目实战(二十七):数据交互(信息编辑)填写总结
Android项目实战(二十七):数据交互(信息编辑)填写总结 前言: 项目中必定用到的数据填写需求.比如修改用户名的文字编辑对话框,修改生日的日期选择对话框等等.现总结一下,方便以后使用. 注: 先 ...
- android圆角对话框,Android项目实战(三十二):圆角对话框Dialog
原文: Android项目实战(三十二):圆角对话框Dialog 前言:html 项目中多处用到对话框,用系统对话框太难看,就本身写一个自定义对话框.android 对话框包括:一.圆角程序员 二.a ...
- Vue + Spring Boot 项目实战(十五):动态加载后台菜单
重要链接: 「系列文章目录」 「项目源码(GitHub)」 本篇目录 前言 一.后端实现 1.表设计 2.pojo 3.菜单查询接口(树结构查询) 二.前端实现 1.后台页面设计 2.数据处理 3.添 ...
- 【Android项目实战 | 从零开始写app(十二)】实现app首页智慧服务热门推荐热门主题、新闻
说在前面,由于各种adapter,xml布局,bean实体类,Activity,也为了让看懂,代码基本都是"简单粗暴直接不好看",没啥okhttp和util工具类之类的封装,本篇幅 ...
最新文章
- Monitor Asynchronous Apex
- boost的chrono模块等待按键的测试程序
- PGM中置信传递、和—积算法
- android aar编程,AndroidStudio脚本命令指定AAR生成目录与版本号
- spring social_Spring Social入门
- 软件的可扩展性与框架的可交互性
- 坦克大战-C语言-详注版
- Python 爬取 201865 条《隐秘的角落》弹幕,发现看剧不如爬山?
- DL_C1_week4-1(Build Deep Neural Network)
- c语言正确声明的格式,c语言函数声明(c语言函数声明格式)
- 反馈抑制器使用场景与市场
- C语言之对 0, ‘0‘ , \0 以及 “0“ 的理解
- 文献综述在哪儿能找到?
- yolov7利用onnx进行推理同时调用usb摄像头
- SpringMVC @GetMapping注解路径冲突问题
- Controller层各注解总结
- 社区折腾日志:基于python搭建个人微信/支付宝免签支付功能
- Python笔记之用turtle库绘制三角函数和反三角函数的图像(考研党福利)
- allowMultiQueries 设置为true不生效问题
- 淘宝/天猫API:img2text-图片识别商品接口