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文件:

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

    }

});

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,848评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,529评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,824评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,329评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,227评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,879评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,218评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,877评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,159评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,155评论 2 315
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,987评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,736评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,273评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,407评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,663评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,158评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,517评论 2 339

推荐阅读更多精彩内容