学习Android(二)

283 阅读29分钟

简介

在上一章节,我们成功的运行了第一个Android项目,使得我们对整体项目结构有个大概的认识,但这仅仅只是一个开始,接下来本章节将会将Android中的常见的UI组件来进行逐个分析讲解。

常用UI组件速查表

类别组件核心用途
文本显示TextViewEditText显示或输入文本
按钮与交互ButtonCheckBox...触发事件或选择选项
图片ImageView显示图片
列表RecyclerView高效展示列表
布局容器ConstaintLayout ...复杂界面布局
对话框与提示AlertDialog用户确认或反馈
进度指示ProgressBar显示加载状态

1. 文本显示

  • TextView

    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView 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:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".TextViewExampleActivity">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="16dp">
    
            <!-- 1. 基础文本 -->
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="基础文本:Hello World!"
                android:textSize="18sp" />
    
            <!-- 2. 文本颜色与字体 -->
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="颜色与字体:红色 + 斜体"
                android:textColor="#FF0000"
                android:textStyle="italic"
                android:layout_marginTop="8dp" />
    
            <!-- 3. 背景与边距 -->
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="背景色 + 边距"
                android:background="#E0E0E0"
                android:padding="12dp"
                android:layout_marginTop="8dp" />
    
            <!-- 4. 单行与省略 -->
            <TextView
                android:layout_width="100dp"
                android:layout_height="wrap_content"
                android:text="超长文本将被截断并显示省略号..."
                android:singleLine="true"
                android:ellipsize="end"
                android:layout_marginTop="8dp" />
    
            <!-- 5. 富文本(HTML) -->
            <TextView
                android:id="@+id/tv_html"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp" />
    
            <!-- 6. 字体阴影 -->
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="字体阴影效果"
                android:shadowColor="#80000000"
                android:shadowDx="2"
                android:shadowDy="2"
                android:shadowRadius="4"
                android:layout_marginTop="8dp" />
    
            <!-- 7. 自动调整文本大小 -->
            <TextView
                android:layout_width="200dp"
                android:layout_height="50dp"
                android:text="自 动 缩 小 文 本 以 适 应 空 间"
                android:autoSizeTextType="uniform"
                android:autoSizeMinTextSize="12sp"
                android:autoSizeMaxTextSize="20sp"
                android:background="#BBDEFB"
                android:gravity="center"
                android:layout_marginTop="8dp" />
    
            <!-- 8. 链接与点击 -->
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="访问官网:https://www.baidu.com"
                android:autoLink="web"
                android:layout_marginTop="8dp" />
    
            <!-- 9. 处理复杂文本 -->
            <TextView
                android:id="@+id/tv_spannable"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:autoLink="web"
                android:layout_marginTop="8dp" />
    
        </LinearLayout>
    
    </ScrollView>
    
    class TextViewExampleActivity : AppCompatActivity() {
    
        private val textHtml: TextView by lazy { findViewById(R.id.tv_html) }
        private val tvSpannable: TextView by lazy { findViewById(R.id.tv_spannable) }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_text_view_example)
            // 定义 HTML 格式的字符串,包含段落、加粗、斜体、下划线、颜色、换行和超链接
            val htmlText = "<p><--------html文本---------></p>" + // 自定义分隔文字(HTML注释视觉效果)
                    "<p><b>加粗文本</b></p>" +             // 加粗文字
                    "<p><i>斜体文本</i></p>" +             // 斜体文字
                    "<p><u>下划线文本</u></p>" +           // 下划线文字
                    "<p><font color='#FF0000'>红色字体</font></p>" + // 使用 font 标签设置红色字体
                    "<p>第一行<br>第二行</p>" +           // <br> 用于换行
                    "<p>访问 <a href='https://www.baidu.com'>示例链接</a></p>" + // 带超链接的文字
                    "<p><--------html文本---------></p>"   // 自定义结尾分隔
            // 判断系统版本是否 >= Android 7.0 (Nougat)
            // Html.FROM_HTML_MODE_LEGACY 是 Nougat(API 24)后加入的,提供更好的 HTML 解析支持
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                textHtml.text = Html.fromHtml(htmlText, Html.FROM_HTML_MODE_LEGACY)
                // 将 HTML 字符串转换为 Spanned 富文本并赋值给 TextView
                // 注意:链接必须手动设置 movementMethod 才能点击
                textHtml.movementMethod = LinkMovementMethod.getInstance()
            }
    
            // 创建一个可富文本操作的 SpannableString
            val spannable = SpannableString("多彩文本")
            // 设置前两个字("多彩")为红色
            spannable.setSpan(
                ForegroundColorSpan(Color.RED), // 红色样式
                0, 2,                            // 起始位置 0 到 2,不包含 2
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE // 不包括起止边界的文字扩展
            )
            // 设置到 TextView 中显示
            tvSpannable.text = spannable
    
        }
    }
    

    运行上述例子我们可以了解大致 TextView 能做到那些文本显示效果,接下来我们来介绍一下 TextView 的一些基础属性

    属性说明示例值
    android:text显示文本的内容android:text="基础文本:Hello World!"
    android:textSize文本字体大小android:textSize="18sp"
    android:textColor文本颜色android:textColor="#FF0000"
    android:textStyle字体样式(粗体bold、斜体italic等)android:textStyle="italic
    android:maxLine最大行数android:maxLines="2"
    android:ellipsize文本溢出时的省略方式(末尾显示省略号:end/开头显示省略号:start/中间显示省略号:middle/超出部门以跑马灯滚动显示:maarquee(需要配合focusable等属性))android:ellipsize="end"
    android:autoLink自动识别并链接文本(如 URL、邮箱)android:autoLink="web"
    android:singleLine设置为 true 时,TextView 的内容将被限制为单行显示,超出部分显示省略号(需配合 ellipsizeandroid:singleLine="true"

    当然我们也可以在代码中动态的进行设置上述的基础属性,就如上述例子我们去设置 html文本和富文本的操作一样,动态的去设置文本属性可以实现更多有趣的效果,这个需要我们在日后开发中自己去发觉和研究。

  • EditText

    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".EditTextExampleActivity">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
    
            <!-- 1. 普通文本输入框 -->
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:text="普通文本输入框:"
                android:textSize="16sp" />
    
            <EditText
                android:id="@+id/etNormal"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:drawable/edit_text"
                android:hint="请输入普通文本"
                android:inputType="text" />
    
            <!-- 2. 数字输入框,限制输入数字 -->
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="数字输入框:"
                android:textSize="16sp" />
    
            <EditText
                android:id="@+id/etNumber"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:drawable/edit_text"
                android:hint="请输入数字"
                android:inputType="number"
                android:maxLength="5" />
    
            <!-- 3. 密码输入框,输入字符以点显示 -->
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="密码输入框:"
                android:textSize="16sp" />
    
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
    
                <!-- 密码输入框 -->
                <EditText
                    android:id="@+id/etPassword"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="@android:drawable/edit_text"
                    android:hint="请输入密码"
                    android:inputType="textPassword" />
    
                <!-- 切换密码显隐的按钮 -->
                <ImageView
                    android:id="@+id/ivToggle"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentRight="true"
                    android:layout_centerVertical="true"
                    android:contentDescription="密码显示切换"
                    android:layout_marginEnd="40dp"
                    android:src="@drawable/ic_show_password" />
            </RelativeLayout>
    
            <!-- 4. 多行文本输入框,支持换行 -->
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="多行文本输入框:"
                android:textSize="16sp" />
    
            <EditText
                android:id="@+id/etMultiline"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:drawable/edit_text"
                android:gravity="top|start"
                android:hint="请输入多行文本,比如留言等..."
                android:inputType="textMultiLine"
                android:lines="3"
                android:minLines="3" />
    
            <!-- 5. 带有颜色设置的输入框 -->
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="自定义文本颜色:"
                android:textSize="16sp" />
    
            <EditText
                android:id="@+id/etCustomStyle"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:drawable/edit_text"
                android:hint="请输入内容"
                android:inputType="text"
                android:textColor="@android:color/holo_blue_dark"
                android:textSize="18sp" />
    
            <!-- 6. 带自定义光标的 EditText  -->
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="带自定义光标的 EditText:"
                android:textSize="16sp" />
            
            <EditText
                android:id="@+id/etCustomCursor"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="输入内容"
                android:textCursorDrawable="@drawable/custom_cursor"
                android:padding="8dp"
                android:background="@android:drawable/edit_text" />
    
        </LinearLayout>
    
    </ScrollView>
    
    class EditTextExampleActivity : AppCompatActivity() {
        private val etNormal: EditText by lazy { findViewById(R.id.etNormal) }
        private val etPassword: EditText by lazy { findViewById(R.id.etPassword) }
        private val ivToggle: ImageView by lazy { findViewById(R.id.ivToggle) }
    
        // 密码是否可见的状态变量,默认 false 表示不可见
        private var isPasswordVisible = false
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_edit_text_example)
    
    
            // 示例:为普通文本输入框设置初始文本
            etNormal.setText("初始文本")
    
            // 示例:简单校验密码输入框内容
            val password = etPassword.text.toString().trim()
            if (password.isEmpty()) {
                etPassword.error = "密码不能为空!"
            }
            // 默认密码隐藏
            etPassword.transformationMethod = PasswordTransformationMethod.getInstance()
    
            // 设置切换按钮的点击事件
            ivToggle.setOnClickListener {
                if (isPasswordVisible) {
                    // 当前密码为可见状态,切换为隐藏
                    etPassword.transformationMethod = PasswordTransformationMethod.getInstance()
                    ivToggle.setImageResource(R.drawable.ic_show_password)
                    isPasswordVisible = false
                } else {
                    // 当前密码为隐藏状态,切换为显示密码
                    etPassword.transformationMethod = null
                    ivToggle.setImageResource(R.drawable.ic_hide_password)
                    isPasswordVisible = true
                }
                // 移动光标至文本末尾
                etPassword.setSelection(etPassword.text.length)
            }
    
        }
    }
    

    ic_show_password

    <?xml version="1.0" encoding="utf-8"?>
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24"
        android:viewportHeight="24">
        <!--
             这条 path 定义的是眼睛的外轮廓,
             使用 material design 中“visibility”图标的 pathData。
        -->
        <path
            android:fillColor="?attr/colorControlNormal"
            android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12,17c-2.76,0-5-2.24-5-5s2.24-5 5-5 5,2.24 5,5-2.24,5-5,5zM12,9c-1.66,0-3,1.34-3,3s1.34,3 3,3 3-1.34 3-3-1.34-3-3-3z" />
    
    </vector>
    

    ic_hide_password

    <?xml version="1.0" encoding="utf-8"?>
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24"
        android:viewportHeight="24">
        <!-- 定义眼睛轮廓和中间瞳孔(隐藏状态下)的部分 -->
    
        <path
            android:fillColor="?attr/colorControlNormal"
            android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12,17c-2.76,0-5-2.24-5-5s2.24-5 5-5 5,2.24 5,5-2.24,5-5,5zM12,9c-1.66,0-3,1.34-3,3s1.34,3 3,3 3-1.34 3-3-1.34-3-3-3z" />
    
        <path
            android:fillColor="@android:color/transparent"
            android:pathData="M4,20 L20,4"
            android:strokeWidth="2"
            android:strokeColor="?attr/colorControlNormal"
            android:strokeLineCap="round"
            android:strokeLineJoin="round" />
    
    </vector>
    

    custom_cursor

    <?xml version="1.0" encoding="utf-8"?>
    <!-- 定义一个简单的矩形光标样式 -->
    <shape xmlns:android="http://schemas.android.com/apk/res/android">
        <!-- 设置光标的尺寸,这里定义宽度为 2dp,高度随 EditText 文本行高度而自动适应时也可以不设置高度 -->
        <size android:width="2dp" />
        <!-- 设置光标的颜色 -->
        <solid android:color="#CC5A5A" />
    </shape>
    

    通过观察源码我们可以知道,EditTextTextView 派生类,那么 TextView 有的属性和方法这里就不在赘述了

    说明示例场景
    text普通文本用户名、昵称
    textPassword密码输入(隐藏字符)密码、验证码
    textEmailAddress邮箱地址(键盘显示 @.com邮箱输入框
    phone电话号码键盘手机号输入框
    number数字键盘(整数)年龄、数量
    numberDecimal 数字键盘(允许小数)价格、金额
    textMultiLine允许多行文本输入评论、个人简介
    textCapCharacters自动大写所有字母验证码输入(全大写)
    textCapWords每个单词首字母大写姓名输入
    textNoSuggestions关闭输入法自动补全敏感信息输入

2. 按钮与交互

  • Button

    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView 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:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ButtonExampleActivity">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
    
            <!-- 基本按钮 -->
            <Button
                android:id="@+id/btnBasic"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="基本按钮"
                android:textColor="#FFFFFF"
                android:backgroundTint="#2196F3"/>
    
            <!-- 带图标的按钮 -->
            <Button
                android:id="@+id/btnIcon"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="带图标按钮"
                android:drawableTop="@android:drawable/ic_dialog_info"/>
    
            <!-- 自定义形状按钮 -->
            <android.widget.Button
                android:id="@+id/btnCustomShape"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="圆角按钮"
                android:textColor="@color/white"
                android:background="@drawable/rounded_button"/>
    
            <!-- 禁用按钮 -->
            <Button
                android:id="@+id/btnDisabled"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="禁用按钮"/>
    
            <!-- 动态操作按钮 -->
            <Button
                android:id="@+id/btnDynamic"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="点击改变其他按钮状态"/>
    
            <!-- 状态变化按钮 -->
            <android.widget.Button
                android:id="@+id/btnState"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="状态变化按钮"
                android:textColor="@color/white"
                android:background="@drawable/button_state_selector" />
    
        </LinearLayout>
    
    </ScrollView>
    

    从布局中,我们可以发现,为什么有些用的是 Button 有些用的是 android.widget.Button ,因为我们的默认主题样式中使用 Material3 的主题

    就是 thems 文件中

    <resources xmlns:tools="http://schemas.android.com/tools">
        <!-- Base application theme. -->
        <style name="Base.Theme.HelloWorld" parent="Theme.Material3.DayNight.NoActionBar">
            <!-- Customize your light theme here. -->
            <!-- <item name="colorPrimary">@color/my_light_primary</item> -->
        </style>
    
        <style name="Theme.HelloWorld" parent="Base.Theme.HelloWorld" />
    </resources>
    

    我们在 AndroidManifest 设置了 application 的主题为上述,所以会出现直接使用 Button 设置Background或者修改背景颜色无效,按钮的颜色一直是默认的主题颜色,因为当你使用 Theme.Material3.*(比如 Theme.Material3.DayNight.NoActionBar)时:

    • Button 默认使用 Material3 的样式系统(基于 MaterialButton,并且不再是简单的 android.widget.Button
    • Material3 的按钮会自动应用 shapeAppearance, rippleColor, containerColor 等属性,并且会忽略你直接设置的 android:background

    换句话说,Material3 中的 Button 是“样式接管者”,你需要用新的方式去自定义它。因为设置了这个主题,我们应该去使用 Material3 风格的UI组件,例如 MaterialButton 等,这里我们主要是讲UI的使用,具体样式我们也可以用原生的样式去逐步实现,这个因人而异,和因项目而异,读者们可以自己去研究研究这些谷歌官方推荐的UI样式,这里不再赘述,如果在项目中有使用这个主题,可以用上述中的方法去避免,当然,也可以直接修改样式,如下

    <resources xmlns:tools="http://schemas.android.com/tools">
        <!-- Base application theme. -->
        <style name="Base.Theme.HelloWorld" parent="Theme.MaterialComponents.NoActionBar.Bridge">
            <!-- Customize your light theme here. -->
            <!-- <item name="colorPrimary">@color/my_light_primary</item> -->
        </style>
    
        <style name="Theme.HelloWorld" parent="Base.Theme.HelloWorld" />
    </resources>
    

    rounded_button 圆角样式

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
        <solid android:color="#0080FF"/> <!-- 填充色 -->
        <corners android:radius="20dp"/> <!-- 圆角角度 -->
    </shape>
    

    button_state_selector 状态选择器

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <!-- 按下状态 -->
        <item android:state_pressed="true">
            <shape android:shape="rectangle">
                <solid android:color="#3700B3" /> <!-- 深紫色 -->
                <corners android:radius="8dp" />  <!-- 圆角 -->
                <stroke android:width="1dp" android:color="#6200EE" /> <!-- 边框 -->
            </shape>
        </item>
    
        <!-- 禁用状态 -->
        <item android:state_enabled="false">
            <shape android:shape="rectangle">
                <solid android:color="#CCCCCC" /> <!-- 灰色 -->
                <corners android:radius="8dp" />
            </shape>
        </item>
    
        <!-- 默认状态 -->
        <item>
            <shape android:shape="rectangle">
                <solid android:color="#6200EE" /> <!-- 紫色 -->
                <corners android:radius="8dp" />
            </shape>
        </item>
    </selector>
    
    class ButtonExampleActivity : AppCompatActivity() {
        private val btnBasic: Button by lazy { findViewById(R.id.btnBasic) }
        private val btnIcon: Button by lazy { findViewById(R.id.btnIcon) }
        private val btnCustomShape: Button by lazy { findViewById(R.id.btnCustomShape) }
        private val btnState: Button by lazy { findViewById(R.id.btnState) }
        private val btnDisabled: Button by lazy { findViewById(R.id.btnDisabled) }
        private val btnDynamic: Button by lazy { findViewById(R.id.btnDynamic) }
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_button_example)
    
            btnBasic.setOnClickListener {
                Toast.makeText(this, "这是最普通的按钮", Toast.LENGTH_SHORT).show()
            }
            btnIcon.setOnClickListener {
                Toast.makeText(this, "这是带图标的按钮", Toast.LENGTH_SHORT).show()
            }
            btnCustomShape.setOnClickListener {
                Toast.makeText(this, "这是圆角按钮", Toast.LENGTH_SHORT).show()
            }
            btnState.setOnClickListener {
                Toast.makeText(this, "状态选择器按钮", Toast.LENGTH_SHORT).show()
            }
            btnDisabled.setOnClickListener {
                it.isEnabled = false
                Toast.makeText(this, "按钮被禁用, 无法点击", Toast.LENGTH_SHORT).show()
            }
            btnDynamic.setOnClickListener {
                btnDisabled.isEnabled = !btnDisabled.isEnabled
                Toast.makeText(this, "修改禁用按钮状态", Toast.LENGTH_SHORT).show()
            }
    
        }
    }
    

    通过观察源码我们可以知道,Button 也是 TextView 派生类,并且常用的方法中没有独有的属性和方法, 那么 TextView 有的属性和方法这里就不在赘述了

  • CheckBox 多选按钮

    <?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"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".CheckBoxExampleActivity">
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="请选择您喜欢的水果:"
            android:textSize="20sp"
            android:layout_marginBottom="16dp"/>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:gravity="center"
            android:layout_marginBottom="16dp">
    
            <Button
                android:id="@+id/selectAllButton"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="全选"
                android:layout_marginEnd="8dp"/>
    
            <Button
                android:id="@+id/invertSelectionButton"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="反选"
                android:layout_marginStart="8dp"/>
        </LinearLayout>
    
        <android.widget.CheckBox
            android:id="@+id/appleCheckBox"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="苹果"
            android:textSize="18sp"
            android:button="@drawable/custom_checkbox"
            android:layout_marginBottom="8dp"/>
    
        <android.widget.CheckBox
            android:id="@+id/bananaCheckBox"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="香蕉"
            android:textSize="18sp"
            android:button="@drawable/custom_checkbox"
            android:layout_marginBottom="8dp"/>
    
        <android.widget.CheckBox
            android:id="@+id/orangeCheckBox"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="橙子"
            android:textSize="18sp"
            android:button="@drawable/custom_checkbox"
            android:layout_marginBottom="8dp"/>
    
        <android.widget.CheckBox
            android:id="@+id/watermelonCheckBox"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="西瓜"
            android:textSize="18sp"
            android:button="@drawable/custom_checkbox"
            android:layout_marginBottom="16dp"/>
    
        <Button
            android:id="@+id/submitButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="提交选择"
            android:layout_marginBottom="16dp"/>
    
        <TextView
            android:id="@+id/selectionCountText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="已选择 0 种水果"
            android:textSize="16sp"
            android:layout_marginBottom="8dp"/>
    
        <TextView
            android:id="@+id/resultText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="您还没有选择任何水果"
            android:textSize="18sp"/>
    
    </LinearLayout>
    

    custom_checkbox

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_checked="true" android:drawable="@drawable/ic_checkbox_checked" />
        <item android:state_checked="false" android:drawable="@drawable/ic_checkbox_unchecked" />
    </selector>
    

    ic_checkbox_checked

    <?xml version="1.0" encoding="utf-8"?>
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24"
        android:viewportHeight="24">
        <path
            android:fillColor="#2196F3"
            android:pathData="M19,3H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-9,14l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z" />
    </vector>
    

    ic_checkbox_unchecked

    <?xml version="1.0" encoding="utf-8"?>
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24"
        android:viewportHeight="24">
        <path
            android:fillColor="#B0BEC5"
            android:pathData="M19,3H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,16H5V5h14v14z" />
    </vector>
    
    class CheckBoxExampleActivity : AppCompatActivity() {
        private val appleCheckBox: CheckBox by lazy { findViewById(R.id.appleCheckBox) }
        private val bananaCheckBox: CheckBox by lazy { findViewById(R.id.bananaCheckBox) }
        private val orangeCheckBox: CheckBox by lazy { findViewById(R.id.orangeCheckBox) }
        private val watermelonCheckBox: CheckBox by lazy { findViewById(R.id.watermelonCheckBox) }
        private val selectAllButton: Button by lazy { findViewById(R.id.selectAllButton) }
        private val invertSelectionButton: Button by lazy { findViewById(R.id.invertSelectionButton) }
        private val submitButton: Button by lazy { findViewById(R.id.submitButton) }
        private val selectionCountText: TextView by lazy { findViewById(R.id.selectionCountText) }
        private val resultText: TextView by lazy { findViewById(R.id.resultText) }
        private val allCheckBoxes = mutableListOf<CheckBox>()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_check_box_example)
    
            // 初始化多选按钮集合
            initCheckBoxes()
    
            // 设置按钮点击事件
            setupButtonListeners()
    
            // 设置多选按钮监听器
            setupCheckBoxListeners()
        }
    
        private fun initCheckBoxes() {
            allCheckBoxes.apply {
                add(appleCheckBox)
                add(bananaCheckBox)
                add(orangeCheckBox)
                add(watermelonCheckBox)
            }
        }
    
        private fun setupButtonListeners() {
            // 全选按钮
            selectAllButton.setOnClickListener {
                allCheckBoxes.forEach { it.isChecked = true }
                updateSelectionInfo()
            }
    
            // 反选按钮
            invertSelectionButton.setOnClickListener {
                allCheckBoxes.forEach { it.toggle() }
                updateSelectionInfo()
            }
    
            // 提交按钮
            submitButton.setOnClickListener {
                val selectedItems = allCheckBoxes
                    .filter { it.isChecked }
                    .map { it.text.toString() }
    
                if (selectedItems.isEmpty()) {
                    resultText.text = "您还没有选择任何水果"
                    Toast.makeText(this, "请至少选择一种水果", Toast.LENGTH_SHORT).show()
                } else {
                    resultText.text = "您选择了: ${selectedItems.joinToString("、")}"
                    Toast.makeText(this, "提交成功! 共选择${selectedItems.size}种水果", Toast.LENGTH_SHORT).show()
                }
            }
        }
    
        private fun setupCheckBoxListeners() {
            allCheckBoxes.forEach { checkBox ->
                checkBox.setOnCheckedChangeListener { _, _ ->
                    updateSelectionInfo()
                }
            }
        }
    
        private fun updateSelectionInfo() {
            val selectedCount = allCheckBoxes.count { it.isChecked }
            selectionCountText.text = "已选择 $selectedCount 种水果"
    
            // 根据选择状态更新全选按钮文本
            selectAllButton.text = if (selectedCount == allCheckBoxes.size) "取消全选" else "全选"
        }
    
    }
    

    多选按钮特点:

    • 独立选择:每个选项可以单独选择或不选择

    • 无互斥:选择某个选项不会影响其他选项

    • 批量处理:可以同时获取所有被选中的选项

    常用方法

    方法说明示例
    isChecked()检查是否选中if (checkBox.isChecked)
    setChecked()设置选中状态checkBox.setChecked(true)
    toggle()切换选中状态checkBox.toggle()
    setOnCheckedChangeListener()状态变化监听checkBox.setOnCheckedChangeListener {...}
  • RadioButton 单选按钮

    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView 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:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".RadioButtonExampleActivity">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="请选择您的支付方式:"
                android:textSize="20sp"
                android:layout_marginBottom="16dp"/>
    
            <!-- 单选按钮组 -->
            <RadioGroup
                android:id="@+id/paymentRadioGroup"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:layout_marginBottom="16dp">
    
                <android.widget.RadioButton
                    android:id="@+id/alipayRadio"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="支付宝"
                    android:textSize="18sp"
                    android:button="@drawable/custom_radio_button"
                    android:layout_marginBottom="8dp"/>
    
                <android.widget.RadioButton
                    android:id="@+id/wechatRadio"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="微信支付"
                    android:textSize="18sp"
                    android:button="@drawable/custom_radio_button"
                    android:layout_marginBottom="8dp"/>
    
                <android.widget.RadioButton
                    android:id="@+id/unionpayRadio"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="银联支付"
                    android:textSize="18sp"
                    android:button="@drawable/custom_radio_button"
                    android:layout_marginBottom="8dp"/>
    
                <android.widget.RadioButton
                    android:id="@+id/otherRadio"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="其他方式(2秒后启用)"
                    android:textSize="18sp"
                    android:button="@drawable/custom_radio_button"
                    android:enabled="false"/>
            </RadioGroup>
    
            <!-- 动态操作按钮 -->
            <Button
                android:id="@+id/addOptionButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="添加数字货币选项"
                android:layout_marginBottom="8dp"/>
    
            <Button
                android:id="@+id/removeOptionButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="移除最后一个选项"
                android:layout_marginBottom="8dp"/>
    
            <Button
                android:id="@+id/confirmButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="确认选择"
                android:layout_marginBottom="8dp"/>
    
            <Button
                android:id="@+id/resetButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="重置选择"
                android:layout_marginBottom="16dp"/>
    
            <TextView
                android:id="@+id/selectionInfoText"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="当前选项数量: 4"
                android:textSize="16sp"
                android:layout_marginBottom="8dp"/>
    
            <TextView
                android:id="@+id/resultText"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="您还没有选择支付方式"
                android:textSize="18sp"/>
    
    
        </LinearLayout>
    </ScrollView>
    

    custom_radio_button

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_checked="true" android:drawable="@drawable/ic_radio_checked" />
        <item android:state_checked="false" android:drawable="@drawable/ic_radio_unchecked" />
    </selector>
    

    ic_radio_checked

    <?xml version="1.0" encoding="utf-8"?>
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24"
        android:viewportHeight="24">
        <path
            android:fillColor="#2196F3"
            android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
        <path
            android:fillColor="#2196F3"
            android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4z"/>
    </vector>
    

    ic_radio_unchecked

    <?xml version="1.0" encoding="utf-8"?>
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24"
        android:viewportHeight="24">
        <path
            android:fillColor="#B0BEC5"
            android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
    </vector>
    
    class RadioButtonExampleActivity : AppCompatActivity() {
        private val paymentRadioGroup: RadioGroup by lazy { findViewById(R.id.paymentRadioGroup) }
        private val alipayRadio: RadioButton by lazy { findViewById(R.id.alipayRadio) }
        private val wechatRadio: RadioButton by lazy { findViewById(R.id.wechatRadio) }
        private val unionpayRadio: RadioButton by lazy { findViewById(R.id.unionpayRadio) }
        private val otherRadio: RadioButton by lazy { findViewById(R.id.otherRadio) }
        private val addOptionButton: Button by lazy { findViewById(R.id.addOptionButton) }
        private val removeOptionButton: Button by lazy { findViewById(R.id.removeOptionButton) }
        private val confirmButton: Button by lazy { findViewById(R.id.confirmButton) }
        private val resetButton: Button by lazy { findViewById(R.id.resetButton) }
        private val selectionInfoText: TextView by lazy { findViewById(R.id.selectionInfoText) }
        private val resultText: TextView by lazy { findViewById(R.id.resultText) }
    
        private var dynamicOptionCount = 0
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_radio_button_example)
    
            // 设置单选按钮组监听
            paymentRadioGroup.setOnCheckedChangeListener { group, checkedId ->
                updateSelectionInfo()
                when (checkedId) {
                    R.id.alipayRadio -> showToast("选择了支付宝")
                    R.id.wechatRadio -> showToast("选择了微信支付")
                    R.id.unionpayRadio -> showToast("选择了银联支付")
                    R.id.otherRadio -> showToast("选择了其他方式")
                    else -> {
                        if (checkedId != -1) {
                            showToast("选择了动态添加的选项 ${findViewById<RadioButton>(checkedId).text}")
                        }
                    }
                }
            }
    
            // 添加选项按钮点击事件
            addOptionButton.setOnClickListener {
                addDynamicRadioOption()
            }
    
            // 移除选项按钮点击事件
            removeOptionButton.setOnClickListener {
                removeLastRadioOption()
            }
    
            // 确认按钮点击事件
            confirmButton.setOnClickListener {
                confirmSelection()
            }
    
            // 重置按钮点击事件
            resetButton.setOnClickListener {
                resetSelection()
            }
    
            // 2秒后启用"其他方式"选项并修改样式
            otherRadio.postDelayed({
                enableOtherOption()
            }, 2000)
    
        }
    
        private fun addDynamicRadioOption() {
            dynamicOptionCount++
            val newRadio = RadioButton(this).apply {
                text = "数字货币支付$dynamicOptionCount"
                id = View.generateViewId()
                textSize = 18f
                setTextColor(Color.parseColor("#9C27B0"))
                setPaddingRelative(20, 20, 20, 20)
            }
    
            paymentRadioGroup.addView(newRadio)
            updateSelectionInfo()
            showToast("已添加选项: ${newRadio.text}")
        }
    
        private fun removeLastRadioOption() {
            if (paymentRadioGroup.childCount > 4) { // 保留初始的4个选项
                val lastIndex = paymentRadioGroup.childCount - 1
                val lastOption = paymentRadioGroup.getChildAt(lastIndex) as RadioButton
                paymentRadioGroup.removeViewAt(lastIndex)
                updateSelectionInfo()
                showToast("已移除选项: ${lastOption.text}")
            } else {
                showToast("不能移除初始选项")
            }
        }
    
        private fun enableOtherOption() {
            otherRadio.isEnabled = true
            otherRadio.text = "信用卡支付"
            otherRadio.setTextColor(Color.parseColor("#8BC34A"))
            updateSelectionInfo()
        }
    
        private fun confirmSelection() {
            val selectedId = paymentRadioGroup.checkedRadioButtonId
            if (selectedId == -1) {
                resultText.text = "请先选择支付方式"
                showToast("请选择支付方式")
            } else {
                val selectedRadio = findViewById<RadioButton>(selectedId)
                resultText.text = "您选择的支付方式是: ${selectedRadio.text}"
                showToast("确认选择: ${selectedRadio.text}")
            }
        }
    
        private fun resetSelection() {
            paymentRadioGroup.clearCheck()
            resultText.text = "您还没有选择支付方式"
            updateSelectionInfo()
            showToast("已重置选择")
        }
    
        private fun updateSelectionInfo() {
            selectionInfoText.text = "当前选项数量: ${paymentRadioGroup.childCount}"
        }
    
        private fun showToast(message: String) {
            Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
        }
    }
    

    RadioButton 属性与方法

    属性说明示例值
    android:checked设置初始选中状态true/false
    android:buttonTint设置单选按钮圆圈颜色"#00AAFF"
    android:button自定义单选按钮图标@drawable/custom_radio
    方法说明示例
    getCheckedRadioButtonId()获取选中按钮IDval id = radioGroup.checkedRadioButtonId
    clearCheck()清除所有选择radioGroup.clearCheck()
    check(id)选中指定ID的按钮radioGroup.check(R.id.alipayRadio)
    setOnCheckedChangeListener()设置组选择变化监听radioGroup.setOnCheckedChangeListener

3. 图片

  • ImageView

    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView 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:orientation="vertical"
        tools:context=".ImageViewExampleActivity">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="普通图片" />
    
            <ImageButton
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:scaleType="centerCrop"
                android:adjustViewBounds="true"
                android:src="@drawable/npc_face"
                app:tint="#80FF0000" />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="图片 不缩放,保持原始尺寸" />
    
            <ImageButton
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:scaleType="center"
                android:src="@drawable/npc_face" />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="图片 等比例缩放,直至完全覆盖 ImageView 区域" />
    
            <ImageButton
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:scaleType="centerCrop"
                android:src="@drawable/npc_face" />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="图片 等比例缩放,确保完整显示在 ImageView 内" />
    
            <ImageButton
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:scaleType="centerInside"
                android:src="@drawable/npc_face" />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="图片 等比例缩放,对齐左上角" />
    
            <ImageButton
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:scaleType="fitStart"
                android:src="@drawable/npc_face" />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="图片 等比例缩放,对齐右下角" />
    
            <ImageButton
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:scaleType="fitEnd"
                android:src="@drawable/npc_face" />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="图片 等比例缩放,居中显示(默认模式)" />
    
            <ImageButton
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:scaleType="fitCenter"
                android:src="@drawable/npc_face" />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="图片 非等比例拉伸,填满整个 ImageView" />
    
            <ImageButton
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:scaleType="fitXY"
                android:src="@drawable/npc_face" />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="通过矩阵(Matrix)自定义缩放、平移或旋转(需代码配合)" />
    
            <ImageButton
                android:id="@+id/imageView"
                android:layout_width="200dp"
                android:layout_height="200dp"
                android:scaleType="matrix"
                android:src="@drawable/npc_face" />
    
        </LinearLayout>
    
    </ScrollView>
    
    class ImageViewExampleActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_image_view_example_activity)
    
            val imageView = findViewById<ImageView>(R.id.imageView)
            val matrix = Matrix()
            matrix.postScale(0.5f, 0.5f)  // 缩放到 50%
            matrix.postTranslate(50f, 50f) // 向右下平移 50px
            imageView.imageMatrix = matrix
            imageView.scaleType = ImageView.ScaleType.MATRIX
        }
    }
    

    ImageView 独有的核心属性

    属性说明
    android:src设置图片资源(@drawable/xxx@mipmap/xxx)。
    android:scaleType控制图片缩放和裁剪方式,共 8 种模式(见下文详解)。
    android:adjustViewBounds是否根据图片宽高比例自动调整 ImageView 的边界(需 layout_widthlayout_heightwrap_content)。
    app:tint为图片着色(如 #FF0000 红色),支持透明度。
    android:cropToPadding若为 true,图片裁剪时会考虑 padding 区域(仅部分 scaleType 生效)。

    常用方法

    方法说明
    setImageResource(int resId)动态设置图片资源。
    setImageDrawable(Drawable drawable)设置 Drawable 对象(如动态生成的图形)。
    setImageBitmap(Bitmap bitmap)直接设置位图(需自行处理内存优化)。
    setScaleType(ImageView.ScaleType type)动态设置缩放类型(如 FIT_CENTER)。
    getDrawable()获取当前显示的 Drawable 对象。

    scaleType详解

    android:scaleTypeImageView 的核心属性,决定图片如何适应控件尺寸:

    模式效果
    center居中显示,不缩放,超出部分裁剪。
    centerCrop等比例缩放图片,填满控件并居中裁剪多余部分(常用)。
    centerInside等比例缩放图片,完整显示在控件内(可能留空白)。
    fitStart等比例缩放,对齐左上角。
    fitEnd等比例缩放,对齐右下角。
    fitCenter等比例缩放,居中显示(默认值)。
    fitXY拉伸图片填满控件(可能变形)。
    matrix通过矩阵自定义缩放(需配合 setImageMatrix 使用)。

    ImageView 的独有特性

    1. scaleType 的精细化控制ImageView 是唯一提供 8 种缩放模式的控件,其他控件(如 ImageButton)继承此特性但更侧重交互。
    2. adjustViewBounds: 自动根据图片比例调整控件尺寸(需结合 wrap_content 使用)。
    3. cropToPadding: 仅在 scaleTypecenterCrop 等裁剪模式时生效,控制是否在裁剪时考虑 padding
    4. tint 着色功能: 直接通过 XML 或代码为图片添加颜色遮罩,无需修改原始图片资源。

4. 列表

  • RecyclerView

    • 定义RecyclerView 是 Android 中用于高效显示大数据集的滚动列表控件,继承自 ViewGroup,是 ListView 的升级版。
    • 核心特性
      • 视图复用机制:自动回收不可见项的视图,大幅优化内存和性能。
      • 布局灵活:通过 LayoutManager 支持列表、网格、瀑布流等多种布局。
      • 高度可定制:支持自定义动画、分割线、点击事件等。
    • 四大核心组件
      1. LayoutManager:控制布局方式(如 LinearLayoutManager)。
      2. Adapter:管理数据与视图的绑定。
      3. ViewHolder:缓存视图引用,避免重复 findViewById
      4. ItemDecoration:添加分割线、间距等装饰。
    <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:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".RecyclerViewExampleActivity">
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="8dp"
            android:scrollbars="vertical" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
    class RecyclerViewExampleActivity : AppCompatActivity() {
        private val adapter by lazy { MyAdapter() }
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_recycler_view_example)
    
            val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
            // 1. 设置布局管理器
            recyclerView.layoutManager = LinearLayoutManager(this)
            // 2. 准备数据
            val items = mutableListOf(
                Item(1, "头部", "头部", 0),
                Item(1, "标题1", "描述内容1", 1),
                Item(2, "标题2", "描述内容2", 1),
                Item(3, "标题3", "描述内容3", 1),
                Item(4, "标题4", "描述内容4", 1),
                Item(5, "尾部", "尾部", 2),
            )
            // 3. 设置 Adapter
            recyclerView.adapter = adapter
    
            adapter.setData(items)
    
            // 4. 添加分割线(可选)
            recyclerView.addItemDecoration(
                DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
            )
        }
    }
    

    Item

    data class Item(
        var id: Int = 0,
        var title: String = "",
        var description: String = "",
        var model: Int = 0 // 0: 头, 1: 内容, 2: 尾部
    )
    

    MyAdapter

    private const val TYPE_HEADER = 0
    private const val TYPE_FOOTER = 1
    private const val TYPE_ITEM = 2
    
    class MyAdapter() :
        RecyclerView.Adapter<RecyclerView.ViewHolder>() {
        private var items: MutableList<Item>? = null
    
        // 添加头、尾、数据项到集合
        fun setData(items: MutableList<Item>) {
            this.items = items;
            notifyDataSetChanged()
        }
    
        override fun getItemViewType(position: Int): Int {
            // 通过 items 中存储的不同数据类型,Adapter 可以判断当前项是头部、普通项还是尾部:
            return when (items?.get(position)!!.model) {
                0 -> TYPE_HEADER
                2 -> TYPE_FOOTER
                else -> TYPE_ITEM
            }
        }
    
    
        // 创建 ViewHolder
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
            return when (viewType) {
                TYPE_HEADER -> {
                    val view = LayoutInflater.from(parent.context)
                        .inflate(R.layout.item_head, parent, false)
                    HeaderViewHolder(view)
                }
    
                TYPE_FOOTER -> {
                    val view = LayoutInflater.from(parent.context)
                        .inflate(R.layout.item_footer, parent, false)
                    FooterViewHolder(view)
                }
    
                else -> {
                    val view = LayoutInflater.from(parent.context)
                        .inflate(R.layout.item_list, parent, false)  // 普通项布局
                    ItemViewHolder(view)
                }
            }
        }
    
        // 绑定数据到视图
        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
            // 在 onBindViewHolder 中,根据 items[position] 的内容填充不同布局:
            when (holder) {
                is HeaderViewHolder -> {
                    // 绑定头部数据(如设置广告图片)
                    holder.itemView.setOnClickListener {
                        Toast.makeText(holder.itemView.context, "点击头部", Toast.LENGTH_SHORT).show()
                    }
                }
    
                is FooterViewHolder -> {
                    // 绑定尾部数据(如设置加载状态)
                    holder.itemView.setOnClickListener {
                        Toast.makeText(holder.itemView.context, "点击尾部", Toast.LENGTH_SHORT).show()
                    }
                }
    
                is ItemViewHolder -> {
                    val item = items?.get(position) as Item  // Item 是数据类
                    holder.tvTitle.text = item.title
                    holder.tvDesc.text = item.description
                }
            }
        }
    
        // 返回数据总数
        override fun getItemCount() = items?.size!!
    
        // 定义不同 ViewHolder
        inner class HeaderViewHolder(view: View) : RecyclerView.ViewHolder(view) {
            val tvHeader: TextView = view.findViewById(R.id.tvHeader)
        }
    
        inner class FooterViewHolder(view: View) : RecyclerView.ViewHolder(view) {
            val tvFooter: TextView = view.findViewById(R.id.tvFooter)
        }
    
        inner class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
            val tvTitle: TextView = view.findViewById(R.id.item_title)
            val tvDesc: TextView = view.findViewById(R.id.item_description)
        }
    }
    

    item_list

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
    
        <TextView
            android:id="@+id/item_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    
        <TextView
            android:id="@+id/item_description"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    
    </LinearLayout>
    

    item_head

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="#FFA726"
        android:gravity="center">
    
        <TextView
            android:id="@+id/tvHeader"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="这是头部"
            android:textSize="20sp"
            android:textColor="#FFFFFF" />
    
    </LinearLayout>
    

    item_footer

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="#26FF00"
        android:gravity="center">
    
    
        <TextView
            android:id="@+id/tvFooter"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="这是尾部"
            android:textSize="20sp"
            android:textColor="#FFFFFF" />
    
    </LinearLayout>
    
    XML 属性
    属性说明
    android:scrollbars滚动条方向(vertical/horizontal)。
    android:clipToPadding是否允许滚动到 Padding 区域外(默认 true)。
    核心方法
    方法说明
    setLayoutManager(LayoutManager)设置布局方式(必选项)。
    setAdapter(Adapter)绑定数据适配器(必选项)。
    addItemDecoration(ItemDecoration)添加分割线或自定义装饰。
    smoothScrollToPosition(int)平滑滚动到指定位置。
    setItemAnimator(ItemAnimator)自定义项动画(如删除/添加效果)。

    RecyclerView 是一个十分强大并且在项目中十分常见的组件,具体一些更加精细的功能,在日后的项目开发中会逐步分析讲解。

5. 布局容器

  • LinearLayout 线性布局

    • 定义LinearLayout 是 Android 中最基础的布局容器之一,继承自 ViewGroup,其子视图(View)会按 水平(horizontal)垂直(vertical) 方向依次排列。

    • 核心特性

      • 单方向排列:所有子控件按单一方向(水平/垂直)依次排列。
      • 权重分配:通过 layout_weight 按比例分配剩余空间。
      • 简单高效:适合简单布局场景,性能优于嵌套布局。
    • 适用场景:表单布局、工具栏按钮排列、列表项布局等。

    垂直布局

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".data.LinearLayoutExampleActivity">
    
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="用户名" />
    
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="密码"
            android:inputType="textPassword" />
    
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:text="登录" /> <!-- 顶部外边距 -->
    
    </LinearLayout>
    

    效果

    • 三个子控件(输入框、输入框、按钮)垂直排列。

    • 输入框宽度填满父布局,按钮位于底部。

    • 控件间显示自定义分割线。

    水平布局

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".data.LinearLayoutExampleActivity">
    
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="首页" />
    
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="搜索" />
    
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="我的" />
    
    </LinearLayout>
    

    效果

    • 三个按钮水平排列,宽度按权重等分父布局。

    • 每个按钮宽度 = 父布局剩余空间 ÷ 总权重(1+1+1=3)。

    LinearLayout 的独有属性

    属性说明
    android:orientation排列方向:vertical(垂直)或 horizontal(水平)(必填属性)。
    android:gravity子控件的对齐方式(如 centerrightbottom)。
    android:layout_weight子控件的权重(需设置对应宽/高为 0dp 生效)。
    android:weightSum总权重值(可选,默认按子控件权重总和计算)。
    android:divider设置分割线 Drawable(需配合 showDividers 使用)。
    android:showDividers分割线显示位置:nonebeginningmiddleend
    android:baselineAligned是否基线对齐(默认 true,影响文本对齐)。

    常用方法

    方法说明
    setOrientation(int orientation)动态设置排列方向(LinearLayout.VERTICALHORIZONTAL)。
    setGravity(int gravity)动态设置子控件的对齐方式。
    setWeightSum(float weightSum)动态设置总权重。
    getOrientation()获取当前排列方向。

    通过灵活使用 LinearLayout 的方向、权重和对齐属性,可以快速构建高效且美观的界面。

  • RelativeLayout 相对布局

    • 定义RelativeLayout 是 Android 中一种基于相对位置的布局容器,继承自 ViewGroup,允许子视图(View)通过相对关系(如对齐父布局边缘、相对于其他视图的位置)进行排列。
    • 核心特性
      • 灵活定位:子控件的位置依赖于父布局或其他子控件。
      • 减少嵌套:通过相对关系替代多层 LinearLayout 嵌套。
      • 动态适配:适合复杂但无需绝对定位的界面。
    • 适用场景:用户资料页、卡片式布局、需要多控件联动的界面。
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:padding="16dp"
        tools:context=".RelativeLayoutExampleActivity">
    
        <!-- 头像(左上角) -->
        <ImageView
            android:id="@+id/iv_avatar"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:layout_alignParentStart="true"
            android:layout_alignParentTop="true"
            android:src="@drawable/npc_face" />
    
        <!-- 用户名(头像右侧,顶部对齐) -->
        <TextView
            android:id="@+id/tv_username"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignTop="@id/iv_avatar"
            android:layout_marginStart="16dp"
            android:layout_toEndOf="@id/iv_avatar"
            android:text="人生游戏牛马NPC1号"
            android:textSize="18sp" />
    
        <!-- 个人简介(用户名下方,右侧与父布局对齐) -->
        <TextView
            android:id="@+id/tv_bio"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/tv_username"
            android:layout_alignStart="@id/tv_username"
            android:layout_marginTop="8dp"
            android:text="Android 开发者 | 热爱技术分享"
            android:textColor="#666666" />
    
        <!-- 关注按钮(右下角) -->
        <Button
            android:layout_width="100dp"
            android:layout_height="40dp"
            android:layout_alignParentEnd="true"
            android:layout_alignParentBottom="true"
            android:layout_marginEnd="16dp"
            android:text="关注" />
    
    </RelativeLayout>
    

    效果

    • 头像位于左上角,用户名在其右侧顶部对齐。

    • 个人简介在用户名下方,左侧对齐用户名。

    • 关注按钮固定在右下角。

    • 所有控件通过相对关系定位,无需嵌套布局。

    RelativeLayout 的独有属性(子控件专用)

    属性说明
    layout_alignParent[Start/End/Top/Bottom]对齐父布局的某一边缘(true/false)。
    layout_centerInParent在父布局中居中(水平和垂直方向)。
    layout_centerHorizontal水平居中。
    layout_centerVertical垂直居中。
    layout_to[Start/End]Of位于某控件的左侧或右侧(值为其他控件的 @id)。
    layout_above位于某控件的上方。
    layout_below位于某控件的下方。
    layout_align[Start/End/Top/Bottom]与某控件的某一边对齐(值为其他控件的 @id)。
    layout_alignBaseline与其他控件的文本基线对齐(常用于 TextView)。

    RelativeLayout 容器属性

    属性说明
    android:gravity子控件的默认对齐方式(如 centerright)。
    android:ignoreGravity指定某个子控件不受 gravity 影响(值为控件 @id)。

    常用方法

    方法说明
    addRule(int verb, int anchor)动态为子控件添加相对规则(如 ALIGN_PARENT_TOP)。
    removeRule(int verb)移除子控件的相对规则。
    getRule(int verb)获取子控件的相对规则。

    通过熟练掌握 RelativeLayout 的相对规则,可以高效实现复杂但灵活的界面布局,尤其适合需要动态调整控件位置的场景。

  • ConstraintLayout 约束布局

    • 定义ConstraintLayout 是 Android 中基于约束关系的布局容器,继承自 ViewGroup,旨在通过扁平化布局层级实现复杂界面设计。
    • 核心特性
      • 灵活定位:子视图通过约束(Constraint)与其他视图或父布局关联。
      • 高性能:减少嵌套层级,优化测量和布局速度。
      • 可视化编辑:Android Studio 提供直观的拖拽布局设计器。
    • 适用场景:响应式布局、复杂界面(如仪表盘、动态表单)、多屏幕适配。
    <?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:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ConstraintLayoutExampleActivity">
    
        <!-- 头像(左侧,垂直居中) -->
        <ImageView
            android:id="@+id/iv_avatar"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:src="@drawable/npc_face"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent" />
    
        <!-- 用户名(头像右侧,顶部对齐) -->
        <TextView
            android:id="@+id/tv_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="人生游戏牛马NPC1号"
            android:textSize="20sp"
            app:layout_constraintStart_toEndOf="@id/iv_avatar"
            app:layout_constraintEnd_toStartOf="@id/btn_follow"
            app:layout_constraintTop_toTopOf="@id/iv_avatar"
            android:layout_marginStart="16dp" />
    
        <!-- 关注按钮(右侧,垂直居中) -->
        <Button
            android:id="@+id/btn_follow"
            android:layout_width="100dp"
            android:layout_height="40dp"
            android:text="关注"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent" />
    
        <!-- 个人简介(用户名下方,撑满剩余宽度) -->
        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="Android 开发者 | 技术博主"
            app:layout_constraintStart_toStartOf="@id/tv_name"
            app:layout_constraintEnd_toEndOf="@id/btn_follow"
            app:layout_constraintTop_toBottomOf="@id/tv_name"
            android:layout_marginTop="8dp" />
    
        <!-- 分割线(底部对齐) -->
        <View
            android:layout_width="0dp"
            android:layout_height="1dp"
            android:background="#E0E0E0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    效果

    • 标题居中,输入框水平撑满,登录按钮居中。
    • 忘记密码左下角,注册按钮右下角,两者底部对齐。
    • 所有控件通过约束关联,无嵌套布局。

    ConstraintLayout 独有属性(子控件专用)

    属性(前缀 app:说明
    layout_constraint[Start/End/Left/Right]_to[Start/End/Left/Right]Of控件的某边对齐到目标控件的某边(如 layout_constraintStart_toEndOf)。
    layout_constraintTop/Bottom_toTop/BottomOf控件的顶部/底部对齐到目标控件的顶部/底部。
    layout_constraintBaseline_toBaselineOf文本基线对齐(多用于 TextView)。
    layout_constraintCircle以目标控件为中心,通过半径和角度定位(圆形定位)。
    layout_constraintWidth/Height_default约束宽度/高度模式:spread(默认)、wrappercent
    layout_constraintWidth/Height_percent按父容器百分比设置宽高(需 default="percent")。
    layout_constraintHorizontal/Vertical_bias偏移比例(0~1),用于调整约束内的位置。
    layout_constraintDimensionRatio宽高比例(如 H,16:9 表示高是宽的 16/9)。
    layout_constraintHorizontal/Vertical_chainStyle链条样式:spreadspread_insidepacked

    容器级属性(ConstraintLayout 独有)

    属性说明
    app:barrierDirection创建屏障(Barrier),自动对齐多个控件边缘(left/right/top/bottom)。
    app:guideline创建引导线(Guideline),辅助定位(垂直或水平,固定位置或百分比)。

    常用方法

    方法说明
    ConstraintSet动态修改约束(代码中替换 XML 约束): clone()applyTo()connect()
    setVisibility(int visibility)控制控件可见性(自动调整约束关系)。
    getViewById(int id)快速获取子控件实例。

    通过掌握 ConstraintLayout 的约束系统与高级功能(如链条、屏障),可以轻松构建高性能的复杂界面,是现代化 Android 开发的核心布局方案。

  • FrameLayout 帧布局

    FrameLayout 是 Android 中最简单的布局容器之一,它的核心特性是 层叠显示子视图(后添加的子视图会覆盖在前面的子视图之上)。常用于以下场景:

    • 叠加显示多个视图(如图片+文字)
    • 单页面内切换不同视图(如 Fragment 容器)
    • 实现悬浮按钮、进度条覆盖层等效果
    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout 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:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".FrameLayoutExampleActivity">
    
        <!-- 底层图片 -->
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/ic_launcher_background"
            android:scaleType="centerCrop"/>
    
        <!-- 上层文字 -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello FrameLayout!"
            android:textSize="24sp"
            android:textColor="#FFFFFF"
            android:layout_gravity="center"
            android:padding="16dp"
            android:background="#66000000"/>
    
    </FrameLayout>
    

    效果说明

    • ImageView 作为底层显示图片
    • TextView 居中叠加在图片上方,带有半透明背景
    • 后添加的 TextView 会覆盖在 ImageView 之上

    通过合理使用 FrameLayout,可以实现高效的视图叠加效果,特别适合需要动态控制视图层级的场景。

6. 对话框与提示

  • AlertDialog 对话框

    AlertDialog 是 Android 中用于显示模态对话框的核心组件,常用于以下场景:

    • 提示关键信息(如警告、错误)
    • 确认用户操作(如删除确认)
    • 选择选项(单选/多选列表)
    • 自定义输入表单

    在对应地方显示弹窗

class AlertDialogExampleActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_alert_dialog_example)

        findViewById<Button>(R.id.btnAlertDialog).setOnClickListener {
            // 在 Activity 或 Fragment 中调用
            MaterialAlertDialogBuilder(this)
                .setTitle("删除确认")
                .setMessage("确定要删除此文件吗?")
                .setIcon(R.drawable.npc_face)
                .setPositiveButton("确定") { dialog, _ ->
                    // 执行删除操作
                    Toast.makeText(this, "点击了确认", Toast.LENGTH_SHORT).show()
                    dialog.dismiss()
                }
                .setNegativeButton("取消") { dialog, _ ->
                    Toast.makeText(this, "点击了取消", Toast.LENGTH_SHORT).show()
                    dialog.dismiss()
                }
                .setNeutralButton("稍后处理") { dialog, _ ->
                    Toast.makeText(this, "稍后处理", Toast.LENGTH_SHORT).show()
                    dialog.dismiss()
                }
                .show()
        }
    }
}

效果说明

Screenshot_20250422_102513.png

  • 标题 + 图标 + 消息内容
  • 底部双操作按钮(Material Design 风格)

核心属性配置

  1. 基础配置方法(Builder)
方法说明
setTitle(CharSequence)设置对话框标题
setMessage(CharSequence)设置消息内容
setIcon(int)设置标题栏图标
setView(View)添加自定义布局
setCancelable(boolean)是否允许点击外部关闭

这里只是简单介绍一下这这个组件,详细的效果在之后的项目中会一一展示出来