需求:实现一个编辑框,可以设置最低高度,和最大高度,当输入的文字行数超过最低高度时,需要editText的高度随着行数的增加而增加,当达到最高高度限制则不在继续增高,而是可以上下滚动。当删除文字的时候,先删除行,当所有行高加起来不够最高高度时,高度随行数减少而减少。就是这样的一个需求,现在来制作自定义View,直接上View代码。
public class LimitedEditTextextends FrameLayout {
private LimitedViewHolder limitedViewHolder;
private int MAX_LENGHT =300;
private final String NUM_TEXT ="%d/%d";
private int originHeight =0;
private int maxHeight =0;
private String hint ="";
private static float textSize =0;
private static float lenghtTextSize =0;
private OnTextChangeListener listener;
private static int textColor;
private static int lengthTextColor;
private static Drawable background;
private static boolean isShowEditNum =true;
public void setStyle(Style style) {
if (style ==null)
throw new NullPointerException("this style is not be null!!!");
limitedViewHolder.style = style;
limitedViewHolder.style.setRootView(this);
limitedViewHolder.setStyle(style);
}
public void setShowEditNum(boolean show) {
isShowEditNum = show;
}
public void setTextColor(int textColor) {
this.textColor = textColor;
limitedViewHolder.editText.setTextColor(textColor);
}
public void setLengthTextColor(int lengthTextColor) {
this.lengthTextColor = lengthTextColor;
limitedViewHolder.tvNum.setTextColor(lengthTextColor);
}
public void setOnTextChangeListener(OnTextChangeListener listener) {
this.listener = listener;
}
public void setHint(String hint) {
this.hint = hint;
limitedViewHolder.editText.setHint(hint);
}
public void setMaxHeight(int maxHeight) {
this.maxHeight = maxHeight;
}
public LimitedEditText(@NonNull Context context) {
super(context);
init(null);
}
public LimitedEditText(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public LimitedEditText(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(AttributeSet attrs) {
getAttrs(attrs);
createRootView();
initListener();
background = getBackground();
}
private void getAttrs(AttributeSet attrs) {
if (attrs !=null) {
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.LimitedEditText);
MAX_LENGHT = ta.getInt(R.styleable.LimitedEditText_maxLenght, MAX_LENGHT);
hint = ta.getString(R.styleable.LimitedEditText_hint);
textSize = ta.getFloat(R.styleable.LimitedEditText_textSize, 12);
lenghtTextSize = ta.getFloat(R.styleable.LimitedEditText_lenghtTextSize, 10);
textColor = ta.getColor(R.styleable.LimitedEditText_textColor, 0);
lengthTextColor = ta.getColor(R.styleable.LimitedEditText_lengthTextColor, 0);
isShowEditNum = ta.getBoolean(R.styleable.LimitedEditText_isShowEditNum, true);
}
}
private void createRootView() {
View inflate = LayoutInflater.from(getContext()).inflate(R.layout.limited_length_edit_text, this, false);
limitedViewHolder =new LimitedViewHolder(inflate);
addView(inflate);
post(() -> originHeight = getMeasuredHeight());
}
private void initListener() {
limitedViewHolder.editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
limitedViewHolder.onTextChanged(s.toString());
limitedViewHolder.setTvNum(s.length());
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
private class LimitedViewHolder {
EditText editText;
TextView tvNum;
LinearLayout llRoot;
private int editTextHeight;
private int layoutHeight;
int line =1;
public Style style;
private LimitedViewHolder(View view) {
editText = view.findViewById(R.id.edit_text);
tvNum = view.findViewById(R.id.tv_num);
llRoot = view.findViewById(R.id.ll_root);
// style = new Style();
setStyle(style);
getEditTextHeiget();
}
private void setStyle(Style style) {
if (style !=null) {
float textSize = style.getTextSize();
if (textSize !=0)
editText.setTextSize(textSize);
float lengthTextSize = style.getLengthTextSize();
if (lengthTextSize !=0)
tvNum.setTextSize(lengthTextSize);
int textColor = style.getTextColor();
if (textColor !=0)
editText.setTextColor(textColor);
int lengthTextColor = style.getLengthTextColor();
if (lengthTextColor !=0)
tvNum.setTextColor(lengthTextColor);
}
editText.setHint(hint);
editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(MAX_LENGHT)});
tvNum.setText(String.format(NUM_TEXT, 0, MAX_LENGHT));
tvNum.setVisibility(isShowEditNum ? VISIBLE : GONE);
}
private void getEditTextHeiget() {
editText.post(() -> editTextHeight = editText.getMeasuredHeight());
llRoot.post(() -> layoutHeight = llRoot.getMeasuredHeight());
}
private void onTextChanged(String s) {
TextPaint paint = editText.getPaint();
int lineCount = editText.getLineCount();
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
float height = (fontMetrics.bottom - fontMetrics.top) *0.8f;//这里获取到单行文字高度
Log.i("asdas", "高度:" + height +":" + editTextHeight);
boolean isAddLine = lineCount > line;
boolean isSubLine = line > lineCount;
if (isAddLine && isNextRowOver(height)) {
addEditTextHeight(height);
}else if (isSubLine && !isVerticalScroll()) {
addEditTextHeight(-height);
}
line = lineCount;
if (listener !=null) {
listener.onTextChangeListener(s);
}
if (s.length() >= MAX_LENGHT) {
onChangeTextOverMax(s);
}else {
setBackground(background);
setStyle(style);
}
}
private void onChangeTextOverMax(String s) {
if (listener !=null)
listener.changeTextOver(s);
if (style ==null)
return;
ObjectAnimator overAnimator = style.getOverAnimator();
if (overAnimator !=null) {
overAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
style.onAnimationOver(animation);
}
});
overAnimator.start();
}
int textColor = style.getOverTextColor();
if (textColor !=0)
setTextColor(textColor);
int overLengthColor = style.getOverLengthColor();
if (overLengthColor !=0) {
tvNum.setTextColor(overLengthColor);
}
}
/**
* 下一行是否超出editText高度
*
* @param height 单行文字高度
* @return true 超出
*/
private boolean isNextRowOver(float height) {
float v = height * (editText.getLineCount() +1);
Log.d("vvvvvvvvvvv", "文字高度:" + v);
return v >= editTextHeight;
}
/**
* 判断是否可以滚动
*
* @return
*/
private boolean isVerticalScroll() {
//滚动的距离
int scrollY = editText.getScrollY();
//控件内容的总高度
int scrollRange = editText.getLayout().getHeight();
//控件实际显示的高度
int scrollExtent = editText.getHeight() - editText.getCompoundPaddingTop() - editText.getCompoundPaddingBottom();
//控件内容总高度与实际显示高度的差值
int scrollDifference = scrollRange - scrollExtent;
if (scrollDifference ==0) {
return false;
}
return (scrollY >0) || (scrollY < scrollDifference -1);
}
private void addEditTextHeight(float height) {
ViewGroup.LayoutParams layoutParams = getLayoutParams();
float overHeight = layoutParams.height + height;
if (overHeight <= originHeight) {
layoutParams.height = originHeight;
setLayoutParams(layoutParams);
return;
}
if (maxHeight !=0 && overHeight >= maxHeight) {
layoutParams.height = maxHeight;
setLayoutParams(layoutParams);
return;
}
layoutParams.height += height;
setLayoutParams(layoutParams);
}
private void setTvNum(int lenght) {
tvNum.setText(String.format(NUM_TEXT, lenght, MAX_LENGHT));
}
}
/**
* 默认样式表
*/
public static class Style {
private View view;
public final void setRootView(View view) {
this.view = view;
}
public float getTextSize() {
return textSize;
}
public float getLengthTextSize() {
return lenghtTextSize;
}
public int getTextColor() {
return textColor;
}
public int getLengthTextColor() {
return lengthTextColor;
}
public int getOverTextColor() {
return textColor;
}
public int getOverLengthColor() {
return lengthTextColor;
}
public ObjectAnimator getOverAnimator() {
//先变小后变大
PropertyValuesHolder scaleXValuesHolder = PropertyValuesHolder.ofKeyframe(View.SCALE_X,
Keyframe.ofFloat(0f, 1.0f),
Keyframe.ofFloat(0.25f, 0.9f),
Keyframe.ofFloat(0.5f, 1.1f),
Keyframe.ofFloat(0.75f, 1.1f),
Keyframe.ofFloat(1.0f, 1.0f)
);
PropertyValuesHolder scaleYValuesHolder = PropertyValuesHolder.ofKeyframe(View.SCALE_Y,
Keyframe.ofFloat(0f, 1.0f),
Keyframe.ofFloat(0.25f, 0.9f),
Keyframe.ofFloat(0.5f, 1.1f),
Keyframe.ofFloat(0.75f, 1.1f),
Keyframe.ofFloat(1.0f, 1.0f)
);
//先往左再往右
PropertyValuesHolder rotateValuesHolder = PropertyValuesHolder.ofKeyframe(View.ROTATION,
Keyframe.ofFloat(0f, 0f),
Keyframe.ofFloat(0.1f, -5f),
Keyframe.ofFloat(0.2f, 5f),
Keyframe.ofFloat(0.3f, -5f),
Keyframe.ofFloat(0.4f, 5f),
Keyframe.ofFloat(0.5f, -5f),
Keyframe.ofFloat(0.6f, 5f),
Keyframe.ofFloat(0.7f, -5f),
Keyframe.ofFloat(0.8f, 5f),
Keyframe.ofFloat(0.9f, -5f),
Keyframe.ofFloat(1.0f, 0f)
);
ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(view, scaleXValuesHolder, scaleYValuesHolder, rotateValuesHolder);
objectAnimator.setDuration(500);
return objectAnimator;
}
public final void setBackground(Drawable drawable) {
view.setBackground(drawable);
}
public void onAnimationOver(Animator animation) {
}
}
public abstract static class OnTextChangeListener {
public void onTextChangeListener(String s) {
}
public abstract void changeTextOver(String s);
}
}
这就是主要的View代码,里面通过Fragment来实现的,我们来看里面的layout文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
android:id="@+id/ll_root"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingRight="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:paddingLeft="16dp"
android:minHeight="198dp">
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@null"
android:gravity="top" />
android:id="@+id/ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
android:id="@+id/tv_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
通过LayoutInflater将这个布局加入到自定义View中进行引用。
<declare-styleable name="LimitedEditText">
<attr name="maxLenght" format="integer" />
<attr name="hint" format="string" />
<attr name="textSize" format="float" />
<attr name="lenghtTextSize" format="float" />
<attr name="textColor" format="color" />
<attr name="lengthTextColor" format="color" />
<attr name="isShowEditNum" format="boolean" />
</declare-styleable>
还有自定义属性的使用。
<LimitedEditText
android:id="@+id/limited_text"
android:layout_width="match_parent"
android:layout_height="198dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
android:background="@drawable/shape_round_10_background"
app:hint="请填写文字"
app:lenghtTextSize="16"
app:isShowEditNum="true"
app:lengthTextColor="#777"
app:maxLenght="300"
app:textColor="@color/black"
app:textSize="18" />
使用时就是这么简单。
设置的布局高度就是输入框的最低高度也就是原本的高度,当输入的文字多余这个高度的时候,这个View会增加自己的高度。
我们可以对View设置样式表,如:
editText.setStyle(new LimitedEditText.Style() {
@Override
public int getOverLengthColor() {
return ContextCompat.getColor(getApplicationContext(),R.color.red);
}
@Override
public void onAnimationOver(Animator animation) {
super.onAnimationOver(animation);
setBackground(ContextCompat.getDrawable(getApplicationContext(), R.drawable.shape_round_10_background_border));
}
});
Style这个类就是VIew的样式表,可以通过集成Style的方式对View的样式进行设置。
还有一些监听,如:
editText.setOnTextChangeListener(new LimitedEditText.OnTextChangeListener(){
@Override
public void changeTextOver(String s) {
Toast.makeText(getApplicationContext(), "输入字数达到最大值", Toast.LENGTH_SHORT).show();
}
});
这个是达到最大字数限制的监听。当然还有一些功能,大家自己去研究一下吧。