現(xiàn)高仿 QQ 消息與電話)
一、引言與項(xiàng)目背景在日常 Android 開發(fā)中QQ 作為一款國民級(jí)社交應(yīng)用其頂部導(dǎo)航欄設(shè)計(jì)簡潔高效是很多開發(fā)者學(xué)習(xí)的標(biāo)桿。QQ 的消息與電話界面頂部 ToolBar 不僅展示了標(biāo)題、狀態(tài)、聯(lián)系人信息還集成了視頻通話、語音通話、搜索、更多菜單等豐富的交互元素。本文將從零開始手把手帶你使用 Android 原生 ToolBar 組件實(shí)現(xiàn)一個(gè)高仿 QQ 消息與電話界面的完整功能涵蓋 UI 布局、自定義 View、動(dòng)畫交互、狀態(tài)管理、主題適配等核心知識(shí)點(diǎn)全文超過 2 萬字適合中級(jí)及以上 Android 開發(fā)者深入閱讀。二、ToolBar 基礎(chǔ)與環(huán)境搭建2.1 ToolBar 與 ActionBar 的關(guān)系在 Android 5.0API 21之前應(yīng)用頂部導(dǎo)航主要使用 ActionBar。ActionBar 功能有限定制性差難以滿足復(fù)雜 UI 需求。Android 5.0 引入了 ToolBar 組件它本質(zhì)上是一個(gè) ViewGroup可以像普通 View 一樣嵌入到布局文件中支持高度自定義。ToolBar 與 ActionBar 的最大區(qū)別在于ToolBar 是 Material Design 體系下的通用組件不強(qiáng)制綁定 Activity 的窗口裝飾開發(fā)者可以自由添加子 View、自定義樣式、控制動(dòng)畫表現(xiàn)。同時(shí)ToolBar 天然支持與 CoordinatorLayout、AppBarLayout 等 Material 組件協(xié)同工作可以輕松實(shí)現(xiàn)折疊、上滑隱藏等高級(jí)效果而 ActionBar 幾乎無法做到。2.2 引入 Material Design 依賴要使用 ToolBar首先需要在項(xiàng)目中引入 Material Design 庫。在 app 模塊的 build.gradle 中添加以下依賴dependencies { implementation com.google.android.material:material:1.9.0 implementation androidx.appcompat:appcompat:1.6.1 implementation androidx.core:core-ktx:1.10.1 implementation androidx.constraintlayout:constraintlayout:2.1.4 }確保使用 AndroidX 系列包舊版 support 庫已停止維護(hù)。ToolBar 類位于 androidx.appcompat.widget.Toolbar 包中。如果項(xiàng)目仍在使用 support 庫建議盡快遷移到 AndroidX因?yàn)?Google 已停止對(duì) support 庫的功能更新和安全維護(hù)。2.3 基礎(chǔ) ToolBar 布局文件在 activity_main.xml 中編寫一個(gè)最基礎(chǔ)的 ToolBar 布局?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical androidx.appcompat.widget.Toolbar android:idid/toolbar android:layout_widthmatch_parent android:layout_height?attr/actionBarSize android:backgroundcolor/colorPrimary android:elevation4dp android:themestyle/ThemeOverlay.AppCompat.Dark.ActionBar app:popupThemestyle/ThemeOverlay.AppCompat.Light app:titleTextColorandroid:color/white / FrameLayout android:idid/content_frame android:layout_widthmatch_parent android:layout_heightmatch_parent / /LinearLayout在 Activity 中使用 ToolBar 替代默認(rèn) ActionBarpublic class MainActivity extends AppCompatActivity { private Toolbar toolbar; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); toolbar findViewById(R.id.toolbar); setSupportActionBar(toolbar); // 可選配置 if (getSupportActionBar() ! null) { getSupportActionBar().setDisplayHomeAsUpEnabled(false); getSupportActionBar().setDisplayShowTitleEnabled(false); } } }由于本文最終要實(shí)現(xiàn)高仿 QQ 風(fēng)格因此我們將默認(rèn)隱藏 ToolBar 自帶的標(biāo)題和返回鍵轉(zhuǎn)而使用自定義布局來承載頭像、昵稱、狀態(tài)等復(fù)雜 UI。2.4 自定義 ToolBar 主題在 styles.xml 中定義 ToolBar 專用主題覆蓋標(biāo)題顏色、字體大小、溢出菜單樣式等屬性style nameAppTheme.Toolbar parentThemeOverlay.AppCompat.Dark.ActionBar item nameandroid:textColorPrimaryandroid:color/white/item item nameandroid:textColorSecondaryandroid:color/white/item item nameactionMenuTextColorandroid:color/white/item item nametitleTextStylestyle/ToolbarTitleStyle/item /style style nameToolbarTitleStyle item nameandroid:textSize18sp/item item nameandroid:textColorandroid:color/white/item item nameandroid:fontFamilysans-serif-medium/item /style在 app 的全局主題中將 ToolBar 的默認(rèn)樣式替換為上面定義的主題可以保證所有 ToolBar 實(shí)例都擁有一致的外觀表現(xiàn)。三、高仿 QQ 消息頂部布局實(shí)戰(zhàn)3.1 分析 QQ 消息界面頂部結(jié)構(gòu)打開 QQ 的消息列表頁面我們觀察到頂部導(dǎo)航欄的結(jié)構(gòu)從上到下依次為頂部狀態(tài)欄區(qū)域系統(tǒng)狀態(tài)欄、信號(hào)、電量等不屬于 ToolBar 本體、標(biāo)題欄區(qū)域左側(cè)顯示用戶頭像加昵稱中間顯示“消息”或自定義狀態(tài)文字右側(cè)通常為添加聯(lián)系人、搜索等圖標(biāo)。在一些版本中中間會(huì)顯示“消息”二字右側(cè)依次為“”和搜索圖標(biāo)。需要注意的是QQ 的消息頁 ToolBar 并不是簡單地使用 ToolBar 的 title 和 menu而是采用完全自定義布局的方式。具體做法是在 ToolBar 內(nèi)部嵌套一個(gè) LinearLayout 或 ConstraintLayout再放置頭像 ImageView、昵稱 TextView、狀態(tài) TextView 以及若干功能圖標(biāo)。這種設(shè)計(jì)可以讓整個(gè)標(biāo)題欄的排版自由度達(dá)到最高便于后期修改與擴(kuò)展。同時(shí)QQ 的電話頁頂部結(jié)構(gòu)類似但中間區(qū)域會(huì)顯示“電話”或在線狀態(tài)右側(cè)圖標(biāo)變?yōu)橥ㄔ捰涗洝⑻砑勇?lián)系人等。本文將分別實(shí)現(xiàn)消息頁和電話頁的 ToolBar并通過 ViewPager 或 TabLayout 實(shí)現(xiàn)頁簽切換。3.2 消息頁 ToolBar 自定義布局實(shí)現(xiàn)首先創(chuàng)建消息頁的 ToolBar 布局文件 layout_toolbar_message.xmlandroidx.appcompat.widget.Toolbar xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto android:idid/toolbar_message android:layout_widthmatch_parent android:layout_height?attr/actionBarSize android:backgroundcolor/qq_blue android:elevation4dp LinearLayout android:layout_widthmatch_parent android:layout_heightmatch_parent android:gravitycenter_vertical android:orientationhorizontal de.hdodenhof.circleimageview.CircleImageView android:idid/iv_avatar android:layout_width36dp android:layout_height36dp android:layout_marginStart8dp android:srcdrawable/ic_avatar_default / TextView android:idid/tv_nickname android:layout_width0dp android:layout_heightwrap_content android:layout_marginStart10dp android:layout_weight1 android:ellipsizeend android:maxLines1 android:text我的昵稱 android:textColorandroid:color/white android:textSize16sp android:textStylebold / TextView android:idid/tv_status android:layout_widthwrap_content android:layout_heightwrap_content android:layout_marginEnd8dp android:text在線 android:textColorcolor/qq_status_green android:textSize12sp / ImageView android:idid/iv_add android:layout_width24dp android:layout_height24dp android:layout_marginEnd12dp android:srcdrawable/ic_add / ImageView android:idid/iv_search android:layout_width24dp android:layout_height24dp android:layout_marginEnd12dp android:srcdrawable/ic_search / /LinearLayout /androidx.appcompat.widget.Toolbar上述布局使用了 CircleImageView 來實(shí)現(xiàn)圓形頭像需要依賴implementation de.hdodenhof:circleimageview:3.1.0在 Activity 或 Fragment 中通過下面的方式加載并設(shè)置自定義布局Toolbar toolbar findViewById(R.id.toolbar_message); toolbar.setTitle(); // 隱藏默認(rèn)標(biāo)題 toolbar.setContentInsetsAbsolute(0, 0); // 清除內(nèi)邊距 // 設(shè)置自定義內(nèi)容 toolbar.addView(getLayoutInflater().inflate(R.layout.layout_toolbar_message, toolbar, false)); // 或者使用 ViewStub 延遲加載以優(yōu)化性能注意為了完全掌控布局我們清除了 ToolBar 的默認(rèn)左右內(nèi)邊距否則無法貼邊顯示頭像等內(nèi)容。同時(shí)自定義的 LinearLayout 已經(jīng)承擔(dān)了所有內(nèi)容擺放任務(wù)因此不需要再調(diào)用 setSupportActionBar 中的標(biāo)題相關(guān)方法。3.3 消息頁 ToolBar 交互事件處理為每個(gè)按鈕綁定點(diǎn)擊事件并在代碼中處理邏輯ImageView ivAdd toolbar.findViewById(R.id.iv_add); ivAdd.setOnClickListener(v - { // 彈出添加好友、創(chuàng)建群聊等菜單 showAddMenu(v); }); ImageView ivSearch toolbar.findViewById(R.id.iv_search); ivSearch.setOnClickListener(v - { // 跳轉(zhuǎn)到搜索頁面或展開搜索欄 navigateToSearch(); }); CircleImageView ivAvatar toolbar.findViewById(R.id.iv_avatar); ivAvatar.setOnClickListener(v - { // 跳轉(zhuǎn)到個(gè)人主頁或側(cè)邊欄 navigateToProfile(); });對(duì)于昵稱點(diǎn)擊可以展示在線狀態(tài)切換或跳轉(zhuǎn)到設(shè)置頁面。這些交互可以根據(jù)實(shí)際需求靈活調(diào)整。3.4 搜索展開與更多菜單實(shí)現(xiàn)當(dāng)用戶點(diǎn)擊搜索圖標(biāo)時(shí)我們可以在 ToolBar 內(nèi)部動(dòng)態(tài)切換為搜索輸入框覆蓋原有布局實(shí)現(xiàn)類似 QQ 的沉浸式搜索效果。實(shí)現(xiàn)思路如下布局切換在 ToolBar 內(nèi)部預(yù)先放置兩個(gè) FrameLayout其中一個(gè)承載默認(rèn)內(nèi)容另一個(gè)承載搜索欄通過設(shè)置 visibility 切換。動(dòng)畫過渡使用 TransitionManager 或自定義動(dòng)畫讓搜索欄展開/收縮時(shí)更加平滑。鍵盤控制展開搜索欄時(shí)自動(dòng)彈出軟鍵盤并監(jiān)聽返回鍵以折疊搜索欄。FrameLayout android:idid/fl_search_container android:layout_widthmatch_parent android:layout_heightmatch_parent android:visibilitygone EditText android:idid/et_search android:layout_widthmatch_parent android:layout_heightmatch_parent android:backgroundandroid:color/transparent android:hint搜索 android:textColorHint#80ffffff android:textColorandroid:color/white android:inputTypetext / ImageView android:idid/iv_close_search android:layout_width24dp android:layout_height24dp android:layout_gravityend|center_vertical android:layout_marginEnd12dp android:srcdrawable/ic_close / /FrameLayout在代碼中通過設(shè)置 fl_search_container 可見并隱藏默認(rèn) LinearLayout實(shí)現(xiàn)搜索模式切換。同時(shí)處理返回鍵邏輯在搜索可見時(shí)優(yōu)先折疊搜索欄而非退出頁面。四、高仿 QQ 電話頂部布局實(shí)戰(zhàn)4.1 電話頁 ToolBar 結(jié)構(gòu)分析QQ 的電話頁通常與消息頁并排通過底部導(dǎo)航或頂部 Tab 切換。電話頁的 ToolBar 布局類似但中間顯示“電話”二字右側(cè)依次為通話記錄圖標(biāo)和添加聯(lián)系人圖標(biāo)。有些版本中還會(huì)在標(biāo)題下方顯示“在線”或“可使用 QQ 電話”等提示信息。電話頁 ToolBar 同樣使用完全自定義布局不再重復(fù)基礎(chǔ)搭建直接給出核心布局文件和交互實(shí)現(xiàn)。4.2 電話頁 ToolBar 布局實(shí)現(xiàn)LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:gravitycenter_vertical android:orientationhorizontal TextView android:idid/tv_title_call android:layout_width0dp android:layout_heightwrap_content android:layout_weight1 android:gravitycenter android:text電話 android:textColorandroid:color/white android:textSize18sp android:textStylebold / ImageView android:idid/iv_call_history android:layout_width24dp android:layout_height24dp android:layout_marginEnd12dp android:srcdrawable/ic_history / ImageView android:idid/iv_add_contact android:layout_width24dp android:layout_height24dp android:layout_marginEnd12dp android:srcdrawable/ic_add_contact / /LinearLayout將該布局 inflate 到電話頁的 ToolBar 中即可。交互邏輯類似點(diǎn)擊通話記錄打開最近通話列表點(diǎn)擊添加聯(lián)系人跳轉(zhuǎn)到聯(lián)系人選擇界面。4.3 電話頁狀態(tài)指示器有時(shí)需要在電話頁頂部展示一個(gè)細(xì)條狀態(tài)欄比如“網(wǎng)絡(luò)狀態(tài)良好”或者“正在通話中”。這可以通過在 ToolBar 下方添加一個(gè)狹長的 View 或者直接在 ToolBar 內(nèi)部增加一個(gè)窄條布局來實(shí)現(xiàn)。例如View android:idid/view_status_bar android:layout_widthmatch_parent android:layout_height4dp android:backgroundcolor/qq_green /在代碼中根據(jù)網(wǎng)絡(luò)狀態(tài)或通話狀態(tài)動(dòng)態(tài)改變背景顏色和顯示隱藏增加用戶體驗(yàn)。五、下拉搜索框與更多菜單實(shí)現(xiàn)5.1 彈出式更多菜單除了在 ToolBar 右側(cè)直接放置圖標(biāo)外QQ 還常使用彈出菜單PopupMenu來收納更多操作。例如點(diǎn)擊“”按鈕彈出包含“加好友/群”、“掃一掃”、“面對(duì)面快傳”等的菜單列表。使用 PopupMenu 實(shí)現(xiàn)private void showAddMenu(View anchor) { PopupMenu popup new PopupMenu(this, anchor, Gravity.END); popup.getMenuInflater().inflate(R.menu.menu_add, popup.getMenu()); popup.setOnMenuItemClickListener(item - { int id item.getItemId(); if (id R.id.action_add_friend) { // 跳轉(zhuǎn)加好友 return true; } else if (id R.id.action_create_group) { // 跳轉(zhuǎn)建群 return true; } return false; }); popup.show(); }菜單文件 res/menu/menu_add.xmlmenu xmlns:androidhttp://schemas.android.com/apk/res/android item android:idid/action_add_friend android:title加好友/群 / item android:idid/action_create_group android:title創(chuàng)建群聊 / item android:idid/action_scan android:title掃一掃 / /menu5.2 下拉搜索框的完整實(shí)現(xiàn)前文已經(jīng)描述了搜索欄的基本布局切換這里補(bǔ)充輸入事件處理和搜索歷史管理。當(dāng)用戶在搜索框輸入文字時(shí)可以使用 RxJava 或 LiveData 實(shí)現(xiàn)防抖避免頻繁觸發(fā)搜索請(qǐng)求。同時(shí)可以保存搜索歷史并在搜索欄下方展示通過 RecycleView 實(shí)現(xiàn)歷史記錄列表。代碼示例private void setupSearchInput(EditText etSearch) { RxTextView.textChanges(etSearch) .debounce(300, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::performSearch); }搜索框展開時(shí)toolbar 下方的 RecyclerView 可以動(dòng)態(tài)顯示搜索結(jié)果或歷史記錄。為了不遮擋內(nèi)容區(qū)域通常會(huì)用 FrameLayout 承載主內(nèi)容將搜索建議列表覆蓋在內(nèi)容之上。六、動(dòng)畫與交互優(yōu)化6.1 ToolBar 展開與收縮動(dòng)畫借助 AppBarLayout 和 CoordinatorLayout可以實(shí)現(xiàn) ToolBar 跟隨列表滑動(dòng)收起和展開的效果。但 QQ 的消息頁 ToolBar 并不支持滑動(dòng)折疊而是始終固定。本文主要介紹點(diǎn)擊搜索時(shí)搜索欄的展開動(dòng)畫以及 Tab 切換時(shí) ToolBar 內(nèi)容的平滑過渡。使用 TransitionManager 簡化動(dòng)畫TransitionManager.beginDelayedTransition(toolbar, new Fade()); toolbar.removeAllViews(); toolbar.addView(searchView);6.2 Tab 切換時(shí) ToolBar 平滑切換QQ 消息頁和電話頁共用同一個(gè) ToolBar 還是分別維護(hù)通??梢允褂?ViewPager 配合 TabLayoutToolBar 獨(dú)立于 ViewPager 之外根據(jù)當(dāng)前選中的頁面動(dòng)態(tài)切換 ToolBar 的內(nèi)容。例如viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { Override public void onPageSelected(int position) { switchToolbarContent(position); } });switchToolbarContent 方法內(nèi)部移除當(dāng)前 ToolBar 的內(nèi)容并 inflate 對(duì)應(yīng)的布局文件。為了增加平滑過渡可以使用 android:animateLayoutChangestrue 在 ToolBar 上啟用默認(rèn)布局動(dòng)畫或者自定義內(nèi)容淡入淡出效果。6.3 圖標(biāo)點(diǎn)擊漣漪效果為所有圖標(biāo) ImageView 設(shè)置 background 為 ripple 效果提升觸摸反饋??梢允褂??attr/selectableItemBackgroundBorderless 或自定義 ripple。android:background?attr/selectableItemBackgroundBorderless七、主題適配與多機(jī)型兼容7.1 日間/夜間模式適配QQ 支持夜間模式ToolBar 的背景、文字顏色、圖標(biāo)顏色都需要隨之變化。利用 Android 的資源限定符和主題屬性可以方便地實(shí)現(xiàn)。定義 attrs.xmlattr nametoolbarBackground formatcolor / attr nametoolbarTextColor formatcolor /然后在日間和夜間主題中分別賦值ToolBar 布局中使用 ?attr/toolbarBackground 等引用屬性即可。圖標(biāo)可以使用 tint 屬性動(dòng)態(tài)著色。7.2 劉海屏、挖孔屏適配在全面屏?xí)r代ToolBar 的高度和內(nèi)容布局需要考慮狀態(tài)欄高度以及劉海區(qū)域??梢酝ㄟ^設(shè)置 android:fitsSystemWindowstrue 并配合 WindowInsets 來獲取系統(tǒng)安全區(qū)域手動(dòng)調(diào)整 ToolBar 的 padding 或 margin。toolbar.setOnApplyWindowInsetsListener((v, insets) - { int top insets.getSystemWindowInsetTop(); v.setPadding(v.getPaddingLeft(), top, v.getPaddingRight(), v.getPaddingBottom()); return insets.consumeSystemWindowInsets(); });7.3 多屏幕尺寸適配針對(duì)不同屏幕寬度頭像大小、圖標(biāo)間距、文字大小可以使用 dimens.xml 中不同限定符的值。對(duì)于大屏設(shè)備可以適當(dāng)增大頭像和圖標(biāo)尺寸保持視覺協(xié)調(diào)。八、性能優(yōu)化與最佳實(shí)踐8.1 避免過度繪制自定義 ToolBar 內(nèi)部 View 層級(jí)應(yīng)盡量扁平化避免嵌套過多的布局容器。使用 ConstraintLayout 替代多層 LinearLayout 可以有效減少層級(jí)。此外為 ToolBar 設(shè)置背景色時(shí)不要覆蓋不必要的透明區(qū)域。8.2 延遲加載與 ViewStub如果 ToolBar 中包含不常用或復(fù)雜的子布局如搜索欄、更多菜單可以使用 ViewStub 延遲初始化減少首次繪制時(shí)間。ViewStub android:idid/stub_search android:layoutlayout/layout_search_bar android:layout_widthmatch_parent android:layout_heightmatch_parent /僅在需要展開搜索時(shí)調(diào)用 inflate之后再進(jìn)行切換。8.3 內(nèi)存與線程管理在處理圖標(biāo)資源時(shí)使用矢量圖VectorDrawable以減小 APK 體積并適應(yīng)不同分辨率。避免在 ToolBar 的頻繁刷新過程中創(chuàng)建大量臨時(shí)對(duì)象。對(duì)于搜索界面的歷史記錄 Adapter使用 DiffUtil 高效更新列表。8.4 無障礙與國際化為所有圖標(biāo)添加 contentDescription 屬性以支持無障礙服務(wù)。文字內(nèi)容不要硬編碼使用 strings.xml 資源便于后期國際化。九、總結(jié)與展望本文從 ToolBar 基礎(chǔ)開始逐步深入到高仿 QQ 消息與電話界面的完整實(shí)現(xiàn)涵蓋了自定義布局、搜索展開、主題適配、動(dòng)畫交互、性能優(yōu)化等多個(gè)方面。通過掌握這些技術(shù)讀者不僅可以實(shí)現(xiàn)一個(gè)高仿 QQ 風(fēng)格的應(yīng)用頂部導(dǎo)航還能舉一反三打造出各種復(fù)雜自定義 UI。未來隨著 Jetpack Compose 的普及ToolBar 的使用方式可能會(huì)逐漸被 TopAppBar 和自定義 Composable 替代但本文涉及的自定義布局思想、狀態(tài)管理、動(dòng)畫處理等能力在任何 UI 框架下都是通用的底層能力。建議讀者在理解原生 View 體系的基礎(chǔ)上再去學(xué)習(xí) Compose會(huì)事半功倍。完整項(xiàng)目源碼可訪問實(shí)際開發(fā)中可提供 GitHub 鏈接希望本文能幫助你的項(xiàng)目快速落地 QQ 風(fēng)格的頂部導(dǎo)航欄。