我们在前面已经分析了Android启动中涉及蓝牙的各个方面,今天我们着重来看看,在蓝牙打开之前,我们能看到的蓝牙UI有哪些,这些UI又是如何实现的。

1,settings中UI的分析

首先,最常见的也是我们通常情况下最新看到的,它就是Settings中蓝牙的显示代码,具体的图片如下:

 

图1,默认settings中的界面

这个界面的实现是在这个文件中:/packages/apps/Settings/res/xml/settings_headers.xml。它采用的是preference-headers来实现的,这样的实现好处就在于可以匹配不同的屏幕,比如pad和phone。我们来看一下,你就会发现其实还是蛮简单的:

<preference-headers
xmlns:android="http://schemas.android.com/apk/res/android"> <!--这个就是那个“无线和网络”五个字了 -->
<!-- WIRELESS and NETWORKS -->
<header android:title="@string/header_category_wireless_networks" />
<!--这个是wifi --> <!-- Wifi -->
<header
android:id="@+id/wifi_settings"
android:fragment="com.android.settings.wifi.WifiSettings"
android:title="@string/wifi_settings_title"
android:icon="@drawable/ic_settings_wireless" />
<!--这个是bluetooth --> <!-- Bluetooth -->
<header
android:id="@+id/bluetooth_settings"
<!—-这里的fragment是比较重要的-->
android:fragment="com.android.settings.bluetooth.BluetoothSettings"
android:title="@string/bluetooth_settings_title"
android:icon="@drawable/ic_settings_bluetooth2" />
……

要显示这个preference-headers,需要重新实现 onBuildHeaders回调方法,毫无疑问,肯定是实现过了,我们来看一下具体的代码:

 @Override
public void onBuildHeaders(List<Header> headers) {
if(UNIVERSEUI_SUPPORT){
loadHeadersFromResource(R.xml.settings_headers_uui, headers);
}else{
//load的preference-headers xml文件
loadHeadersFromResource(R.xml.settings_headers, headers);
} //这个会根据支持的features来决定是否需要把一些list去除掉
updateHeaderList(headers); mHeaders = headers;
}

这样来看,这个preference-headers的显示还是比较简单的,细心的同学会发现,上面header只有title和icon啊,我们在界面上还有一个开关,这里怎么没有啊?呵呵,好问题,其实上面的代码并不是真正的UI上的显示代码,真正的UI显示代码在哪里呢,我们来慢慢看。

我们知道settings其实最终调用的是setListAdapter,那么这个地方是如何实现的呢?我们来看源码:

  public void setListAdapter(ListAdapter adapter) {
if (mHeaders == null) {
mHeaders = new ArrayList<Header>();
// When the saved state provides the list of headers, onBuildHeaders is not called
// Copy the list of Headers from the adapter, preserving their order
for (int i = 0; i < adapter.getCount(); i++) {
mHeaders.add((Header) adapter.getItem(i));
}
} // Ignore the adapter provided by PreferenceActivity and substitute ours instead
//重点要关注这里,看HeaderAdapter是如何构建的
super.setListAdapter(new HeaderAdapter(this, mHeaders));
}

来看一下HeaderAdapter的构造

 public HeaderAdapter(Context context, List<Header> objects) {
super(context, 0, objects);
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); // Temp Switches provided as placeholder until the adapter replaces these with actual
// Switches inflated from their layouts. Must be done before adapter is set in super
//从注释来看,这里只是占位而已,后面会被layout中的内容真正地覆盖的,我们后面会详细分析
mWifiEnabler = new WifiEnabler(context, new Switch(context));
//这里就是要构造我们的BluetoothEnabler了,这个在1.1中进行分析,这里可以理解为蓝牙那边的一些初始化,那边的分析会陷入进去比较多,若是想从整体上先理解,请跳过1.1,直接看后面1.2的内容
mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
}

1.1 BluetoothEnabler的分析

BluetoothEnabler主要是用来管理蓝牙的on off的开关的。

public BluetoothEnabler(Context context, Switch switch_) {
mContext = context;
mSwitch = switch_;
//local bluetooth manager就是在bluetooth api上面提供一个简单的接口
//详见1.1.1分析
LocalBluetoothManager manager = LocalBluetoothManager.getInstance(context);
if (manager == null) {
// Bluetooth is not supported
mLocalAdapter = null;
mSwitch.setEnabled(false);
} else {
mLocalAdapter = manager.getBluetoothAdapter();
}
//加入对ACTION_STATE_CHANGED和ACTION_AIRPLANE_MODE_CHANGED的action的处理
mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
mIntentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
//btwifi的conexist是否被置位。若是没有,意味着wifi和bt只能有一个,则需要加一些action的处理
if (SystemProperties.get("ro.btwifi.coexist", "true").equals("false")) {
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
mSupportBtWifiCoexist = false;
}
}

1.1.1 LocalBluetoothManager的分析

local bluetooth manager就是在bluetooth api上面提供一个简单的接口。也就是说他是封装在bluetooth提供的api之上的。

public static synchronized LocalBluetoothManager getInstance(Context context) {
if (sInstance == null) {
//调用LocalBluetoothAdapter,调用api得到对应的bluetooth adapter
LocalBluetoothAdapter adapter = LocalBluetoothAdapter.getInstance();
if (adapter == null) {
return null;
}
// This will be around as long as this process is
//得到整个应该的生命周期,所以运行够长时间
Context appContext = context.getApplicationContext();
//新建LocalBluetoothManager
sInstance = new LocalBluetoothManager(adapter, appContext);
} return sInstance;
} private LocalBluetoothManager(LocalBluetoothAdapter adapter, Context context) {
mContext = context;
mLocalAdapter = adapter;
//新建CachedBluetoothDeviceManager,用来管理远端设备的,就是对端
mCachedDeviceManager = new CachedBluetoothDeviceManager(context);
// BluetoothEventManager用来管理从bluetooth API那边传过来的broadcast和callback,并把他们分配到对应的class中去,详见1.1.2
mEventManager = new BluetoothEventManager(mLocalAdapter,
mCachedDeviceManager, context);
//用来管理对bluetooth profile的访问的,详见1.1.3
mProfileManager = new LocalBluetoothProfileManager(context,
mLocalAdapter, mCachedDeviceManager, mEventManager);
}

1.1.2BluetoothEventManager的分析

上文已经讲过了,bluetoothEventManager是用来管理api那边传过来的broadcast和callback,他会根据各个broadcast进行最终的分配,我们来了解一下它究竟关注了哪些broadcast和callback。

 BluetoothEventManager(LocalBluetoothAdapter adapter,
CachedBluetoothDeviceManager deviceManager, Context context) {
mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mAdapterIntentFilter = new IntentFilter();
mProfileIntentFilter = new IntentFilter();
mHandlerMap = new HashMap<String, Handler>();
mContext = context; // Bluetooth on/off broadcasts
// ACTION_STATE_CHANGED,在蓝牙的on和off的时候会发出
addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());
//这两个是扫描的broadcast,分别表示开始扫描和停止扫描
// Discovery broadcasts
addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false));
//这是扫描到设备和设备消失的broadcast
addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());
addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler());
//这个是设备名字改变的action
addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler()); // Pairing broadcasts
//这个是设备配对状态改变的action,比如正在配对,已经配对之类的
addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());
//取消配对的handler
addHandler(BluetoothDevice.ACTION_PAIRING_CANCEL, new PairingCancelHandler()); // Fine-grained state broadcasts
//CLASS和UUID改变的action
addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler()); // Dock event broadcasts
//dock的event
addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
//注册对这些action处理的receiver
mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter);
}

1.1.3 LocalBluetoothProfileManager的分析

LocalBluetoothProfileManager是用来访问支持的bluetoothprofile的LocalBluetoothProfile的。具体的代码如下:

 LocalBluetoothProfileManager(Context context,
LocalBluetoothAdapter adapter,
CachedBluetoothDeviceManager deviceManager,
BluetoothEventManager eventManager) {
mContext = context; mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mEventManager = eventManager;
// pass this reference to adapter and event manager (circular dependency)
//和localadapter以及eventmanager关联
mLocalAdapter.setProfileManager(this);
mEventManager.setProfileManager(this); ParcelUuid[] uuids = adapter.getUuids(); // uuids may be null if Bluetooth is turned off
if (uuids != null) {
//根据uuid刷新我们支持的profile,在蓝牙off的状态下(从没有打开过的情况下),他应该是null,这里我就暂时不详细介绍了,会在后面的文章中再详细介绍
updateLocalProfiles(uuids);
} // Always add HID and PAN profiles
//HID和PAN总是会加入的,具体的后面的文章用到再详细介绍
mHidProfile = new HidProfile(context, mLocalAdapter);
addProfile(mHidProfile, HidProfile.NAME,
BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); mPanProfile = new PanProfile(context);
addPanProfile(mPanProfile, PanProfile.NAME,
BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); Log.d(TAG, "LocalBluetoothProfileManager construction complete");
}

这里,我们总结一下,BluetoothEnabler构造所涉及的各类和他们的主要作用:

1)BluetoothEnabler—用于管理蓝牙的on/off开关操作。

2)LocalBluetoothManager—在framework的bluetooth api之上进行了重新封装,向该应用本身提供了一些简单的接口。

3)CachedBluetoothDeviceManager—用于管理远端设备的类,比如耳机,鼠标等

4)BluetoothEventManager—用于管理从framework的bluetooth api那边上来的broadcast和callback,并把这些反馈到对应的class中去。

5)LocalBluetoothProfileManager—管理对各个bluetoothprofile的访问和操作

1.2真正的开关实现

基本到bluetooth中兜了一圈,我们还是没有发现任何和那个开关相关的内容。没有关系,我们继续来分析Settings中的内容,我们突然发现它重写了getView,哈哈,大家都知道PreferenceActivity中每个list都是通过getView来得到对应要显示的内容的,所以我们有必要来看看这个内容。

   @Override
public View getView(int position, View convertView, ViewGroup parent) {
HeaderViewHolder holder;
//根据postition得到对应item
Header header = getItem(position);
//得到type,wifi和蓝牙是有swtich的,这个见1.2.1,很简单的
//就是下面switch来判断用的,蓝牙是HEADER_TYPE_SWITCH,就是有个开关啦
int headerType = getHeaderType(header);
View view = null; if (convertView == null) {
holder = new HeaderViewHolder();
switch (headerType) {
case HEADER_TYPE_CATEGORY:
……
//bluetooth是witch的type哦
case HEADER_TYPE_SWITCH:
//找到preference_header_switch_item这个layout
view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
false);
//细心的你一定发现这里的icon和title神马的好像和我们真正要显示的不太一样啊?别急,继续看下面你就明白了
holder.icon = (ImageView) view.findViewById(R.id.icon);
holder.title = (TextView)
view.findViewById(com.android.internal.R.id.title);
holder.summary = (TextView)
view.findViewById(com.android.internal.R.id.summary);
//这里就是开关了
holder.switch_ = (Switch) view.findViewById(R.id.switchWidget);
break; case HEADER_TYPE_NORMAL:
……
break;
}
//这里把这个holder加入到view,需要注意的这个holder还是会变的哦
view.setTag(holder);
} else {
view = convertView;
holder = (HeaderViewHolder) view.getTag();
} // All view fields must be updated every time, because the view may be recycled
switch (headerType) {
case HEADER_TYPE_CATEGORY:
holder.title.setText(header.getTitle(getContext().getResources()));
break; case HEADER_TYPE_SWITCH:
// Would need a different treatment if the main menu had more switches
if (header.id == R.id.wifi_settings) {
mWifiEnabler.setSwitch(holder.switch_);
} else {
//这里会把这个开关和bluetoothEnabler中的开关相关联,具体见1.2.2,这样对这个开关的操作才能真正有所反应,所以这个很关键哦
mBluetoothEnabler.setSwitch(holder.switch_);
}
// No break, fall through on purpose to update common fields
//同样注意的是这里没有break
//$FALL-THROUGH$
case HEADER_TYPE_NORMAL:
//这里就是把我们每个header对应的icon,title重新设置一下哦。
//这样每个header都可以使用自己独有的资源了,了解了吧,呵呵
holder.icon.setImageResource(header.iconRes);
holder.title.setText(header.getTitle(getContext().getResources()));
CharSequence summary = header.getSummary(getContext().getResources());
if (!TextUtils.isEmpty(summary)) {
holder.summary.setVisibility(View.VISIBLE);
holder.summary.setText(summary);
} else {
holder.summary.setVisibility(View.GONE);
}
break;
}
//把这个view返回就可以显示了
return view;
}

1.2.1 getHeaderType

这个函数用于得到不同header的类型,我们关注的蓝牙是有一个开关的。这个其实从上面图1也是可以看出来的,只有wifi和蓝牙后面有一个开关的按钮,我们来看具体的代码:

static int getHeaderType(Header header) {
if (header.fragment == null && header.intent == null) {
return HEADER_TYPE_CATEGORY;
} else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings) {
//wifi和蓝牙就是switch的类型
return HEADER_TYPE_SWITCH;
} else {
return HEADER_TYPE_NORMAL;
}
}

1.2.2 bluetoothEnabler的setSwitch分析

这个函数的大概作用就是为了把我们ui上的switch和bluetoothEnabler相关联,这样我们在ui上点击这个开关的时候才能真正地去打开/关闭蓝牙。具体代码如下:

public void setSwitch(Switch switch_) {
//已经关联过了,就不需要再次关联了
if (mSwitch == switch_) return;
//把原来开关的监听先清除掉
mSwitch.setOnCheckedChangeListener(null);
mSwitch = switch_;
//这里把开关的操作和自身关联起来,这样你的点击才会真正地起作用
mSwitch.setOnCheckedChangeListener(this);
//得到当前蓝牙的状态
//整个这个地方的state是在开机后所做的操作来实现的,我们在之前的文章中有详细介绍过
int bluetoothState = BluetoothAdapter.STATE_OFF;
if (mLocalAdapter != null) bluetoothState = mLocalAdapter.getBluetoothState();
boolean isOn = bluetoothState == BluetoothAdapter.STATE_ON;
boolean isOff = bluetoothState == BluetoothAdapter.STATE_OFF;
//若是当前蓝牙是打开的,这里就会把开关移到打开的那个位置了,所以,我们可以看到,若是蓝牙默认是打开的,ui上开关就是打开的,它的实现就是在这里喽
mSwitch.setChecked(isOn);
if (WirelessSettings.isRadioAllowed(mContext, Settings.System.RADIO_BLUETOOTH)) {
//允许蓝牙,开关肯定是可见的
mSwitch.setEnabled(isOn || isOff);
} else {
//若是不运行蓝牙,这个开关就不可见了
mSwitch.setEnabled(false);
} if (mSupportBtWifiCoexist == false && isWifiAndWifiApStateDisabled() == false) {
//wifi打开了,这里就不能用蓝牙了,当然这个是在wifi和蓝牙不能共存的设置中。。悲催
mSwitch.setChecked(false);
mSwitch.setEnabled(false);
}
}

至此,在打开Settings的时候,我们看到的ui上蓝牙相关的内容已经全部讲解完毕了。回顾一下,总得来说,就是首先有一个header的列表,然后在onBuildHeaders中会把这个列表加载进来,然后根据每个header不同的类型决定是否加入一些别的元素,比如按钮之类的。然后具体关联到bluetooth中去,根据bluetooth当时处于的状态显示对应的按钮状况,如实是否处于打开之类的。大概的流程就是这样了。

若您觉得该文章对您有帮助,请在下面用鼠标轻轻按一下“顶”,哈哈~~·

眼花缭乱的UI,蓝牙位于何方的更多相关文章

  1. ASP.NET Zero--基于令牌的认证&SWAGGER UI

    基于令牌的认证 任何应用程序都可以将应用程序中的任何功能认证和使用为API.例如,您可以创建一个移动应用程序消耗相同的API.在本节中,我们将演示来自Postman的API (Google Chrom ...

  2. ASP.NET MVC掉过的坑_MVC初识及MVC应用程序结构

    APS.Net MVC 浅谈[转] 来自MSDN 点击访问 MVC 理论结构 模型-视图-控制器 (MVC) 体系结构模式将应用程序分成三个主要组件:模型.视图和控制器. ASP.NET MVC 框架 ...

  3. Android 显示原理简介

    作者:yearzhu,2011年进入腾讯公司,从事过Web端及移动端的测试工作,喜爱新鲜事物及新技术,目前在SNG开放平台测试组负责的移动互联SDK的测试工作. 现在越来越多的应用开始重视流畅度方面的 ...

  4. Unity3D之UGUI学习笔记(二):Rect Transform与Anchor

    Rect Transform 我们都知道,Unity3D中所有的GameObject都必须要携带一个Transform组件,且该组件无法移除,那么作为UI显示的GameObject则不是携带Trans ...

  5. Unity3D之UGUI学习笔记(一):UGUI介绍以及Canvas

    UGUI是Unity3D4.6官方提供的UI系统,支持2D和3D UI的开发. Unity3D UI史 OnGUI 在Unity4.6之前,官方提供的是OnGUI函数来开发UI界面,当然问题也比较多, ...

  6. Activity的测量(Measure)、布局(Layout)和绘制(Draw)过程分析

    一个Android应用程序窗口里面包含了很多UI元素,这些UI元素是以树形结构来组织的,即它们存在着父子关系,其中,子UI元素位于父UI元素里面,因此,在绘制一个Android应用程序窗口的UI之前, ...

  7. Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8372924 在前面一篇文章中,我们分析了And ...

  8. Unity3D专访——真正的面试

    本来想写一系列的,一半的攻击,现在面试的水.人之奸,用大哥的话说,要走新手是做螺丝钉和抹布用的.还有一半是对出出学校的或者是自废武功转3d的朋友们提供一个比較有价值的參考. 只是我时间实在仓促.没有保 ...

  9. Android View绘制和显示原理简介

    现在越来越多的应用开始重视流畅度方面的测试,了解Android应用程序是如何在屏幕上显示的则是基础中的基础,就让我们一起看看小小屏幕中大大的学问.这也是我下篇文章--<Android应用流畅度测 ...

随机推荐

  1. 【Android Tricks 6】ViewPager首页与尾页的滑动动作响应

    ViewPager能够说是Android应用中使用比較广发的一个组件了.它能够帮助我们非常 方便地实现滑动更换View的效果.刚好近期搞的一个项目有一个需求用到了这个,同 时是要能在首页和尾页滑动时可 ...

  2. 清空DateTimePicker控件的好方法

    [控件ID,不要加这个方括号].Format = DateTimePickerFormat.Custom; [控件ID,不要加这个方括号].CustomFormat = " "; ...

  3. c++中的const参数,const变量,const指针,const对象,以及const成员函数

    const 是constant 的缩写,“恒定不变”的意思.被const 修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性.所以很多C++程序设计书籍建议:“Use const whe ...

  4. 图片压缩上传 Android

    图片压缩的话 想保持 图像清晰度,但是又想保持图片的大小在100k左右. 同时的话又不想自己写那些压缩的代码的话.那你就找对地方了. 提供一个思路. 先读取你的文件,然后读到bitmap里面进行尺寸裁 ...

  5. hadoop笔记之Hive的数据存储(外部表)

    Hive的数据存储(外部表) Hive的数据存储(外部表) 外部表 指向已经在HDFS中存在的数据,可以创建Partition 它和内部表在元数据的组织上是相同的,而实际数据的存储则有较大的差异 外部 ...

  6. hadoop笔记之Hive的管理(远程登录方式)

    Hive的管理(三) Hive的管理(三) Hive的远程服务 远程服务启动方式 端口号10000 启动方式:hive --service hiveserver (注意:以JDBC或ODBC的程序登录 ...

  7. Javascript 缓冲运动——逐行分析代码,让你轻松了解缓冲运动的原理

    看过上一篇关于Javascript 匀速运动文章的朋友相信对于运动已经有了初步的了解 接下来 讲一下关于缓冲运动的原理 ,我会逐行分析代码,代码简单易懂,能马上理解其中的原理,适用于初学者. #div ...

  8. vb mid 函数使用说明

    Mid就是从一个字符串中取子字符串,比如a="aabbcc",我们想取出"bb"就可以用Mid("aabbcc",3,2)Mid有3个参数, ...

  9. php面向对象编程学习之高级特性

    前几天写了一篇关于php面向对象基础知识的博客,这两天看了php面向对象的高级特性,写出来记录一下吧,方便以后拿出来复习. 面向对象除了最基本的定义类之外,最主要就是因为面向的一些高级特性,运用这些高 ...

  10. 两个DIV,左DIV宽度固定,右DIV自动填满剩余空间

    <style type="text/css"> #main{ width:98%; } #sidebar{ float:left; width:200px; backg ...