常规手机 App 蓝牙页面都是触屏点击 ,但电视、车机、工控机、机顶盒等非触屏 这类设备没有触控,完全依赖上下左右 DPAD 按键 + 焦点切换来操作。
这里分享一套老项目中的纯按键交互蓝牙列表页面,完整包含:蓝牙开关控制、设备扫描、已配对 / 未配对双列表展示、一键配对、断开确认弹窗、A2dp/Headset 双协议自动连接、按键焦点上下左右联动、焦点选中态 UI 变化。
全程基于原生蓝牙广播监听、反射调用系统隐藏方法完成配对与断开,无需第三方库,布局、权限、Activity、Adapter、按键事件全套代码完整,可直接复制到项目复用,非常适合TV / 车载 / 嵌入式安卓设备开发参考。
首先在AndroidManifest.xml添加蓝牙权限
xml
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!--Android 6.0+ 扫描其实还需要 ACCESS_FINE_LOCATION(系统要求)-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
布局文件
ini
<?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="match_parent"
android:background="@color/black"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/x2"
android:text=""
android:textColor="@color/white"
android:textSize="@dimen/x11" />
<TextView
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginTop="@dimen/x3"
android:background="@drawable/line_gradient" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_bt_title"
android:layout_width="match_parent"
android:layout_height="@dimen/x24"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_refresh"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/btn_color"
android:focusable="true"
android:gravity="center"
android:text="刷新"
android:textColor="@color/white"
android:textSize="@dimen/x12" />
<TextView
android:id="@+id/tv_open"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/orange"
android:focusable="true"
android:gravity="center"
android:text="开启"
android:textColor="@color/white"
android:textSize="@dimen/x12" />
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_tips_bluetooth"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="请开启蓝牙"
android:textColor="@color/white"
android:textSize="@dimen/x12"
android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/pairRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/pairRecyclerView"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<LinearLayout
android:id="@+id/ll_break_link"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:gravity="center_horizontal"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:id="@+id/tv_break_link"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="5px"
android:paddingRight="5px"
android:text="确定断开?"
android:textColor="@color/white"
android:textSize="@dimen/x11" />
<TextView
android:id="@+id/tv_confirm"
android:layout_width="@dimen/x60"
android:layout_height="@dimen/x20"
android:layout_marginTop="@dimen/x12"
android:background="@drawable/bg_select_btn"
android:focusable="true"
android:gravity="center"
android:text="确认"
android:textColor="@color/white"
android:textSize="@dimen/x11" />
<TextView
android:id="@+id/tv_cancel_pd"
android:layout_width="@dimen/x60"
android:layout_height="@dimen/x20"
android:layout_marginTop="@dimen/x5"
android:layout_marginBottom="@dimen/x15"
android:background="@drawable/bg_unselect_btn"
android:focusable="true"
android:gravity="center"
android:text="取消"
android:textColor="@color/orange"
android:textSize="@dimen/x11" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>
java代码
ini
public class BluetoothActivity extends BaseActivity<ActivityBluetoothBinding> {
TextView tv_title;
LinearLayout ll_bt_title;
TextView tv_refresh, tv_open; //刷新 打开
TextView tv_tips_bluetooth; //蓝牙状态提示
LinearLayout ll_break_link; //断开链接
TextView tv_break_link; //是否与"xxx"断开链接
TextView tv_confirm,tv_cancel; //确认 取消
RecyclerView mRecyclerView, mPairRecyclerView;
CollectionAdapter mAdapter, mPairAdapter;
private BluetoothAdapter mBluetoothAdapter;
boolean select_ly = true; //选择蓝牙
boolean isShowDialog = false; //弹窗时
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bluetooth);
//隐藏状态栏
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
tv_title = findViewById(R.id.tv_title);
tv_title.setText("蓝牙");
ll_bt_title = findViewById(R.id.ll_bt_title);
tv_refresh = findViewById(R.id.tv_refresh);
tv_open = findViewById(R.id.tv_open);
tv_tips_bluetooth = findViewById(R.id.tv_tips_bluetooth);
ll_break_link = findViewById(R.id.ll_break_link);
tv_break_link = findViewById(R.id.tv_break_link);
tv_confirm = findViewById(R.id.tv_confirm);
tv_cancel = findViewById(R.id.tv_cancel_pd);
initRecyclerView();
initBluetooth();
initUIFocus();
tv_open.requestFocus();
}
@Override
protected void init() {
}
@Override
public int setLayoutID() {
return R.layout.activity_bluetooth;
}
private void initRecyclerView() {
/*未配对RecyclerView*/
mAdapter = new CollectionAdapter(this, mUnPairClickListener, false);
mRecyclerView = findViewById(R.id.recyclerView);
mRecyclerView.setAdapter(mAdapter);
/*已配对*/
mPairAdapter = new CollectionAdapter(this, mPairClickListener, true);
mPairRecyclerView = findViewById(R.id.pairRecyclerView);
mPairRecyclerView.setAdapter(mPairAdapter);
}
private void initBluetooth(){
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
Toast.makeText(BluetoothActivity.this, "此设备不支持蓝牙", Toast.LENGTH_SHORT).show();
return;
}
if (mBluetoothAdapter.isEnabled()) {
mUIState = UI_STATE_ENABLE;
startScan();
} else {
mUIState = UI_STATE_DISABLE;
}
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);//开始扫描
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//扫描完成
filter.addAction(BluetoothAdapter.ACTION_REQUEST_ENABLE);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);//蓝牙开关监听
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);//配对监听
filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);//蓝牙名称监听
registerReceiver(mBluetoothReceiver, filter);
if (mBluetoothAdapter.isEnabled()) { //蓝牙已开启
tv_open.setText("关闭");
mHandler.sendEmptyMessage(1);
mHandler.sendEmptyMessage(0);
}else {
tv_open.setText("开启");
tv_tips_bluetooth.setText("请开启蓝牙");
mHandler.sendEmptyMessage(2); //显示提示
}
Log.e(TAG, "bt name: "+ mBluetoothAdapter.getName());
}
/*开始扫描蓝牙*/
private void startScan() {
if (mBluetoothAdapter.isEnabled()) {
mPairAdapter.mDatas.clear();
mPairAdapter.mDatas.addAll(mBluetoothAdapter.getBondedDevices());
mPairAdapter.notifyDataSetChanged();
mBluetoothAdapter.cancelDiscovery();
mBluetoothAdapter.startDiscovery();
}
}
public void onSwitchClick() {
if (isOpening) {
return;
}
if (mBluetoothAdapter.isEnabled()) {
//关闭蓝牙
mBluetoothAdapter.cancelDiscovery();
mBluetoothAdapter.disable();
tv_open.setText("开启");
tv_tips_bluetooth.setText("请开启蓝牙");
mHandler.sendEmptyMessage(2);//显示提示
} else {
/*打开蓝牙*/
isOpening = true;
tv_tips_bluetooth.setText("蓝牙搜索中...");
mBluetoothAdapter.enable();
tv_open.setText("关闭");
mHandler.sendEmptyMessageDelayed(1, 1500); //隐藏提示
mHandler.sendEmptyMessageDelayed(0, 1500); //显示蓝牙列表
}
}
int mUIState = UI_STATE_DISABLE;
private static final String TAG = "TAG";
public static final int UI_STATE_DISABLE = 0;//未开启
public static final int UI_STATE_ENABLE = 1;//已开启
public boolean isOpening = false;//正在打开中..
public BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null) {
switch (intent.getAction()) {
/*找到蓝牙设备*/
case BluetoothDevice.ACTION_FOUND: //搜索中
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (!mBluetoothAdapter.getBondedDevices().contains(device) && !mAdapter.mDatas.contains(device) && !TextUtils.isEmpty(device.getName())) {
Log.e(TAG, "名称:" + device.getName() + ",mac:" + device.getAddress());
mAdapter.addNewBluetoothDevice(device);
}
break;
case BluetoothDevice.ACTION_PAIRING_REQUEST:
Log.e(TAG, "BluetoothDevice.ACTION_PAIRING_REQUEST");
break;
case BluetoothDevice.ACTION_NAME_CHANGED:
Log.e(TAG, "BluetoothDevice.ACTION_NAME_CHANGED");
break;
case BluetoothAdapter.ACTION_DISCOVERY_STARTED: //开始搜索
Log.e(TAG, "onReceive: BluetoothAdapter.ACTION_DISCOVERY_STARTED");
break;
case BluetoothAdapter.ACTION_DISCOVERY_FINISHED: //搜索完成
Log.e(TAG, "onReceive: BluetoothAdapter.ACTION_DISCOVERY_FINISHED");
if (mPairAdapter.getItemCount() == 0 && mAdapter.getItemCount()==0) {
tv_tips_bluetooth.setText("未找到可用设备");
mHandler.sendEmptyMessage(2);//显示提示
}
break;
case BluetoothAdapter.ACTION_REQUEST_ENABLE:
Log.e(TAG, "onReceive: BluetoothAdapter.ACTION_REQUEST_ENABLE");
break;
case BluetoothAdapter.ACTION_STATE_CHANGED:
Log.e(TAG, "onReceive: BluetoothAdapter.ACTION_STATE_CHANGED");
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
switch (state) {
case BluetoothAdapter.STATE_OFF: //关闭蓝牙
Log.e(TAG, "onReceive: BluetoothAdapter.STATE_OFF");
mAdapter.mDatas.clear();
mAdapter.notifyDataSetChanged();
mUIState = UI_STATE_DISABLE;
break;
case BluetoothAdapter.STATE_TURNING_ON:
break;
case BluetoothAdapter.STATE_TURNING_OFF:
break;
case BluetoothAdapter.STATE_ON: //开启蓝牙
Log.e(TAG, "onReceive: BluetoothAdapter.STATE_ON");
mUIState = UI_STATE_ENABLE;
isOpening = false;
startScan();
break;
}
break;
/*配对*/
case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
BluetoothDevice pairDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
String tip = "";
switch (bondState) {
case BluetoothDevice.BOND_BONDED:
tip = "BOND_BONDED";//配对完成
/*添加配对的数据*/
mPairAdapter.addNewBluetoothDevice(pairDevice);
/*从未配对列表中移除*/
mAdapter.mDatas.remove(mAdapter.pairPosition);
mAdapter.notifyItemRemoved(mAdapter.pairPosition);
mAdapter.pairPosition = -1;
mAdapter.mPairingDevice = null;
//add auto connect
connectBT(BluetoothActivity.this,pairDevice);
break;
case BluetoothDevice.BOND_BONDING:
tip = "BOND_BONDING";//配对中
break;
case BluetoothDevice.BOND_NONE:
tip = "BOND_NONE";//取消配对
if (mPairAdapter.cancelBondPos == -1) {
mAdapter.mPairingDevice = null;
mAdapter.notifyItemChanged(mAdapter.pairPosition);
mAdapter.pairPosition = -1;
Toast.makeText(BluetoothActivity.this,"配对失败",Toast.LENGTH_SHORT).show();
} else if (mPairAdapter.cancelBondPos != -1) {//取消配对
BluetoothDevice cancelBondDevice = mPairAdapter.mDatas.get(mPairAdapter.cancelBondPos);
mAdapter.mDatas.add(cancelBondDevice);
mAdapter.notifyDataSetChanged();
mPairAdapter.mDatas.remove(mPairAdapter.cancelBondPos);
mPairAdapter.cancelBondPos = -1;
mPairAdapter.notifyDataSetChanged();
}
break;
}
Log.e(TAG, "ACTION_BOND_STATE_CHANGED: " + tip);
break;
/*本地蓝牙名称发生改变*/
case BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED:
// tvBluetoothName.setText(intent.getStringExtra(BluetoothAdapter.EXTRA_LOCAL_NAME));
break;
}
}
}
};
BluetoothHeadset bluetoothHeadset;
BluetoothA2dp bluetoothA2dp;
BluetoothDevice mPairDevice;
BluetoothProfile.ServiceListener serviceListener = new BluetoothProfile.ServiceListener() {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
Log.e(TAG, profile+":onServiceConnected " + mPairDevice);
if(profile == BluetoothProfile.HEADSET) {
bluetoothHeadset = (BluetoothHeadset) proxy;
connectHeadSetDevice(mPairDevice);
}else if(profile == BluetoothProfile.A2DP) {
bluetoothA2dp = (BluetoothA2dp) proxy;
connectA2dpDevice(mPairDevice);
}
}
@Override
public void onServiceDisconnected(int profile) {
Log.e(TAG, "onServiceDisconnected " + profile);
if(profile == BluetoothProfile.HEADSET) {
bluetoothHeadset = null;
}else if(profile == BluetoothProfile.A2DP) {
bluetoothA2dp = null;
}
}
};
/**
* 连接已配对蓝牙设备
* @pairDevice
*/
private boolean connectBT(Context context, BluetoothDevice pairDevice) {
if (pairDevice.getBluetoothClass().getMajorDeviceClass() !=
BluetoothClass.Device.Major.AUDIO_VIDEO) {
return true;
}
mPairDevice = pairDevice;
Log.e(TAG,"connectBT "+bluetoothHeadset);
if(bluetoothHeadset!=null){
connectHeadSetDevice(pairDevice);
}else {
mBluetoothAdapter.getProfileProxy(context, serviceListener, BluetoothProfile.HEADSET);
}
if(bluetoothA2dp!=null){
connectA2dpDevice(pairDevice);
}else {
mBluetoothAdapter.getProfileProxy(context, serviceListener, BluetoothProfile.A2DP);
}
return false;
}
/**
* 连接headset协议
* @param pairDevice
*/
private void connectHeadSetDevice(BluetoothDevice pairDevice) {
Class btHeadsetCls = BluetoothHeadset.class;
try {
Method connect = btHeadsetCls.getMethod("connect", BluetoothDevice.class);
connect.setAccessible(true);
connect.invoke(bluetoothHeadset, pairDevice);
} catch (Exception e) {
Log.e(TAG, e + "");
}
}
/**
* 连接A2DP媒体协议
* @param pairDevice
*/
private void connectA2dpDevice(BluetoothDevice pairDevice) {
Class btA2dpCls = BluetoothA2dp.class;
try {
Method connect = btA2dpCls.getMethod("connect", BluetoothDevice.class);
connect.setAccessible(true);
connect.invoke(bluetoothA2dp, pairDevice);
} catch (Exception e) {
Log.e(TAG, e + "");
}
}
Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 0: //显示蓝牙列表
mRecyclerView.setVisibility(View.VISIBLE);
mPairRecyclerView.setVisibility(View.VISIBLE);
break;
case 1: //隐藏提示
tv_tips_bluetooth.setVisibility(View.GONE);
break;
case 2: //显示提示
tv_tips_bluetooth.setVisibility(View.VISIBLE);
mPairRecyclerView.setVisibility(View.GONE);
mRecyclerView.setVisibility(View.GONE);
break;
case 3: //隐藏取消布局 显示头部
ll_break_link.setVisibility(View.GONE);
ll_bt_title.setVisibility(View.VISIBLE);
isShowDialog = false;
break;
}
return true;
}
});
public View.OnClickListener mUnPairClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = ((int) v.getTag());
BluetoothDevice device = mAdapter.mDatas.get(pos);
if (mAdapter.mPairingDevice == null) {
mAdapter.pairPosition = pos;
mAdapter.mPairingDevice = device;
mBluetoothAdapter.cancelDiscovery();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
device.createBond();
mAdapter.notifyItemChanged(pos);
}
}
}
};
public View.OnClickListener mPairClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) { //取消配对
mPairAdapter.cancelBondPos = ((int) v.getTag());
final BluetoothDevice device = mPairAdapter.mDatas.get(mPairAdapter.cancelBondPos);
mRecyclerView.setVisibility(View.GONE);
mPairRecyclerView.setVisibility(View.GONE);
ll_bt_title.setVisibility(View.GONE);
ll_break_link.setVisibility(View.VISIBLE);
tv_break_link.setText("确定与设备""+device.getName()+""断开连接吗?");
isShowDialog = true;
tv_confirm.requestFocus();
}
};
private class CollectionAdapter extends RecyclerView.Adapter<CollectionAdapter.Holder> {
public List<BluetoothDevice> mDatas = new ArrayList();
public Context mContext;
public BluetoothDevice mPairingDevice;//配对中蓝牙
public boolean isPair;
public int pairPosition = -1;//配对的位置
public int cancelBondPos = -1;//取消配对的位置
public int mCurPos = 0;//当前选中的位置
public int mPosSomeTime = 0;//临时选中
private View.OnClickListener mListener;
public CollectionAdapter(Context context, View.OnClickListener listener, boolean isPair) {
this.mContext = context;
this.mListener = listener;
this.isPair = isPair;
}
@Override
public CollectionAdapter.Holder onCreateViewHolder( ViewGroup parent, int viewType) {
return new CollectionAdapter.Holder(LayoutInflater.from(mContext).inflate(R.layout.item_bluetooth, parent, false));
}
@Override
public void onBindViewHolder( CollectionAdapter.Holder holder, int position) {
holder.upateData(position, mDatas.get(position));
}
@Override
public int getItemCount() {
return mDatas.size();
}
public void addNewBluetoothDevice(BluetoothDevice device) {
mDatas.add(device);
notifyItemChanged(mDatas.size() - 1);
}
public class Holder extends RecyclerView.ViewHolder {
RelativeLayout item_unit;
TextView menuitem;
TextView checked;
public Holder(View itemView) {
super(itemView);
menuitem = itemView.findViewById(R.id.menuitem);
checked = itemView.findViewById(R.id.checked);
itemView.setOnClickListener(mListener);
}
public void upateData(int position,BluetoothDevice bluetooth) {
itemView.setTag(position);
menuitem.setText(bluetooth.getName());
if(position == mPosSomeTime){
checked.setVisibility(View.VISIBLE);
itemView.requestFocus();
}else {
checked.setVisibility(View.GONE);
}
if (isPair) {
checked.setVisibility(View.VISIBLE);
if (cancelBondPos != position) {
checked.setText("已配对");
}
} else {
if (bluetooth == mPairingDevice) {
checked.setText("配对中");
checked.setVisibility(View.VISIBLE);
} else {
checked.setVisibility(View.GONE);
}
}
itemView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if(hasFocus){
menuitem.setMarqueeRepeatLimit(Integer.MAX_VALUE);
menuitem.setEllipsize(TextUtils.TruncateAt.MARQUEE);
menuitem.setFocusableInTouchMode(true);
menuitem.setHorizontallyScrolling(true);
menuitem.setSelected(true);
}else {
menuitem.setSelected(false);
}
}
});
}
}
}
@Override
protected void onDestroy() {
if(bluetoothHeadset!=null){
mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET,bluetoothHeadset);
}
if(bluetoothA2dp!=null){
mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP,bluetoothA2dp);
}
mBluetoothAdapter.cancelDiscovery();
unregisterReceiver(mBluetoothReceiver);
super.onDestroy();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK: //返回键
if(isShowDialog){
isShowDialog = false;
mHandler.sendEmptyMessage(3);
mHandler.sendEmptyMessage(0);
return true;
}
break;
case KeyEvent.KEYCODE_DPAD_UP: //向上键
// Log.d("TAG","KEYCODE_DPAD_UP--->");
break;
case KeyEvent.KEYCODE_DPAD_DOWN: //向下键
if(getCurrentFocus().getId() == R.id.tv_refresh && mPairAdapter != null && mPairAdapter.getItemCount() > 0){
mPairRecyclerView.getChildAt(0).requestFocus();
return true;
}
break;
case KeyEvent.KEYCODE_DPAD_LEFT: //向左键
// Log.e("TAG","left--->");
if(!isShowDialog){
tv_refresh.requestFocus();
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT: //向右键
// Log.e("TAG","right--->");
if(!isShowDialog){
tv_open.requestFocus();
}
break;
}
return super.onKeyDown(keyCode, event);
}
private void initUIFocus(){
tv_open.setOnClickListener(new View.OnClickListener() { //打开 关闭蓝牙
@Override
public void onClick(View v) {
select_ly = true;
tv_open.setBackgroundColor(getResources().getColor(R.color.orange));
tv_refresh.setBackgroundColor(getResources().getColor(R.color.btn_color));
onSwitchClick();
}
});
tv_refresh.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
select_ly = false;
tv_open.setBackgroundColor(getResources().getColor(R.color.btn_color));
tv_refresh.setBackgroundColor(getResources().getColor(R.color.orange));
if (!mBluetoothAdapter.isEnabled()) {
Toast.makeText(BluetoothActivity.this, "请先开启蓝牙", Toast.LENGTH_SHORT).show();
}else {
mHandler.sendEmptyMessage(2); //显示提示
tv_tips_bluetooth.setText("蓝牙搜索中...");
mHandler.sendEmptyMessageDelayed(1, 1500); //隐藏提示
mHandler.sendEmptyMessageDelayed(0, 1500); //显示蓝牙列表
}
}
});
tv_open.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus){
if(select_ly){
tv_open.setBackground(getResources().getDrawable(R.drawable.shape_cd));
}else {
tv_open.setBackground(getResources().getDrawable(R.drawable.shape_scb));
}
}else {
if(select_ly){
tv_open.setBackgroundColor(getResources().getColor(R.color.orange));
}else {
tv_open.setBackgroundColor(getResources().getColor(R.color.btn_color));
}
}
}
});
tv_refresh.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus){
if(!select_ly){
tv_refresh.setBackground(getResources().getDrawable(R.drawable.shape_cd));
}else {
tv_refresh.setBackground(getResources().getDrawable(R.drawable.shape_scb));
}
}else {
if(!select_ly){
tv_refresh.setBackgroundColor(getResources().getColor(R.color.orange));
}else {
tv_refresh.setBackgroundColor(getResources().getColor(R.color.btn_color));
}
}
}
});
tv_confirm.setOnFocusChangeListener(new View.OnFocusChangeListener() { //确认
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus){
tv_confirm.setBackground(getResources().getDrawable(R.drawable.bg_select_btn));
tv_confirm.setTextColor(getResources().getColor(R.color.white));
}else {
tv_confirm.setBackground(getResources().getDrawable(R.drawable.bg_unselect_btn));
tv_confirm.setTextColor(getResources().getColor(R.color.orange));
}
}
});
tv_cancel.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus){
tv_cancel.setBackground(getResources().getDrawable(R.drawable.bg_select_btn));
tv_cancel.setTextColor(getResources().getColor(R.color.white));
}else {
tv_cancel.setBackground(getResources().getDrawable(R.drawable.bg_unselect_btn));
tv_cancel.setTextColor(getResources().getColor(R.color.orange));
}
}
});
tv_confirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mHandler.sendEmptyMessage(3);
mHandler.sendEmptyMessage(0); //显示蓝牙列表
BluetoothDevice device = mPairAdapter.mDatas.get(mPairAdapter.cancelBondPos);
mPairAdapter.notifyItemChanged(mPairAdapter.cancelBondPos);
try {
Method method = device.getClass().getMethod("removeBond");
method.invoke(device);
} catch (Exception e) {
e.printStackTrace();
}
}
});
tv_cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mHandler.sendEmptyMessage(3);
mHandler.sendEmptyMessage(0); //显示蓝牙列表
}
});
}
}
代码解释:
1、onKeyDown 方法:纯按键设备必须手动控制焦点流转,按上 / 下 / 左 / 右自动跳到下一个可聚焦控件,弹窗弹出时锁定焦点只在确认、取消之间切换。
2、蓝牙全量广播注册:覆盖蓝牙所有生命周期事件,实现 UI 自动同步状态,不用轮询。
3、反射实现配对 / 断开蓝牙:系统原生 API 不对外提供断开、主动连接方法,必须用反射调用隐藏接口,这是蓝牙开发常用高阶技巧。
4、已配对 / 未配对 双列表设计:这是产品标准交互设计思路,很适合新手学习业务分层。
5、onDestroy 里关闭蓝牙协议代理、停止扫描、解注册广播,避免内存泄漏和后台持续耗电,这是代码规范。
item_bluetooth.xml
ini
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/item_unit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/selector1"
android:focusable="true"
android:gravity="center_vertical">
<TextView
android:id="@+id/checked"
android:layout_width="wrap_content"
android:layout_height="@dimen/x22"
android:layout_alignParentRight="true"
android:textColor="@color/white"
android:gravity="center_vertical"
android:textSize="@dimen/x9"
android:layout_marginRight="@dimen/x1" />
<TextView
android:id="@+id/menuitem"
android:layout_width="match_parent"
android:layout_height="@dimen/x22"
android:layout_marginLeft="@dimen/x2"
android:gravity="center_vertical"
android:layout_toLeftOf="@id/checked"
android:paddingRight="@dimen/x1"
android:singleLine="true"
android:ellipsize="end"
android:text=""
android:textColor="@color/white"
android:textSize="@dimen/x9" />
</RelativeLayout>
蓝牙交互流程图
打开页面 → 蓝牙开关(焦点默认落此处)
↓
开启蓝牙 → 自动扫描 → 已配对列表 + 未配对列表
↓
未配对设备 → 按OK键 → createBond() → 系统弹配对框 → BOND_BONDED → 自动 connect
↓
已配对设备 → 按OK键 → 弹出断开确认 → 确认 → removeBond() → 移回未配对列表