Android自定義View實現(xiàn)可伸縮高度EditText

需求:實現(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)聽航厚。當然還有一些功能,大家自己去研究一下吧锰蓬。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末幔睬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子芹扭,更是在濱河造成了極大的恐慌麻顶,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舱卡,死亡現(xiàn)場離奇詭異辅肾,居然都是意外死亡,警方通過查閱死者的電腦和手機灼狰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門宛瞄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人交胚,你說我怎么就攤上這事份汗。” “怎么了蝴簇?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵杯活,是天一觀的道長。 經(jīng)常有香客問我熬词,道長旁钧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任互拾,我火速辦了婚禮歪今,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颜矿。我一直安慰自己寄猩,他們只是感情好,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布骑疆。 她就那樣靜靜地躺著田篇,像睡著了一般替废。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上泊柬,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天椎镣,我揣著相機與錄音,去河邊找鬼兽赁。 笑死状答,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的闸氮。 我是一名探鬼主播剪况,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼教沾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了授翻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤堪唐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后淮菠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體男公,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年枢赔,在試婚紗的時候發(fā)現(xiàn)自己被綠了拥知。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片踏拜。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖低剔,靈堂內(nèi)的尸體忽然破棺而出速梗,到底是詐尸還是另有隱情襟齿,我是刑警寧澤姻锁,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布猜欺,位于F島的核電站,受9級特大地震影響替梨,放射性物質(zhì)發(fā)生泄漏装黑。R本人自食惡果不足惜弓熏,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挽鞠。 院中可真熱鬧,春花似錦信认、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽款熬。三九已至,卻和暖如春贤牛,著一層夾襖步出監(jiān)牢的瞬間则酝,已是汗流浹背殉簸。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工沽讹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人椭微。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像蝇率,于是被迫代替她去往敵國和親刽沾。 傳聞我的和親對象是個殘疾皇子本慕,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容