You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Android Studio(Kotlin)中实现UC浏览器同款搜索栏?

单Toolbar实现UC浏览器风格搜索栏交互逻辑

一、优化后的XML布局(仅一个Toolbar)

将所有需要的控件整合到单个Toolbar中,通过控制控件可见性、图标和文本变化实现交互,避免切换整个Toolbar:

<?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"
    android:padding="16dp"
    tools:context=".fragment.HomeFragment">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/search_toolbar"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@drawable/custom_design"
        android:elevation="6dp"
        android:minHeight="?attr/actionBarSize"
        android:theme="?attr/actionBarTheme"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <LinearLayout
            android:id="@+id/search_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">

            <!-- 搜索引擎图标按钮 -->
            <ImageButton
                android:id="@+id/engine_icon_btn"
                android:layout_width="46dp"
                android:layout_height="46dp"
                android:layout_marginBottom="10dp"
                android:layout_weight="1"
                android:background="?attr/selectableItemBackground"
                android:contentDescription="@string/search_engine_icon"
                android:src="@drawable/google"
                tools:ignore="TouchTargetSizeCheck" />

            <!-- 初始状态的提示文本 -->
            <TextView
                android:id="@+id/search_hint_tv"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="8"
                android:background="@android:color/transparent"
                android:clickable="true"
                android:focusable="true"
                android:gravity="center_vertical"
                android:paddingStart="8dp"
                android:paddingEnd="8dp"
                android:text="@string/search_or_enter_url"
                android:textColor="#494747"
                android:textSize="18sp" />

            <!-- 输入状态的EditText -->
            <EditText
                android:id="@+id/search_input_et"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="8"
                android:background="@android:color/transparent"
                android:hint="@string/search_or_enter_url"
                android:imeOptions="actionSearch"
                android:inputType="text"
                android:paddingStart="8dp"
                android:paddingEnd="8dp"
                android:singleLine="true"
                android:textColorHint="#494747"
                android:visibility="gone"
                tools:ignore="VisualLintTextFieldSize" />

            <!-- 麦克风/清除按钮(复用同一个控件) -->
            <ImageButton
                android:id="@+id/mic_clear_btn"
                android:layout_width="46dp"
                android:layout_height="46dp"
                android:layout_marginBottom="10dp"
                android:layout_weight="1"
                android:background="?attr/selectableItemBackground"
                android:contentDescription="@string/microphone"
                android:src="@drawable/baseline_mic_24"
                tools:ignore="TouchTargetSizeCheck" />

            <!-- 分割线(仅输入状态显示) -->
            <View
                android:id="@+id/divider_view"
                android:layout_width="2dp"
                android:layout_height="40dp"
                android:layout_gravity="center_vertical"
                android:background="?android:attr/listDivider"
                android:visibility="gone" />

            <!-- 取消/搜索按钮(复用同一个控件) -->
            <TextView
                android:id="@+id/cancel_search_tv"
                android:layout_width="46dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:clickable="true"
                android:focusable="true"
                android:gravity="center"
                android:text="@string/cancel"
                android:textColor="#434141"
                android:visibility="gone" />

        </LinearLayout>

    </androidx.appcompat.widget.Toolbar>

</androidx.constraintlayout.widget.ConstraintLayout>

二、Kotlin逻辑实现状态切换

在HomeFragment中处理所有交互逻辑,通过状态枚举管理不同场景下的控件变化:

import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.ImageButton
import android.widget.TextView
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.Fragment
import android.text.Editable
import android.text.TextWatcher
import android.app.AlertDialog

class HomeFragment : Fragment() {

    private lateinit var searchHintTv: TextView
    private lateinit var searchInputEt: EditText
    private lateinit var micClearBtn: ImageButton
    private lateinit var cancelSearchTv: TextView
    private lateinit var dividerView: View
    private lateinit var engineIconBtn: ImageButton

    private enum class SearchState {
        INITIAL, INPUT, INPUT_WITH_TEXT
    }

    private var currentState = SearchState.INITIAL

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_home, container, false)
        initViews(view)
        setupListeners()
        return view
    }

    private fun initViews(view: View) {
        searchHintTv = view.findViewById(R.id.search_hint_tv)
        searchInputEt = view.findViewById(R.id.search_input_et)
        micClearBtn = view.findViewById(R.id.mic_clear_btn)
        cancelSearchTv = view.findViewById(R.id.cancel_search_tv)
        dividerView = view.findViewById(R.id.divider_view)
        engineIconBtn = view.findViewById(R.id.engine_icon_btn)
    }

    private fun setupListeners() {
        // 初始状态点击提示文本切换到输入状态
        searchHintTv.setOnClickListener {
            switchToState(SearchState.INPUT)
            searchInputEt.requestFocus()
            // 弹出软键盘
            val imm = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
            imm.showSoftInput(searchInputEt, InputMethodManager.SHOW_IMPLICIT)
        }

        // 文本输入监听,动态切换按钮状态
        searchInputEt.addTextChangedListener(object : TextWatcher {
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                currentState = if (!s.isNullOrEmpty()) SearchState.INPUT_WITH_TEXT else SearchState.INPUT
                updateStateUI()
            }

            override fun afterTextChanged(s: Editable?) {}
        })

        // 麦克风/清除按钮点击事件
        micClearBtn.setOnClickListener {
            when (currentState) {
                SearchState.INITIAL, SearchState.INPUT -> performVoiceSearch()
                SearchState.INPUT_WITH_TEXT -> searchInputEt.setText("")
            }
        }

        // 取消/搜索按钮点击事件
        cancelSearchTv.setOnClickListener {
            when (currentState) {
                SearchState.INPUT -> {
                    switchToState(SearchState.INITIAL)
                    // 隐藏软键盘
                    val imm = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
                    imm.hideSoftInputFromWindow(searchInputEt.windowToken, 0)
                }
                SearchState.INPUT_WITH_TEXT -> performSearch(searchInputEt.text.toString())
            }
        }

        // 搜索引擎图标点击弹出选择列表
        engineIconBtn.setOnClickListener { showEngineSelectionDialog() }
    }

    // 切换状态并更新UI
    private fun switchToState(state: SearchState) {
        currentState = state
        updateStateUI()
    }

    // 根据当前状态更新控件显示
    private fun updateStateUI() {
        when (currentState) {
            SearchState.INITIAL -> {
                searchHintTv.visibility = View.VISIBLE
                searchInputEt.visibility = View.GONE
                micClearBtn.setImageResource(R.drawable.baseline_mic_24)
                micClearBtn.contentDescription = getString(R.string.microphone)
                dividerView.visibility = View.GONE
                cancelSearchTv.visibility = View.GONE
                view?.findViewById<Toolbar>(R.id.search_toolbar)?.setBackgroundResource(R.drawable.custom_design)
            }
            SearchState.INPUT -> {
                searchHintTv.visibility = View.GONE
                searchInputEt.visibility = View.VISIBLE
                micClearBtn.setImageResource(R.drawable.baseline_mic_24)
                micClearBtn.contentDescription = getString(R.string.microphone)
                dividerView.visibility = View.VISIBLE
                cancelSearchTv.visibility = View.VISIBLE
                cancelSearchTv.text = getString(R.string.cancel)
                view?.findViewById<Toolbar>(R.id.search_toolbar)?.setBackgroundColor(resources.getColor(R.color.white))
            }
            SearchState.INPUT_WITH_TEXT -> {
                searchHintTv.visibility = View.GONE
                searchInputEt.visibility = View.VISIBLE
                micClearBtn.setImageResource(R.drawable.baseline_cancel_24)
                micClearBtn.contentDescription = getString(R.string.clear_text)
                dividerView.visibility = View.VISIBLE
                cancelSearchTv.visibility = View.VISIBLE
                cancelSearchTv.text = getString(R.string.search)
                view?.findViewById<Toolbar>(R.id.search_toolbar)?.setBackgroundColor(resources.getColor(R.color.white))
            }
        }
    }

    // 语音搜索实现(自行补充)
    private fun performVoiceSearch() {
        // 此处添加语音搜索逻辑
    }

    // 搜索逻辑实现(自行补充)
    private fun performSearch(query: String) {
        // 此处添加搜索跳转逻辑,比如打开WebView
    }

    // 搜索引擎选择对话框
    private fun showEngineSelectionDialog() {
        val engines = arrayOf("Google", "Bing", "百度", "搜狗")
        AlertDialog.Builder(requireContext())
            .setTitle("选择搜索引擎")
            .setItems(engines) { _, which ->
                when (which) {
                    0 -> engineIconBtn.setImageResource(R.drawable.google)
                    1 -> engineIconBtn.setImageResource(R.drawable.bing)
                    2 -> engineIconBtn.setImageResource(R.drawable.baidu)
                    3 -> engineIconBtn.setImageResource(R.drawable.sogou)
                }
            }
            .show()
    }
}

三、核心优化点

  1. 控件复用:麦克风/清除、取消/搜索按钮均复用同一控件,仅切换图标和文本,避免冗余布局
  2. 状态枚举管理:用SearchState明确区分三种交互状态,逻辑清晰易维护
  3. 视觉同步:随状态切换Toolbar背景样式,匹配UC浏览器的视觉效果
  4. 用户体验优化:自动控制软键盘显示/隐藏,减少操作步骤

内容的提问来源于stack exchange,提问作者Arshad Siddiqui

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 15:04:49