无触摸屏场景下的蓝牙交互:Android 纯按键蓝牙扫描配对与 A2DP/Headset 连接

常规手机 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() → 移回未配对列表

相关推荐
计算机安禾1 小时前
【算法设计与分析】第29篇:启发式与元启发式搜索方法综述
java·数据库·算法
DIY源码阁1 小时前
JavaSwing学生选课系统 - MySQL版
java·数据库·mysql·eclipse
砍材农夫1 小时前
物联网实战:Spring Boot + Netty 搭建 MQTT | MQTT 设备模拟器
java·spring boot·后端·物联网·struts·spring·netty
城管不管1 小时前
Agent——001
android·java·数据库·llm·prompt
AC赳赳老秦1 小时前
OpenClaw批量任务队列优化:解决任务堆积、执行缓慢、优先级混乱问题
java·大数据·数据库·c++·自动化·php·openclaw
Pluchon1 小时前
萌萌技术分享笔记——Java综合项目
java·开发语言·笔记·git·github·mybatis·postman
TDengine (老段)1 小时前
TDengine Compaction 合并策略 — STT 整理、文件合并与后台调度
java·大数据·数据库·物联网·时序数据库·tdengine·涛思数据
J2虾虾2 小时前
Spring AI Alibaba - 多智能体(Multi-agent)
java·人工智能·spring