Android自定义View实现可伸缩高度EditText

需求:实现一个编辑框,可以设置最低高度,和最大高度,当输入的文字行数超过最低高度时,需要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文件:

    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" />

通过LayoutInflater将这个布局加入到自定义View中进行引用。

   

   

   

   

   

   

   

还有自定义属性的使用。

    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();

    }

});

这个是达到最大字数限制的监听。当然还有一些功能,大家自己去研究一下吧。

你可能感兴趣的:(Android自定义View实现可伸缩高度EditText)