Android Java 编辑框
自定义编辑框 参考博客园、参考CSDN
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.CycleInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.EditText;
import java.util.jar.Attributes;
public class ClearEditText extends EditText implements View.OnFocusChangeListener, TextWatcher {
//删除按钮的引用
private Drawable mClearDrawable;
private Context context;
//控件是否有焦点
private boolean hasFocus;
public ClearEditText(Context context) {
this(context, null);
}
public ClearEditText(Context context, AttributeSet attrs) {
//这里构造方法也很重要,不加这个很多属性不能再XML里面定义
this(context, attrs, android.R.attr.editTextStyle);
}
public ClearEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
//获取EditText的DrawableRight,假如没有设置我们就使用默认的图片
mClearDrawable = getCompoundDrawables()[2];
if (mClearDrawable == null) {
mClearDrawable = getResources().getDrawable(R.drawable.delete_selector);
}
mClearDrawable.setBounds(0, 0, mClearDrawable.getIntrinsicWidth(), mClearDrawable.getIntrinsicHeight());
//默认设置隐藏图标
setClearIconVisible(false);
//设置焦点改变的监听
setOnFocusChangeListener(this);
//设置输入框里面内容发生改变的监听
addTextChangedListener(this);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mClearDrawable != null && event.getAction() == MotionEvent.ACTION_UP) {
int x = (int) event.getX();
//判断触摸点是否在水平范围内
boolean isInnerWidth = (x > (getWidth() - getTotalPaddingRight())) &&
(x < (getWidth() - getPaddingRight()));
//获取删除图标的边界,返回一个Rect对象
Rect rect = mClearDrawable.getBounds();
//获取删除图标的高度
int height = rect.height();
int y = (int) event.getY();
/计算图标底部到控件底部的距离
int distance = (getHeight() - height) / 2;
//判断触摸点是否在竖直范围内(可能会有点误差)
//触摸点的纵坐标在distance到(distance+图标自身的高度)之内,则视为点中删除图标
boolean isInnerHeight = (y > distance) && (y < (distance + height));
if (isInnerHeight && isInnerWidth) {
this.setText("");
}
}
return super.onTouchEvent(event);
}
/**
* 设置清除图标的显示与隐藏,调用setCompoundDrawables为EditText绘制上去
*
* @param visible
*/
private void setClearIconVisible(boolean visible) {
Drawable right = visible ? mClearDrawable : null;
setCompoundDrawables(getCompoundDrawables()[0], getCompoundDrawables()[1],
right, getCompoundDrawables()[3]);
}
/**
* 当ClearEditText焦点发生变化的时候,判断里面字符串长度设置清除图标的显示与隐藏
*/
@Override
public void onFocusChange(View v, boolean hasFocus) {
this.hasFocus = hasFocus;
if (hasFocus) {
setClearIconVisible(getText().length() > 0);
} else {
setClearIconVisible(false);
}
}
/**
* 当输入框里面内容发生变化的时候回调的方法
*/
@Override
public void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
if (hasFocus) {
setClearIconVisible(text.length() > 0);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
}
/**
* 设置晃动动画
*/
public void setShakeAnimation() {
this.startAnimation(shakeAnimation(5));
}
/**
* 晃动动画
*
* @param counts 1秒钟晃动多少下
* @return
*/
public static Animation shakeAnimation(int counts) {
Animation translateAnimation = new TranslateAnimation(0, 10, 0, 0);
translateAnimation.setInterpolator(new CycleInterpolator(counts));
translateAnimation.setDuration(1000);
return translateAnimation;
}
}
使用编辑框
<?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"
tools:context=".MainActivity">
<com.java.editbox.ClearEditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="60dp"
android:layout_marginRight="10dp"
android:background="@drawable/login_edittext_bg"
android:drawableLeft="@drawable/icon_user"
android:drawableRight="@drawable/delete_selector"
android:hint="输入用户名"
android:singleLine="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:background="@drawable/login_button_bg"
android:text="登录"
android:gravity="center"
android:textColor="@android:color/white"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private Toast mToast;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ClearEditText username = (ClearEditText) findViewById(R.id.username);
final TextView mButton = (TextView) findViewById(R.id.login);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (TextUtils.isEmpty(username.getText())) {
//设置晃动
username.setShakeAnimation();
//设置提示
showToast("用户名不能为空!");
}
}
});
}
// 显示Toast消息
private void showToast(String msg) {
if (mToast == null) {
mToast = Toast.makeText(this, msg, Toast.LENGTH_SHORT);
} else {
mToast.setText(msg);
}
mToast.show();
}
}
Android Kotlin 编辑框
import android.content.Context
import android.graphics.drawable.Drawable
import android.text.Editable
import android.text.TextWatcher
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.View.OnFocusChangeListener
import android.view.animation.Animation
import android.view.animation.CycleInterpolator
import android.view.animation.TranslateAnimation
import androidx.appcompat.widget.AppCompatEditText
class ClearEditText @JvmOverloads constructor(
context: Context?,
attrs: AttributeSet? = null,
defStyle: Int = android.R.attr.editTextStyle
) :
AppCompatEditText(context!!, attrs, defStyle), OnFocusChangeListener, TextWatcher {
//删除按钮的引用
private var mClearDrawable: Drawable? = null
private val context: Context? = null
//控件是否有焦点
private var hasFocus = false
init {
init()
}
private fun init() {
//获取EditText的DrawableRight,假如没有设置我们就使用默认的图片
mClearDrawable = compoundDrawables[2]
if (mClearDrawable == null) {
mClearDrawable = resources.getDrawable(R.drawable.delete_selector)
}
mClearDrawable?.setBounds(
0,
0,
mClearDrawable!!.intrinsicWidth,
mClearDrawable!!.intrinsicHeight
)
//默认设置隐藏图标
setClearIconVisible(false)
//设置焦点改变的监听
onFocusChangeListener = this
//设置输入框里面内容发生改变的监听
addTextChangedListener(this)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
if (mClearDrawable != null && event.action == MotionEvent.ACTION_UP) {
val x = event.x.toInt()
//判断触摸点是否在水平范围内
val isInnerWidth = x > width - totalPaddingRight && x < width - paddingRight
//获取删除图标的边界,返回一个Rect对象
val rect = mClearDrawable!!.bounds
//获取删除图标的高度
val height = rect.height()
val y = event.y.toInt()
//计算图标底部到控件底部的距离
val distance = (getHeight() - height) / 2
//判断触摸点是否在竖直范围内(可能会有点误差)
//触摸点的纵坐标在distance到(distance+图标自身的高度)之内,则视为点中删除图标
val isInnerHeight = y > distance && y < distance + height
if (isInnerHeight && isInnerWidth) {
this.setText("")
}
}
return super.onTouchEvent(event)
}
/**
* 设置清除图标的显示与隐藏,调用setCompoundDrawables为EditText绘制上去
*
* @param visible
*/
private fun setClearIconVisible(visible: Boolean) {
val right = if (visible) mClearDrawable else null
setCompoundDrawables(
compoundDrawables[0], compoundDrawables[1],
right, compoundDrawables[3]
)
}
/**
* 当ClearEditText焦点发生变化的时候,判断里面字符串长度设置清除图标的显示与隐藏
*/
override fun onFocusChange(v: View, hasFocus: Boolean) {
this.hasFocus = hasFocus
if (hasFocus) {
setClearIconVisible(text?.length!! > 0)
} else {
setClearIconVisible(false)
}
}
/**
* 当输入框里面内容发生变化的时候回调的方法
*/
override fun onTextChanged(
text: CharSequence,
start: Int,
lengthBefore: Int,
lengthAfter: Int
) {
if (hasFocus) {
setClearIconVisible(text.length > 0)
}
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun afterTextChanged(s: Editable) {}
/**
* 设置晃动动画
*/
fun setShakeAnimation() {
startAnimation(shakeAnimation(5))
}
companion object {
/**
* 晃动动画
*
* @param counts 1秒钟晃动多少下
* @return
*/
fun shakeAnimation(counts: Int): Animation {
val translateAnimation: Animation = TranslateAnimation(0f, 10f, 0f, 0f)
translateAnimation.interpolator = CycleInterpolator(counts.toFloat())
translateAnimation.duration = 1000
return translateAnimation
}
}
}
Android Compose 编辑框
导入依赖包
dependencies {
......
implementation ("androidx.activity:activity-compose:1.3.1")
implementation("androidx.compose.material:material:1.4.3")
implementation("androidx.compose.ui:ui-tooling:1.4.3")
}
启用Compose
Compose 自定义编辑框
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
var shake by remember { mutableStateOf(false) }
val transition = updateTransition(targetState = shake, label = "shake")
val shakeOffset by transition.animateDp(label = "",
transitionSpec = {
keyframes {
durationMillis = 300
0.dp at 0
(-10).dp at 25 with LinearOutSlowInEasing
0.dp at 50
10.dp at 75
0.dp at 100
(-8).dp at 125
0.dp at 150
8.dp at 175
0.dp at 200
(-5).dp at 225
0.dp at 250
5.dp at 275
0.dp at 300
}
}) {
if (it) 0.dp else 0.dp
}
Column {
DecorateTextField(shakeOffset = shakeOffset)
RoundedCornerClickText(onClick = {
shake = !shake
})
}
}
}
@Composable
fun DecorateTextField(shakeOffset: Dp) {
var text by rememberSaveable {
mutableStateOf("")
}
Box(
Modifier
.padding(start = 10.dp, end = 10.dp, top = 20.dp)
.fillMaxWidth()
.offset(x = shakeOffset),
contentAlignment = Alignment.TopCenter
) {
BasicTextField(
value = text,
onValueChange = {
text = it
},
textStyle = TextStyle(color = Color.Black),
cursorBrush = SolidColor(Color.Blue),
decorationBox = { innerTextField ->//decorationBox内部负责编写输入框样式
Row(
Modifier
.fillMaxWidth()
.height(50.dp)
.border(0.3.dp, Color.Blue, RoundedCornerShape(10.dp)),
verticalAlignment = Alignment.CenterVertically
) {
Spacer(Modifier.width(5.dp))
Icon(
Icons.Default.AccountCircle,
tint = Color.Black,
contentDescription = null
)
Spacer(Modifier.width(5.dp))
Box(modifier = Modifier.padding(top = 7.dp, bottom = 7.dp, end = 7.dp)) {
// 判断是否输入文本,如果输入则清空隐藏提示
if (text.isEmpty()) {
Text(
text = "请输入搜索内容",
style = TextStyle(
color = Color(0, 0, 0, 128),
fontSize = 16.sp,
)
)
}
//自定义样式这行代码是关键,没有这一行输入文字后无法展示,光标也看不到
innerTextField()
}
}
}
)
// 编辑框存在文本值
if (text.isNotEmpty()) {
// 添加一个清除按钮,点击时清除文本
val interactionSource = remember { MutableInteractionSource() }
Box(
modifier = Modifier
.align(Alignment.CenterEnd)
.clickable(
interactionSource = interactionSource,
indication = null
) {
text = ""
}
.padding(end = 8.dp) // 根据需要调整内边距
) {
Icon(
imageVector = Icons.Default.Close, // 使用合适的图标
contentDescription = "clear",
modifier = Modifier.size(24.dp), // 根据需要调整图标大小
tint = Color.Unspecified // 根据需要调整颜色
)
}
}
}
}
@Composable
fun RoundedCornerClickText(onClick: () -> Unit) {
Box(modifier = Modifier.padding(start = 10.dp, end = 10.dp, top = 40.dp)) {
Button(
modifier = Modifier
.fillMaxWidth()
.height(50.dp),
onClick = onClick,
shape = RoundedCornerShape(5.dp),
colors = ButtonDefaults.buttonColors(Color.Green)
) {
Text(text = "登录", style = TextStyle(color = Color.White))
}
}
}
}
IOS Object-c 编辑框
自定义编辑框
使用编辑框
抖动动画
IOS Swift 编辑框
自定义编辑框
使用编辑框
抖动动画
Flutter 编辑框
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
const shakeCount = 4;
const shakeDuration = Duration(milliseconds: 600);
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
//定义一个controller
final TextEditingController _editController = TextEditingController();
// 显示一键删除按钮
bool visible = false;
late final AnimationController _shakeController =
AnimationController(vsync: this, duration: shakeDuration);
@override
void initState() {
_shakeController.addListener(() {
if (_shakeController.status == AnimationStatus.completed) {
_shakeController.reset();
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Column(
children: [
AnimatedBuilder(
animation: _shakeController,
builder: (context, child) {
final sineValue =
sin(shakeCount * 2 * pi * _shakeController.value);
return Transform.translate(
offset: Offset(sineValue * 10, 0),
child: child,
);
},
child: _editBox()),
_loginButton(),
],
));
}
Widget _editBox() {
return Container(
margin: const EdgeInsets.only(left: 10.0, right: 10.0, top: 20.0),
padding: const EdgeInsets.only(left: 10.0),
height: 50.0,
decoration: BoxDecoration(
color: Colors.transparent,
border: Border.all(color: Colors.purple, width: 0.6),
borderRadius: const BorderRadius.all(Radius.circular(10.0))),
child: Row(
children: [
const Icon(Icons.supervisor_account),
Expanded(
flex: 1,
child: TextField(
autofocus: true,
decoration: null,
onChanged: (v) {
if (kDebugMode) {
print('编辑框的值:$v');
}
_changeVisible();
},
controller: _editController, //设置controller
),
),
Visibility(
visible: visible,
child: GestureDetector(
onTap: () {
_editController.clear();
_changeVisible();
},
child: Container(
alignment: Alignment.center,
width: 40.0,
height: 40.0,
color: Colors.transparent,
child: const Icon(Icons.close),
),
),
)
],
));
}
void _changeVisible() {
// 获取编辑框文本值
var v = _editController.value.text;
// 编辑框文本值长度小于等于1刷新界面
if (!visible || v.length <= 1) {
setState(() {
visible = (v.isNotEmpty);
});
}
}
Widget _loginButton() {
return GestureDetector(
onTap: () {
_shakeController.reset();
_shakeController.forward();
},
child: Container(
width: double.infinity,
alignment: Alignment.center,
height: 50.0,
margin: const EdgeInsets.only(left: 10.0, right: 10.0, top: 50.0),
decoration: BoxDecoration(
color: Colors.lightBlue, borderRadius: BorderRadius.circular(10.0)),
child: const Text(
'登录',
style: TextStyle(color: Colors.white),
),
),
);
}
}
ReactNative 编辑框
import React, {Component,useRef,useEffect} from 'react';
import { AppRegistry,TextInput, Button,View,
StyleSheet,Image,TouchableOpacity,
Text,Animated,Easing} from 'react-native';
import {name as appName} from './app.json';
export default class TextInputComponent extends Component {
constructor(props) {
super(props);
this.state = {
inputValue:'' // 编辑框初始化值
}
}
// 清空文本
clearText=()=> {
this.setState({inputValue:''});
}
render() {
return (
<View style={styles.editView}>
<Image source={require('./assets/user_logo.png')} style={styles.editViewImage}/>
<TextInput
style={styles.input}
value={this.state.inputValue}
onChangeText={(text)=>{this.setState({inputValue:text})}}
placeholder="请输入编辑框内容"
/>
{this.state.inputValue && (<TouchableOpacity onPress={this.clearText}>
<Image
source={require('./assets/edit_delete.png')}
style={styles.deleteViewImage}
/>
</TouchableOpacity>
)}
</View>
)
}
}
// 抖动动画
const ShakeAnimation = () => {
const shakeAnimation = useRef(new Animated.Value(0)).current;
shake = () => {
// 定义动画:在X轴左右抖动
Animated.sequence([
Animated.timing(shakeAnimation, {
toValue: 3,
duration: 100,//毫秒
useNativeDriver: true,//使用原生动画驱动,默认不启用(false)
}),
Animated.timing(shakeAnimation, {
toValue: -3,
duration: 100,//毫秒
useNativeDriver: true,
}),
Animated.spring(shakeAnimation, {
toValue: 0,
useNativeDriver: true,
}),
]).start(); // 开始动画
};
// 应用动画值
const shakeStyle = {
transform: [
{
translateX: shakeAnimation.interpolate({
inputRange: [0, 1],
outputRange: [0, 3], // 抖动的幅度
}),
},
],
};
return (<View style={styles.container}>
<Animated.View style={shakeStyle}>
<TextInputComponent/>
</Animated.View>
<Button title="登录" color="blue" onPress={this.shake}/>
</View>);
};
const styles = StyleSheet.create({
editViewImage:{
width:25,
height:25,
marginLeft:10,
},
deleteViewImage:{
width:25,
height:25,
marginRight:10,
},
editView:{
flexDirection: 'row',
alignItems: 'center',
marginBottom: 40,
borderWidth: 1,
borderColor: 'gray',
borderRadius: 10,
},
container: {
flex: 1,
justifyContent: 'top',
padding: 20,
},
input: {
height: 45,
padding: 5,
flex: 1,
},
});
AppRegistry.registerComponent(appName, () => ShakeAnimation);
案例
切换到分支 flutter_editbox