Android语言基础教程(50)Android基本组件之文本框与编辑框:[特殊字符]别让APP变哑巴!文本框与编辑框——Android界的社牛与社恐大揭秘
👻 第一章:认识Android世界的“社牛”和“社恐”
在Android应用的社交圈里,如果说ImageView是朋友圈晒图达人,Button是派对气氛组,那么TextView就是那个侃侃而谈的“社牛”——永远在输出信息,从不停歇。而EditText则是内心戏十足的“社恐”——表面安静如鸡,实则随时准备接收用户的悄悄话。
还记得你第一次打开某个APP时,那个热情洋溢的“欢迎来到XXX!”吗?那是TextView在打招呼。而当你要输入账号密码时,那个小心翼翼等你打字的空白框,就是EditText在默默递话筒。
说来有趣,这对CP其实是血缘至亲——EditText全盘继承了TextView的能力,却发展出截然不同的性格。就像哥哥继承家产当霸道总裁(专注展示),弟弟偏要搞艺术创作(专注输入)。今天,就让我们扒开这对兄弟的底裤,看看它们到底多有意思!
🎨 第二章:TextView——不只是“文字打印器”
2.1 基础人设:佛系展示官
TextView堪称组件界的劳模,从显示“Hello World”到展示万字小说,从单行标题到多行详情,无处不在。但你以为它只是个无情的文字机器?大错特错!
它的核心技能:
📢 单向输出:只显示,不接收输入(用户想修改?没门!)🎭 表情包大师:支持Emoji、颜文字、( ͡° ͜ʖ ͡°) 等各种骚操作🎨 美颜十级:字体、颜色、大小随心变,CSS看了都直呼内行
2.2 十八般武艺属性详解
别看TextView长得老实,打扮起来也是花枝招展。来感受下它的“美妆包”:
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我是标题我怕谁!"
android:textColor="#FF6B6B"
android:textSize="24sp"
android:textStyle="bold"
android:background="@drawable/bg_rounded"
android:padding="16dp"
android:gravity="center"
android:shadowColor="#66000000"
android:shadowDx="2"
android:shadowDy="2"
android:shadowRadius="4"
android:lineSpacingExtra="8dp"/>
重点属性吐槽:
:控制文字在框内的位置,相当于给文字找座位(左中右随便坐)
android:gravity:文字要不要加粗、斜体,或者又粗又斜(戏真多)
android:textStyle系列:给文字加阴影,瞬间从2D变3D,立体感拉满
android:shadow
2.3 编程花式玩法
光会在XML里静态装逼怎么行?真正的强者都在代码里动态换装:
val tvStatus = findViewById<TextView>(R.id.tv_status)
// 秒变红色警告文字
tvStatus.apply {
text = "账号密码错误!"
setTextColor(Color.RED)
setBackgroundColor(Color.parseColor("#FFF0F0"))
// 加点动画效果,让提示更吸睛
animate().scaleX(1.1f).scaleY(1.1f).setDuration(300).start()
}
// 玩点高级的——部分文字变色
val spannable = SpannableString("第二杯半价!立即购买")
spannable.setSpan(
ForegroundColorSpan(Color.RED),
3, 5,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
tvStatus.text = spannable
⌨️ 第三章:EditText——用户的“情绪收纳箱”
3.1 双重人格:安静时像天使,输入时像战场
如果说TextView是输出型人格,那EditText就是典型的输入型人格。它表面上是个乖巧的空白框,实际上内心OS:“用户爸爸快给我输入!我要数据!我要内容!”
它的核心定位:
🎤 话题引导者:通过hint属性温柔提示“请输入账号”🛡️ 秩序维护者:限制输入类型、长度、格式,防止用户乱来🔍 信息过滤师:实时验证输入内容,及时给出反馈
3.2 防手残属性大全
用户的手就像帕金森,什么奇葩输入都干得出来。EditText的职责就是提前设防:
<EditText
android:id="@+id/et_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入邮箱地址"
android:inputType="textEmailAddress"
android:maxLines="1"
android:maxLength="50"
android:drawableStart="@drawable/ic_email"
android:drawablePadding="8dp"
android:backgroundTint="#2196F3"
android:autofillHints="email"/>
防呆设计解析:
:这是大杀器!能自动调出对应键盘:
android:inputType
:密码键盘,显示***
textPassword:数字键盘,告别误触字母
number:拨号键盘,专治各种不服
phone:带@的邮箱专用键盘
textEmailAddress
:防话痨,输入超过长度自动截断
android:maxLength:左边加个小图标,瞬间提升B格
android:drawableStart
3.3 监听与验证——给输入上紧箍咒
光有静态防御还不够,动态监控才是王道:
val etPassword = findViewById<EditText>(R.id.et_password)
// 实时监听文字变化
etPassword.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) {
// 文字变化中:实时验证
val password = s.toString()
if (password.length < 6) {
etPassword.error = "密码至少6位!"
} else {
etPassword.error = null // 清除错误提示
}
}
override fun afterTextChanged(s: Editable?) {
// 文字变化后:可以搞事了
}
})
// 焦点变化监听:失去焦点时进行最终验证
etPassword.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) {
val password = etPassword.text.toString()
if (!isValidPassword(password)) {
Toast.makeText(this, "密码格式不正确!", Toast.LENGTH_SHORT).show()
}
}
}
🎯 第四章:实战!打造个人资料编辑页
理论说再多都是纸上谈兵,来撸个真实场景——几乎每个APP都有的“个人资料编辑页”。
4.1 布局文件:颜值即正义
<?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="match_parent"
android:orientation="vertical"
android:padding="20dp">
<!-- 标题 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="编辑个人资料"
android:textSize="24sp"
android:textStyle="bold"
android:gravity="center"
android:layout_marginBottom="30dp"/>
<!-- 昵称输入 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="昵称"
android:textStyle="bold"
android:layout_marginBottom="8dp"/>
<EditText
android:id="@+id/et_nickname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="给自己取个酷炫的昵称"
android:maxLines="1"
android:maxLength="20"
android:backgroundTint="#FF6B6B"
android:layout_marginBottom="20dp"/>
<!-- 个性签名 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="个性签名"
android:textStyle="bold"
android:layout_marginBottom="8dp"/>
<EditText
android:id="@+id/et_bio"
android:layout_width="match_parent"
android:layout_height="120dp"
android:gravity="top"
android:hint="用一句话介绍自己吧~"
android:maxLength="100"
android:inputType="textMultiLine"
android:backgroundTint="#4ECDC4"
android:layout_marginBottom="20dp"/>
<!-- 显示已输入字数 -->
<TextView
android:id="@+id/tv_counter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:text="0/100"
android:textColor="#666"
android:layout_marginBottom="30dp"/>
<!-- 保存按钮 -->
<Button
android:id="@+id/btn_save"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="保存资料"
android:background="#FF6B6B"
android:textColor="#FFF"
android:textSize="16sp"/>
</LinearLayout>
4.2 逻辑代码:让界面活起来
class ProfileEditActivity : AppCompatActivity() {
private lateinit var etNickname: EditText
private lateinit var etBio: EditText
private lateinit var tvCounter: TextView
private lateinit var btnSave: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_profile_edit)
// 绑定视图
initViews()
// 设置监听器
setupListeners()
}
private fun initViews() {
etNickname = findViewById(R.id.et_nickname)
etBio = findViewById(R.id.et_bio)
tvCounter = findViewById(R.id.tv_counter)
btnSave = findViewById(R.id.btn_save)
// 设置初始数据
etNickname.setText("Android小王子")
etBio.setText("热爱编码的移动开发者")
updateCounter(etBio.text.length)
}
private fun setupListeners() {
// 个性签名字数实时统计
etBio.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(s: CharSequence?, p1: Int, p2: Int, p3: Int) {
updateCounter(s?.length ?: 0)
}
override fun afterTextChanged(s: Editable?) {}
})
// 保存按钮点击事件
btnSave.setOnClickListener {
saveProfile()
}
// 昵称输入完成验证
etNickname.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) {
validateNickname()
}
}
}
private fun updateCounter(length: Int) {
tvCounter.text = "$length/100"
// 超过80字给个警告色
if (length > 80) {
tvCounter.setTextColor(Color.RED)
} else {
tvCounter.setTextColor(Color.parseColor("#666"))
}
}
private fun validateNickname(): Boolean {
val nickname = etNickname.text.toString().trim()
return when {
nickname.isEmpty() -> {
etNickname.error = "昵称不能为空"
false
}
nickname.length < 2 -> {
etNickname.error = "昵称至少2个字符"
false
}
nickname.length > 20 -> {
etNickname.error = "昵称最多20个字符"
false
}
else -> {
etNickname.error = null
true
}
}
}
private fun saveProfile() {
val nickname = etNickname.text.toString().trim()
val bio = etBio.text.toString().trim()
if (!validateNickname()) {
Toast.makeText(this, "请检查昵称格式", Toast.LENGTH_SHORT).show()
return
}
// 模拟保存操作
Toast.makeText(this, "资料保存成功!", Toast.LENGTH_SHORT).show()
// 这里实际开发中应该调用API保存数据
Log.d("Profile", "昵称: $nickname, 签名: $bio")
}
}
🚀 第五章:进阶技巧——从能用→好用
5.1 自定义样式——告别原生丑
原生EditText长得太“安卓”,是时候给它整容了:
<!-- res/drawable/et_custom_bg.xml -->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFF"/>
<corners android:radius="8dp"/>
<stroke android:width="1dp" android:color="#DDD"/>
</shape>
<!-- 使用自定义背景 -->
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/et_custom_bg"
android:padding="16dp"/>
5.2 输入过滤——精确控制内容
除了长度限制,还能玩得更细:
// 只允许输入字母和数字
val letterNumberFilter = InputFilter { source, start, end, dest, dstart, dend ->
val regex = Regex("[a-zA-Z0-9]*")
if (source.toString().matches(regex)) {
source // 允许输入
} else {
"" // 拒绝输入
}
}
etNickname.filters = arrayOf(letterNumberFilter)
// 或者用更简单的方式——正则表达式过滤
val noEmojiFilter = InputFilter { source, _, _, _, _, _ ->
source.toString().replace(Regex("[uD83C-uDBFFuDC00-uDFFF]"), "")
}
etNickname.filters = arrayOf(noEmojiFilter)
5.3 自动完成——预测用户意图
// 模拟常用昵称推荐
val commonNicknames = arrayOf("Android大神", "Java小王子", "Kotlin爱好者", "代码诗人")
val adapter = ArrayAdapter(this, android.R.layout.simple_dropdown_item_1line, commonNicknames)
etNickname.setAdapter(adapter)
etNickname.threshold = 1 // 输入1个字符就开始提示
💡 第六章:避坑指南与性能优化
6.1 常见坑爹场景
内存泄漏:TextWatcher用完不移除?Activity哭了
// 正确做法:在适当时机移除监听器
override fun onDestroy() {
etBio.removeTextChangedListener(textWatcher)
super.onDestroy()
}
键盘遮挡:EditText在底部?输入时被键盘挡住?
<!-- 在Manifest中配置 -->
<activity
android:name=".ProfileEditActivity"
android:windowSoftInputMode="adjustResize"/>
性能杀手:在TextWatcher里做耗时操作?卡死你没商量
// 错误示范:在输入监听里请求网络
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
// 千万别这么干!会频繁请求网络
api.search(s.toString())
}
// 正确做法:使用延迟搜索
private fun setupSearch() {
etSearch.addTextChangedListener(object : TextWatcher {
private var timer: Timer? = null
override fun onTextChanged(s: CharSequence?, p1: Int, p2: Int, p3: Int) {
timer?.cancel()
timer = Timer().apply {
schedule(object : TimerTask() {
override fun run() {
// 延迟500毫秒后搜索
runOnUiThread { doSearch(s.toString()) }
}
}, 500)
}
}
})
}
🎊 第七章:结语——让你的APP”会说话”
看到这里,你应该明白:TextView和EditText这对看似简单的组合,实际上蕴含着巨大的交互能量。
TextView就像APP的播音员,负责清晰、准确、美观地传递信息。它的每一个属性调整,都是在优化用户体验的细节。
EditText则是APP的倾听者,通过精心的输入控制和实时反馈,让用户在输入过程中感到被理解、被引导、被保护。
一个优秀的Android开发者,应该像导演调教演员一样,充分挖掘每个组件的潜力。当你的TextView能够用合适的语气说话,当你的EditText能够温柔地引导用户输入,你的APP就真正”活”过来了。
现在,打开Android Studio,用今天学到的技巧,打造一个让用户忍不住想打字的输入体验吧!记住:好的界面自己会说话,而伟大的界面会让用户愿意跟它对话。


