一、新建项目
选择Navigation Drawer View Activity
二、安装依赖库
打开app目录下的build.gradle文件
plugins {
id 'com.android.application'
}
android {
namespace 'com.example.bluetoothled'
compileSdk 33
defaultConfig {
applicationId "com.example.bluetoothled"
minSdk 29
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.0'
implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
implementation 'androidx.navigation:navigation-fragment:2.5.3'
implementation 'androidx.navigation:navigation-ui:2.5.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation 'org.videolan.android:libvlc-all:3.4.4'
implementation 'me.aflak.libraries:bluetooth:1.3.9'
}
安装dependence{ }中的依赖库,具体版本可以根据自己的需求改变,但是有可能导致某些库报错。
三、编写布局文件
1.activity_main.xml文件
此文件主要最为目录导航页面,提供界面进行页面跳转。
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<include
android:id="@+id/app_bar_main"
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer" />
</androidx.drawerlayout.widget.DrawerLayout>
2. app_bar.xml文件
此文件提供蓝牙连接活动按钮,进行蓝牙连接。并且此页面浮于content_main.xml文件之上。
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Theme.Bluetoothled.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/Theme.Bluetoothled.PopupOverlay" >
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/content_main" />//导入content_main视图
<CheckBox
android:id="@+id/Bluetooth_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="130dp"
android:layout_marginTop="50dp" />
<CheckBox
android:id="@+id/Focus_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="340dp"
android:layout_marginTop="50dp" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/fab_margin"
android:layout_marginBottom="16dp"
app:srcCompat="@drawable/bt" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
3. content_main.xml文件
提供文件导航
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:showIn="@layout/app_bar_main">
<fragment
android:id="@+id/nav_host_fragment_content_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
4. nav_header_main.xml文件
提供好看的导航背景
<?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"
android:layout_width="match_parent"
android:layout_height="@dimen/nav_header_height"
android:background="@drawable/side_nav_bar"
android:gravity="bottom"
android:orientation="vertical"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<ImageView
android:id="@+id/imageView"
android:layout_width="105dp"
android:layout_height="81dp"
android:contentDescription="@string/nav_header_desc"
android:paddingTop="@dimen/nav_header_vertical_spacing"
app:srcCompat="@drawable/bt" />
</LinearLayout>
5. fragmengt_m01_author.xml文件
主要操作界面布局,主要的蓝牙活动和VLC播放器都放在此页面,作为目录分级下的页面。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.m01.Author.AuthorFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="30dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:text="蓝牙连接:"
android:textStyle="bold" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_weight="1"
android:gravity="center"
android:paddingLeft="15dp"
android:text="对焦状态:"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:orientation="horizontal">
<org.videolan.libvlc.util.VLCVideoLayout
android:id = "@+id/videoLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="horizontal"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<Button
android:id="@+id/auto_focusing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="10dp"
android:text="自动对焦" />
<Button
android:id="@+id/up"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:text="上" />
<Button
android:id="@+id/manual_focusing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginRight="10dp"
android:text="手动对焦" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<Button
android:id="@+id/left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="10dp"
android:text="左" />
<Button
android:id="@+id/stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="10dp"
android:text="停" />
<Button
android:id="@+id/right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:text="右" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<Button
android:id="@+id/pztneg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="10dp"
android:text="近"
tools:ignore="TouchTargetSizeCheck" />
<Button
android:id="@+id/down"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="10dp"
android:text="远" />
<Button
android:id="@+id/pztpos"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:text="近"
tools:ignore="TouchTargetSizeCheck" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="59dp"
android:orientation="horizontal">
<Button
android:id="@+id/slow_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_weight="1"
android:text="慢"
tools:ignore="TouchTargetSizeCheck" />
<Button
android:id="@+id/play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_weight="1"
android:text="播放"
tools:ignore="TouchTargetSizeCheck" />
<Button
android:id="@+id/fast_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_weight="1"
android:text="快"
tools:ignore="TouchTargetSizeCheck" />
</LinearLayout>
</LinearLayout>
四、编写java功能文件
1.MainActivity.java
此文件的主要功能为:
·连接蓝牙
·判断蓝牙连接状态
·根据返回状态打印
package com.example.bluetoothled;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.Menu;
import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;
import com.example.bluetoothled.databinding.FragmentM01AuthorBinding;
import com.example.bluetoothled.ui.m01.Author.AuthorFragment;
import com.example.bluetoothled.util.ByteReader;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.navigation.NavigationView;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.appcompat.app.AppCompatActivity;
import com.example.bluetoothled.databinding.ActivityMainBinding;
import me.aflak.bluetooth.Bluetooth;
public class MainActivity extends AppCompatActivity {
private AppBarConfiguration mAppBarConfiguration;
private ActivityMainBinding binding;
MainViewModel mainModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mainModel= new ViewModelProvider(this).get(MainViewModel.class);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.appBarMain.toolbar);
//Interlocking with the menu (navigation)
DrawerLayout drawer = binding.drawerLayout;
NavigationView navigationView = binding.navView;
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
mAppBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav_author, R.id.nav_thinkercad, R.id.nav_wokwi,R.id.nav_proteus, R.id.nav_led, R.id.nav_joystick)
.setOpenableLayout(drawer)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);
permissionGranted();
Bluetooth bluetooth = new Bluetooth(this);//1
bluetooth.setReader(ByteReader.class); //Need to set Bluetooth byte reader //2
mainModel.setBluetooth(bluetooth); //Received from mainModel Same as setting connect status information.
Log.d("###","main.oncreate");
binding.appBarMain.fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(mainModel.bluetooth.isConnected())
Snackbar.make(view, "HUAWEI_MATE_AF", Snackbar.LENGTH_LONG)
.setAction("Disconnect", v -> {
mainModel.bluetooth.disconnect();
}).show();
else
Snackbar.make(view, "HUAWEI_MATE_AF", Snackbar.LENGTH_LONG)
.setAction("Connect", v->{
mainModel.bluetooth.connectToName("HUAWEI_MATE_AF");
}).show();
}
});
mainModel.bluetoothStatus.observe(this, status->{
Toast.makeText(this, status, Toast.LENGTH_SHORT).show();
if(status.equals("Connected")) {
mainModel.firmata.sendString(status);
binding.appBarMain.BluetoothState.setChecked(true);
}
if (status.equals("received")){
binding.appBarMain.FocusState.setChecked(true);
}
});
}
//3
@Override
protected void onStart() {
super.onStart();
mainModel.bluetooth.onStart();
}
//4
@Override
protected void onStop() {
super.onStop();
mainModel.bluetooth.onStop();
}
@Override
public boolean onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
return NavigationUI.navigateUp(navController, mAppBarConfiguration)
|| super.onSupportNavigateUp();
}
void permissionGranted() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
requestPermissions(
new String[]{
android.Manifest.permission.BLUETOOTH,
android.Manifest.permission.BLUETOOTH_SCAN,
android.Manifest.permission.BLUETOOTH_ADVERTISE,
android.Manifest.permission.BLUETOOTH_CONNECT
},
1);//1是需要的
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(
new String[]{
android.Manifest.permission.BLUETOOTH
},
1);//1是需要的
}
}
}
2.MainViewModel.java
提供判断蓝牙连接状态函数,根据此文件返回值进行后续判断。
package com.example.bluetoothled;
import android.bluetooth.BluetoothDevice;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.example.bluetoothled.util.Firmata;
import com.example.bluetoothled.util.FirmataVersionData;
import me.aflak.bluetooth.Bluetooth;
import me.aflak.bluetooth.interfaces.DeviceCallback;
public class MainViewModel extends ViewModel {
Bluetooth bluetooth;
public Firmata firmata;
public MutableLiveData<String> bluetoothStatus = new MutableLiveData<>();
public MutableLiveData<FirmataVersionData> firmataVersionData = new MutableLiveData<>();
public MainViewModel(){
bluetoothStatus.setValue("Disconnected");
firmataVersionData.setValue(new FirmataVersionData());
}
void setBluetooth(Bluetooth bluetooth){
this.bluetooth = bluetooth;
firmata =new Firmata(this.bluetooth);
bluetooth.setDeviceCallback(new DeviceCallback() {
@Override
public void onDeviceConnected(BluetoothDevice device) {
bluetoothStatus.postValue("Connected");
}
@Override
public void onDeviceDisconnected(BluetoothDevice device, String message) {
bluetoothStatus.postValue("Disconnected");
}
@Override
public void onMessage(byte[] message) {
bluetoothStatus.postValue("received");
}
@Override
public void onError(int errorCode) {
bluetoothStatus.postValue("Error");
}
@Override
public void onConnectError(BluetoothDevice device, String message) {
bluetoothStatus.postValue("Connect Error");
}
});
firmata.attach((m,n)->{
FirmataVersionData version = new FirmataVersionData();
version.setMajor(m);
version.setMinor(n);
firmataVersionData.postValue(version);
});
}
public Bluetooth getBluetooth(){
return bluetooth;
}
}
3.AuthorFragment.java
定义了一些按钮和VLC视频播放器,主要收发功能在此实现。
package com.example.bluetoothled.ui.m01.Author;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import com.example.bluetoothled.MainViewModel;
import com.example.bluetoothled.R;
import com.example.bluetoothled.databinding.FragmentM01AuthorBinding;
import android.net.Uri;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.MediaPlayer;
import org.videolan.libvlc.util.VLCVideoLayout;
public class AuthorFragment extends Fragment {
private FragmentM01AuthorBinding binding;
AuthorViewModel Model;
MainViewModel mainModel;
//private static final String url = "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4";
private static final String url = "rtsp://192.168.1.3:554/";
private LibVLC libVlc;
private MediaPlayer mediaPlayer;
private VLCVideoLayout videoLayout;
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Model = new ViewModelProvider(this).get(AuthorViewModel.class);
mainModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class);
binding = FragmentM01AuthorBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
libVlc = new LibVLC(requireActivity());
mediaPlayer = new MediaPlayer(libVlc);
videoLayout = view.findViewById(R.id.videoLayout);
binding.up.setOnClickListener(v->{
mainModel.firmata.sendSysex("u");
});
binding.down.setOnClickListener(v->{
mainModel.firmata.sendSysex("d");
});
binding.left.setOnClickListener(v->{
mainModel.firmata.sendSysex("l");
});
binding.right.setOnClickListener(v->{
mainModel.firmata.sendSysex("r");
});
binding.stop.setOnClickListener(v->{
mainModel.firmata.sendSysex("s");
});
binding.pztneg.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// 按下时发送命令
mainModel.firmata.sendSysex("N");
Toast.makeText(getContext(),"pressed",Toast.LENGTH_SHORT).show();
} else if (event.getAction() == MotionEvent.ACTION_UP) {
// 松开时发送命令
mainModel.firmata.sendSysex("T");
Toast.makeText(getContext(),"released",Toast.LENGTH_SHORT).show();
}
return false;
}
});
binding.pztpos.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// 按下时发送命令
mainModel.firmata.sendSysex("P");
} else if (event.getAction() == MotionEvent.ACTION_UP) {
// 松开时发送命令
mainModel.firmata.sendSysex("T");
}
return false;
}
});
binding.autoFocusing.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mainModel.firmata.sendSysex("a");
}
});
binding.manualFocusing.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mainModel.firmata.sendSysex("m");
}
});
binding.slowSelect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mainModel.firmata.sendSysex("L");
//bluetoothState.setChecked(true);
//binding.BluetoothState.setChecked(true);
}
});
binding.fastSelect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mainModel.firmata.sendSysex("H");
}
});
binding.play.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startVideoPlayback();
}
});
}
private void startVideoPlayback() {
mediaPlayer.attachViews(videoLayout, null, false, false);
Media media = new Media(libVlc, Uri.parse(url));
media.setHWDecoderEnabled(true, false);
media.addOption(":network-caching=600");
mediaPlayer.setMedia(media);
media.release();
mediaPlayer.play();
}
@Override
public void onStop()
{
super.onStop();
mediaPlayer.stop();
mediaPlayer.detachViews();
}
@Override
public void onDestroy()
{
super.onDestroy();
mediaPlayer.release();
libVlc.release();
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}
4.AuthorViewModel.java
package com.example.bluetoothled.ui.m01.Author;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class AuthorViewModel extends ViewModel {
private final MutableLiveData<String> mText;
public AuthorViewModel() {
mText = new MutableLiveData<>();
mText.setValue("This is home fragment");
}
public LiveData<String> getText() {
return mText;
}
}
总结
具体可以参考提供的项目文件,各个功能块之间分的比较清楚,能够容易看懂。
项目连接:链接:https://pan.baidu.com/s/1JkeSidV0jrrekTUAITEfwg?pwd=gi69
提取码:gi69