需求:實現(xiàn)一個編輯框,可以設置最低高度娘侍,和最大高度,當輸入的文字行數(shù)超過最低高度時泳炉,需要editText的高度隨著行數(shù)的增加而增加憾筏,當達到最高高度限制則不在繼續(xù)增高,而是可以上下滾動花鹅。當刪除文字的時候踩叭,先刪除行,當所有行高加起來不夠最高高度時翠胰,高度隨行數(shù)減少而減少容贝。就是這樣的一個需求,現(xiàn)在來制作自定義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();
? ? ? ? ? ? //控件內(nèi)容的總高度
? ? ? ? ? ? int scrollRange = editText.getLayout().getHeight();
? ? ? ? ? ? //控件實際顯示的高度
? ? ? ? ? ? int scrollExtent = editText.getHeight() - editText.getCompoundPaddingTop() - editText.getCompoundPaddingBottom();
? ? ? ? ? ? //控件內(nèi)容總高度與實際顯示高度的差值
? ? ? ? ? ? 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來實現(xiàn)的锻狗,我們來看里面的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="請?zhí)顚懳淖?
? ? 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的樣式進行設置。
還有一些監(jiān)聽掂僵,如:
editText.setOnTextChangeListener(new LimitedEditText.OnTextChangeListener(){
@Override
public void changeTextOver(String s) {
Toast.makeText(getApplicationContext(), "輸入字數(shù)達到最大值", Toast.LENGTH_SHORT).show();
? ? }
});
這個是達到最大字數(shù)限制的監(jiān)聽航厚。當然還有一些功能,大家自己去研究一下吧锰蓬。