registeredListeners = new ArrayList<>();
17 |
18 | void registerListener (View.OnFocusChangeListener listener) {
19 | registeredListeners.add(listener);
20 | }
21 |
22 | void clearListeners() {
23 | registeredListeners.clear();
24 | }
25 |
26 | @Override
27 | public void onFocusChange(View view, boolean b) {
28 |
29 | for(View.OnFocusChangeListener listener:registeredListeners) {
30 | listener.onFocusChange(view, b);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/textfieldboxes/src/main/java/studio/carbonylgroup/textfieldboxes/ExtendedEditText.java:
--------------------------------------------------------------------------------
1 | package studio.carbonylgroup.textfieldboxes;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Canvas;
7 | import android.graphics.ColorFilter;
8 | import android.graphics.Paint;
9 | import android.graphics.PixelFormat;
10 | import android.graphics.drawable.Drawable;
11 | import android.support.annotation.NonNull;
12 | import android.support.design.widget.TextInputEditText;
13 | import android.util.AttributeSet;
14 | import android.view.View;
15 |
16 |
17 | /**
18 | * Text Field Boxes
19 | * Created by CarbonylGroup on 2017/09/01
20 | */
21 | @SuppressWarnings("unused")
22 | public class ExtendedEditText extends TextInputAutoCompleteTextView {
23 |
24 | public int DEFAULT_TEXT_COLOR;
25 | private OnFocusChangeListener defaultFocusListener;
26 | private CompositeListener focusListener = new CompositeListener();
27 |
28 | /**
29 | * prefix Label text at the start.
30 | */
31 | protected String prefix;
32 |
33 | /**
34 | * suffix Label text at the end.
35 | */
36 | protected String suffix;
37 |
38 | /**
39 | * Prefix text color. DEFAULT_TEXT_COLOR by default.
40 | */
41 | protected int prefixTextColor;
42 |
43 | /**
44 | * Suffix text color. DEFAULT_TEXT_COLOR by default.
45 | */
46 | protected int suffixTextColor;
47 |
48 | public ExtendedEditText(Context context) {
49 |
50 | this(context, null);
51 | super.setOnFocusChangeListener(focusListener);
52 | initDefaultColor();
53 | }
54 |
55 | public ExtendedEditText(Context context, AttributeSet attrs) {
56 |
57 | this(context, attrs, android.R.attr.editTextStyle);
58 | super.setOnFocusChangeListener(focusListener);
59 | initDefaultColor();
60 | handleAttributes(context, attrs);
61 | }
62 |
63 | public ExtendedEditText(Context context, AttributeSet attrs, int defStyle) {
64 |
65 | super(context, attrs, defStyle);
66 | super.setOnFocusChangeListener(focusListener);
67 | initDefaultColor();
68 | handleAttributes(context, attrs);
69 | }
70 |
71 | protected void initDefaultColor() {
72 |
73 | Resources.Theme theme = getContext().getTheme();
74 | TypedArray themeArray;
75 |
76 | /* Get Default Text Color From Theme */
77 | themeArray = theme.obtainStyledAttributes(new int[]{android.R.attr.textColorTertiary});
78 | DEFAULT_TEXT_COLOR = themeArray.getColor(0, 0);
79 |
80 | themeArray.recycle();
81 | }
82 |
83 | @Override
84 | protected void onFinishInflate() {
85 |
86 | super.onFinishInflate();
87 |
88 | /* Texts */
89 | setPrefix(this.prefix);
90 | setSuffix(this.suffix);
91 |
92 | /* Colors */
93 | setPrefixTextColor(this.prefixTextColor);
94 | setSuffixTextColor(this.suffixTextColor);
95 | }
96 |
97 | @Override
98 | public void setOnFocusChangeListener(OnFocusChangeListener l) {
99 |
100 | focusListener.clearListeners();
101 | focusListener.registerListener(defaultFocusListener);
102 | focusListener.registerListener(l);
103 | }
104 |
105 | void setDefaultOnFocusChangeListener(OnFocusChangeListener l) {
106 |
107 | defaultFocusListener = l;
108 | focusListener.registerListener(l);
109 | }
110 |
111 | protected void handleAttributes(Context context, AttributeSet attrs) {
112 | try {
113 |
114 | TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.ExtendedEditText);
115 |
116 | /* Texts */
117 | this.prefix = styledAttrs.getString(R.styleable.ExtendedEditText_prefix)
118 | == null ? "" : styledAttrs.getString(R.styleable.ExtendedEditText_prefix);
119 | this.suffix = styledAttrs.getString(R.styleable.ExtendedEditText_suffix)
120 | == null ? "" : styledAttrs.getString(R.styleable.ExtendedEditText_suffix);
121 |
122 | /* Colors */
123 | this.prefixTextColor = styledAttrs
124 | .getInt(R.styleable.ExtendedEditText_prefixTextColor, DEFAULT_TEXT_COLOR);
125 | this.suffixTextColor = styledAttrs
126 | .getInt(R.styleable.ExtendedEditText_suffixTextColor, DEFAULT_TEXT_COLOR);
127 | styledAttrs.recycle();
128 |
129 | } catch (Exception e) {
130 | e.printStackTrace();
131 | }
132 | }
133 |
134 | private class TextDrawable extends Drawable {
135 |
136 | private TextDrawable() {
137 |
138 | setBounds(0, 0, (int) getPaint().measureText(prefix) + 2, (int) getTextSize());
139 | setPadding(0, 0, (int) getPaint().measureText(suffix) - 2, 0);
140 | }
141 |
142 | @Override
143 | public void draw(@NonNull Canvas canvas) {
144 |
145 | final int lineBase = getLineBounds(0, null);
146 | final int lineBottom = getLineBounds(getLineCount() - 1, null);
147 | final float endX = getWidth() - getPaint().measureText(suffix) - 2;
148 |
149 | Paint paint = getPaint();
150 | paint.setColor(prefixTextColor);
151 | canvas.drawText(prefix, 0, canvas.getClipBounds().top + lineBase, paint);
152 | paint.setColor(suffixTextColor);
153 | canvas.drawText(suffix, endX, canvas.getClipBounds().top + lineBottom, paint);
154 | }
155 |
156 | @Override
157 | public void setAlpha(int alpha) {/* Not supported */}
158 |
159 | @Override
160 | public void setColorFilter(ColorFilter colorFilter) {/* Not supported */}
161 |
162 | @Override
163 | public int getOpacity() {
164 | return PixelFormat.OPAQUE;
165 | }
166 | }
167 |
168 | public void setPrefix(String prefix) {
169 |
170 | this.prefix = prefix;
171 | this.setCompoundDrawables(new TextDrawable(), null, null, null);
172 | }
173 |
174 | public void setSuffix(String suffix) {
175 |
176 | this.suffix = suffix;
177 | this.setCompoundDrawables(new TextDrawable(), null, null, null);
178 | }
179 |
180 | public void setPrefixTextColor(int color) {
181 | this.prefixTextColor = color;
182 | }
183 |
184 | public void setSuffixTextColor(int color) {
185 | this.suffixTextColor = color;
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/textfieldboxes/src/main/java/studio/carbonylgroup/textfieldboxes/SimpleTextChangedWatcher.java:
--------------------------------------------------------------------------------
1 | package studio.carbonylgroup.textfieldboxes;
2 |
3 | import android.text.TextWatcher;
4 |
5 | public interface SimpleTextChangedWatcher {
6 | /**
7 | * Called after a {@link TextWatcher} observes a text change event
8 | * @param theNewText the (now) current text of the text field
9 | * @param isError true if the current text means the textview shows as in error state
10 | */
11 | void onTextChanged(String theNewText, boolean isError);
12 | }
13 |
--------------------------------------------------------------------------------
/textfieldboxes/src/main/java/studio/carbonylgroup/textfieldboxes/TextFieldBoxes.java:
--------------------------------------------------------------------------------
1 | package studio.carbonylgroup.textfieldboxes;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Color;
7 | import android.graphics.PorterDuff;
8 | import android.graphics.PorterDuffColorFilter;
9 | import android.graphics.drawable.ColorDrawable;
10 | import android.graphics.drawable.Drawable;
11 | import android.support.annotation.Nullable;
12 | import android.support.v4.content.ContextCompat;
13 | import android.support.v4.view.ViewCompat;
14 | import android.support.v4.widget.TextViewCompat;
15 | import android.support.v7.widget.AppCompatImageButton;
16 | import android.support.v7.widget.AppCompatTextView;
17 | import android.text.Editable;
18 | import android.text.TextWatcher;
19 | import android.util.AttributeSet;
20 | import android.util.TypedValue;
21 | import android.view.LayoutInflater;
22 | import android.view.View;
23 | import android.view.ViewGroup;
24 | import android.view.inputmethod.InputMethodManager;
25 | import android.widget.EditText;
26 | import android.widget.FrameLayout;
27 | import android.widget.RelativeLayout;
28 | import android.widget.Space;
29 | import android.widget.TextView;
30 |
31 | import java.lang.reflect.Field;
32 |
33 |
34 | /**
35 | * Text Field Boxes
36 | * Created by CarbonylGroup on 2017/08/25
37 | */
38 | @SuppressWarnings("unused")
39 | public class TextFieldBoxes extends FrameLayout {
40 |
41 | /**
42 | * all the default colors to be used on light or dark themes.
43 | */
44 | public int DEFAULT_ERROR_COLOR;
45 | public int DEFAULT_PRIMARY_COLOR;
46 | public int DEFAULT_TEXT_COLOR;
47 | public int DEFAULT_DISABLED_TEXT_COLOR;
48 | public int DEFAULT_BG_COLOR;
49 | public int DEFAULT_FG_COLOR;
50 |
51 | /**
52 | * whether the text field is enabled. True by default.
53 | */
54 | protected boolean enabled;
55 |
56 | /**
57 | * labelText text at the top.
58 | */
59 | protected String labelText;
60 |
61 | /**
62 | * helper Label text at the bottom.
63 | */
64 | protected String helperText;
65 |
66 | /**
67 | * max characters count limit. 0 means no limit. 0 by default.
68 | */
69 | protected int maxCharacters;
70 |
71 | /**
72 | * min characters count limit. 0 means no limit. 0 by default.
73 | */
74 | protected int minCharacters;
75 |
76 | /**
77 | * the text color for the helperLabel text. DEFAULT_TEXT_COLOR by default.
78 | */
79 | protected int helperTextColor;
80 |
81 | /**
82 | * the text color for the counterLabel text. DEFAULT_TEXT_COLOR by default.
83 | */
84 | protected int mCounterTextColor;
85 |
86 | /**
87 | * the text color for when something is wrong (e.g. exceeding max characters, setError()).
88 | * DEFAULT_ERROR_COLOR by default.
89 | */
90 | protected int errorColor;
91 |
92 | /**
93 | * the color for the underline, the floating label text and the icon signifier tint when HAVING focus.
94 | * Current theme primary color by default.
95 | */
96 | protected int primaryColor;
97 |
98 | /**
99 | * the color for the underline, the floating label text and the icon signifier tints when NOT HAVING focus.
100 | * DEFAULT_TEXT_COLOR by default.
101 | */
102 | protected int secondaryColor;
103 |
104 | /**
105 | * the color for panel at the back. DEFAULT_BG_COLOR by default.
106 | */
107 | protected int panelBackgroundColor;
108 |
109 | /**
110 | * the resource ID of the icon signifier. 0 by default.
111 | */
112 | protected int iconSignifierResourceId;
113 |
114 | /**
115 | * the resource ID of the icon at the end. 0 by default.
116 | */
117 | protected int endIconResourceId;
118 |
119 | /**
120 | * whether the icon signifier will change its color when gaining or losing focus
121 | * as the label and the bottomLine do. True by default.
122 | */
123 | protected boolean isResponsiveIconColor;
124 |
125 | /**
126 | * whether to show the clear button at the end of the EditText. False by default.
127 | */
128 | protected boolean hasClearButton;
129 |
130 | /**
131 | * whether the EditText is having the focus. False by default.
132 | */
133 | protected boolean hasFocus;
134 |
135 | /**
136 | * Whether the label is fixed at top when there's a hint. False by default.
137 | */
138 | protected boolean alwaysShowHint;
139 |
140 | /**
141 | * whether the field uses a dense spacing between its elements.
142 | * Usually useful in a multi-field form. False by default.
143 | */
144 | protected boolean useDenseSpacing;
145 |
146 | /**
147 | * whether the field uses a rtl direction for 'Persian (Farsi)' and 'Arabic' languages
148 | * False by default.
149 | */
150 | protected boolean rtl;
151 |
152 | protected int labelColor = -1;
153 | protected int labelTopMargin = -1;
154 | protected int ANIMATION_DURATION = 100;
155 | protected boolean onError = false;
156 | protected boolean activated = false;
157 | /**
158 | * See {@link #setManualValidateError(boolean)}
159 | */
160 | protected boolean isManualValidateError = false;
161 |
162 | protected View panel;
163 | protected View bottomLine;
164 | protected Space labelSpace;
165 | protected Space labelSpaceBelow;
166 | protected ViewGroup editTextLayout;
167 | protected ExtendedEditText editText;
168 | protected RelativeLayout rightShell;
169 | protected RelativeLayout upperPanel;
170 | protected RelativeLayout bottomPart;
171 | protected RelativeLayout inputLayout;
172 | protected AppCompatTextView helperLabel;
173 | protected AppCompatTextView counterLabel;
174 | protected AppCompatTextView floatingLabel;
175 | protected AppCompatImageButton clearButton;
176 | protected AppCompatImageButton iconImageButton;
177 | protected AppCompatImageButton endIconImageButton;
178 | protected InputMethodManager inputMethodManager;
179 |
180 | protected SimpleTextChangedWatcher textChangeListener;
181 | private ColorDrawable mPasswordToggleDummyDrawable;
182 | private Drawable mOriginalEditTextEndDrawable;
183 |
184 | public TextFieldBoxes(Context context) {
185 |
186 | super(context);
187 | init();
188 | }
189 |
190 | public TextFieldBoxes(Context context, AttributeSet attrs) {
191 |
192 | super(context, attrs);
193 | init();
194 | handleAttributes(context, attrs);
195 | }
196 |
197 | public TextFieldBoxes(Context context, AttributeSet attrs, int defStyleAttr) {
198 | super(context, attrs, defStyleAttr);
199 | init();
200 | handleAttributes(context, attrs);
201 | }
202 |
203 | protected void init() {
204 | initDefaultColor();
205 | inputMethodManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
206 | }
207 |
208 | protected void initDefaultColor() {
209 |
210 | Resources.Theme theme = getContext().getTheme();
211 | TypedArray themeArray;
212 |
213 | /* Get Default Error Color From Theme */
214 | DEFAULT_ERROR_COLOR = ContextCompat.getColor(getContext(), R.color.A400red);
215 |
216 | /* Get Default Background Color From Theme */
217 | themeArray = theme.obtainStyledAttributes(new int[]{android.R.attr.colorForeground});
218 | DEFAULT_BG_COLOR = adjustAlpha(themeArray.getColor(0, 0), 0.06f);
219 |
220 | /* Get Default Foreground Color From Theme */
221 | themeArray = theme.obtainStyledAttributes(new int[]{android.R.attr.colorBackground});
222 | DEFAULT_FG_COLOR = themeArray.getColor(0, 0);
223 |
224 | /* Get Default Primary Color From Theme */
225 | themeArray = theme.obtainStyledAttributes(new int[]{R.attr.colorPrimary});
226 | if (isLight(DEFAULT_BG_COLOR))
227 | DEFAULT_PRIMARY_COLOR = lighter(themeArray.getColor(0, 0), 0.2f);
228 | else DEFAULT_PRIMARY_COLOR = themeArray.getColor(0, 0);
229 |
230 | /* Get Default Text Color From Theme */
231 | themeArray = theme.obtainStyledAttributes(new int[]{android.R.attr.textColorTertiary});
232 | DEFAULT_TEXT_COLOR = themeArray.getColor(0, 0);
233 |
234 | /* Get Default Disabled Text Color From Theme */
235 | themeArray = theme.obtainStyledAttributes(new int[]{android.R.attr.disabledAlpha});
236 | float disabledAlpha = themeArray.getFloat(0, 0);
237 | themeArray = theme.obtainStyledAttributes(new int[]{android.R.attr.textColorTertiary});
238 | DEFAULT_DISABLED_TEXT_COLOR = adjustAlpha(themeArray.getColor(0, 0), disabledAlpha);
239 |
240 | themeArray.recycle();
241 | }
242 |
243 | protected ExtendedEditText findEditTextChild() {
244 |
245 | if (getChildCount() > 0 && getChildAt(0) instanceof ExtendedEditText)
246 | return (ExtendedEditText) getChildAt(0);
247 | return null;
248 | }
249 |
250 | @Override
251 | protected void onFinishInflate() {
252 |
253 | super.onFinishInflate();
254 | initViews();
255 | triggerSetters();
256 | }
257 |
258 | @Override
259 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
260 |
261 | int widthMode = MeasureSpec.getMode(widthMeasureSpec);
262 | int heightMode = MeasureSpec.getMode(heightMeasureSpec);
263 |
264 | if (widthMode == MeasureSpec.EXACTLY) {
265 |
266 | /* match_parent or specific value */
267 | this.inputLayout.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
268 | this.upperPanel.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
269 | this.editTextLayout.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
270 |
271 | } else if (widthMode == MeasureSpec.AT_MOST) {
272 |
273 | /* wrap_content */
274 | this.inputLayout.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
275 | this.upperPanel.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
276 | this.editTextLayout.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
277 | }
278 |
279 | if (heightMode == MeasureSpec.EXACTLY) {
280 |
281 | /* match_parent or specific value */
282 | this.panel.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
283 | this.rightShell.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
284 | this.upperPanel.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
285 |
286 | ((RelativeLayout.LayoutParams) this.bottomPart.getLayoutParams())
287 | .addRule(RelativeLayout.BELOW, 0);
288 | ((RelativeLayout.LayoutParams) this.bottomLine.getLayoutParams())
289 | .addRule(RelativeLayout.BELOW, 0);
290 | ((RelativeLayout.LayoutParams) this.bottomPart.getLayoutParams())
291 | .addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
292 | ((RelativeLayout.LayoutParams) this.bottomLine.getLayoutParams())
293 | .addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
294 | ((RelativeLayout.LayoutParams) this.panel.getLayoutParams())
295 | .addRule(RelativeLayout.ABOVE, R.id.text_field_boxes_bottom);
296 |
297 | } else if (heightMode == MeasureSpec.AT_MOST) {
298 |
299 | /* wrap_content */
300 | this.panel.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
301 | this.rightShell.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
302 | this.upperPanel.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
303 |
304 | ((RelativeLayout.LayoutParams) this.bottomPart.getLayoutParams())
305 | .addRule(RelativeLayout.BELOW, R.id.text_field_boxes_panel);
306 | ((RelativeLayout.LayoutParams) this.bottomLine.getLayoutParams())
307 | .addRule(RelativeLayout.BELOW, R.id.text_field_boxes_upper_panel);
308 | ((RelativeLayout.LayoutParams) this.bottomPart.getLayoutParams())
309 | .addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, 0);
310 | ((RelativeLayout.LayoutParams) this.bottomLine.getLayoutParams())
311 | .addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, 0);
312 | ((RelativeLayout.LayoutParams) this.panel.getLayoutParams())
313 | .addRule(RelativeLayout.ABOVE, 0);
314 | }
315 |
316 | updateClearAndEndIconLayout();
317 |
318 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
319 | }
320 |
321 | private void updateClearAndEndIconLayout() {
322 |
323 | if ((endIconImageButton != null && endIconImageButton.getDrawable() != null) || hasClearButton) {
324 |
325 | int clearButtonW = hasClearButton ? clearButton.getMeasuredWidth() : 0;
326 | int endIconW = (endIconImageButton != null && endIconImageButton.getDrawable() != null) ?
327 | endIconImageButton.getMeasuredWidth() : 0;
328 | if (mPasswordToggleDummyDrawable == null)
329 | mPasswordToggleDummyDrawable = new ColorDrawable();
330 |
331 | upperPanel.setPadding(getResources().getDimensionPixelOffset(R.dimen.upper_panel_paddingStart), 0, getResources().getDimensionPixelOffset(R.dimen.upper_panel_paddingEnd_small), 0);
332 |
333 | // We add a fake drawableRight to EditText so it will have padding on the right side and text will not go
334 | // under the icons.
335 | if (!rtl)
336 | mPasswordToggleDummyDrawable.setBounds(0, 0, endIconW + clearButtonW, 0);
337 |
338 | final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(editText);
339 | // Store the user defined end compound drawable so that we can restore it later
340 | if (compounds[2] != mPasswordToggleDummyDrawable) {
341 | mOriginalEditTextEndDrawable = compounds[2];
342 | }
343 | TextViewCompat.setCompoundDrawablesRelative(editText, compounds[0], compounds[1],
344 | mPasswordToggleDummyDrawable, compounds[3]);
345 |
346 | } else {
347 |
348 | upperPanel.setPadding(getResources().getDimensionPixelOffset(R.dimen.upper_panel_paddingStart), 0, getResources().getDimensionPixelOffset(R.dimen.upper_panel_paddingEnd), 0);
349 |
350 | if (mPasswordToggleDummyDrawable != null) {
351 | // Make sure that we remove the dummy end compound drawable if it exists, and then
352 | // clear it
353 | final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(editText);
354 | if (compounds[2] == mPasswordToggleDummyDrawable) {
355 | TextViewCompat.setCompoundDrawablesRelative(editText, compounds[0],
356 | compounds[1], mOriginalEditTextEndDrawable, compounds[3]);
357 | mPasswordToggleDummyDrawable = null;
358 | }
359 | }
360 | }
361 | }
362 |
363 | private void initViews() {
364 |
365 | this.editText = findEditTextChild();
366 | if (editText == null) return;
367 | this.addView(LayoutInflater.from(getContext()).inflate(rtl ?
368 | R.layout.text_field_boxes_layout_rtl :
369 | R.layout.text_field_boxes_layout,
370 | this, false));
371 | removeView(this.editText);
372 |
373 | this.editText.setBackgroundColor(Color.TRANSPARENT);
374 | this.editText.setDropDownBackgroundDrawable(new ColorDrawable(DEFAULT_FG_COLOR));
375 | this.editText.setMinimumWidth(10);
376 | this.inputLayout = this.findViewById(R.id.text_field_boxes_input_layout);
377 | this.floatingLabel = findViewById(R.id.text_field_boxes_label);
378 | this.panel = findViewById(R.id.text_field_boxes_panel);
379 | this.labelSpace = findViewById(R.id.text_field_boxes_label_space);
380 | this.labelSpaceBelow = findViewById(R.id.text_field_boxes_label_space_below);
381 | this.bottomLine = findViewById(R.id.bg_bottom_line);
382 | this.rightShell = findViewById(R.id.text_field_boxes_right_shell);
383 | this.upperPanel = findViewById(R.id.text_field_boxes_upper_panel);
384 | this.bottomPart = findViewById(R.id.text_field_boxes_bottom);
385 | this.clearButton = findViewById(R.id.text_field_boxes_clear_button);
386 | this.endIconImageButton = findViewById(R.id.text_field_boxes_end_icon_button);
387 | this.helperLabel = findViewById(R.id.text_field_boxes_helper);
388 | this.counterLabel = findViewById(R.id.text_field_boxes_counter);
389 | this.iconImageButton = findViewById(R.id.text_field_boxes_imageView);
390 | this.editTextLayout = findViewById(R.id.text_field_boxes_editTextLayout);
391 |
392 | this.inputLayout.addView(this.editText);
393 | this.editTextLayout.setAlpha(0f);
394 | this.floatingLabel.setPivotX(0f);
395 | this.floatingLabel.setPivotY(0f);
396 | this.labelColor = this.floatingLabel.getCurrentTextColor();
397 | this.clearButton.setColorFilter(DEFAULT_TEXT_COLOR);
398 | this.clearButton.setAlpha(0.35f);
399 | this.endIconImageButton.setColorFilter(DEFAULT_TEXT_COLOR);
400 | this.endIconImageButton.setAlpha(0.54f);
401 | this.labelTopMargin = RelativeLayout.LayoutParams.class
402 | .cast(this.floatingLabel.getLayoutParams()).topMargin;
403 |
404 | initOnClick();
405 |
406 | // Have to update useDenseSpacing then the dimensions before the first activation
407 | setUseDenseSpacing(this.useDenseSpacing);
408 | updateDimens(this.useDenseSpacing);
409 | if (!this.editText.getText().toString().isEmpty() || this.hasFocus)
410 | activate(false);
411 | }
412 |
413 | private void initOnClick() {
414 |
415 | final FrameLayout mainBody = this;
416 |
417 | this.panel.setOnClickListener(new OnClickListener() {
418 | @Override
419 | public void onClick(View v) {
420 | if (!isActivated()) activate(true);
421 | setHasFocus(true);
422 | inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
423 | mainBody.performClick();
424 | }
425 | });
426 |
427 | this.iconImageButton.setOnClickListener(new OnClickListener() {
428 | @Override
429 | public void onClick(View v) {
430 | if (!isActivated()) activate(true);
431 | setHasFocus(true);
432 | inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
433 | mainBody.performClick();
434 | }
435 | });
436 |
437 | this.editText.setDefaultOnFocusChangeListener(new OnFocusChangeListener() {
438 | @Override
439 | public void onFocusChange(View view, boolean b) {
440 | if (b) setHasFocus(true);
441 | else setHasFocus(false);
442 | }
443 | });
444 |
445 | this.editText.addTextChangedListener(new TextWatcher() {
446 |
447 | private String lastValue = "";
448 |
449 | @Override
450 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
451 | //do nothing
452 | }
453 |
454 | @Override
455 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
456 | //do nothing
457 | }
458 |
459 | @Override
460 | public void afterTextChanged(Editable editable) {
461 | if (!activated && !editable.toString().isEmpty()) activate(true);
462 | if (activated && editable.toString().isEmpty() && !hasFocus) deactivate();
463 | if (isManualValidateError) {
464 | updateCounterText(false);
465 | } else {
466 | validate(); //this will call updateCounterText(true);
467 | }
468 |
469 | // Only trigger simple watcher when the String actually changed
470 |
471 | if (!lastValue.equals(editable.toString())){
472 | lastValue = editable.toString();
473 | if (textChangeListener != null) {
474 | textChangeListener.onTextChanged(editable.toString(), onError);
475 | }
476 | }
477 |
478 | }
479 | });
480 |
481 | this.clearButton.setOnClickListener(new OnClickListener() {
482 | @Override
483 | public void onClick(View view) {
484 | editText.setText("");
485 | }
486 | });
487 | }
488 |
489 | protected void handleAttributes(Context context, AttributeSet attrs) {
490 |
491 | try {
492 |
493 | TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.TextFieldBoxes);
494 |
495 | /* Texts */
496 | this.labelText = styledAttrs.getString(R.styleable.TextFieldBoxes_labelText)
497 | == null ? "" : styledAttrs.getString(R.styleable.TextFieldBoxes_labelText);
498 | this.helperText = styledAttrs.getString(R.styleable.TextFieldBoxes_helperText)
499 | == null ? "" : styledAttrs.getString(R.styleable.TextFieldBoxes_helperText);
500 |
501 | /* Colors */
502 | this.helperTextColor = styledAttrs
503 | .getInt(R.styleable.TextFieldBoxes_helperTextColor, DEFAULT_TEXT_COLOR);
504 | this.mCounterTextColor = styledAttrs
505 | .getInt(R.styleable.TextFieldBoxes_mCounterTextColor, DEFAULT_TEXT_COLOR);
506 | this.errorColor = styledAttrs
507 | .getInt(R.styleable.TextFieldBoxes_errorColor, DEFAULT_ERROR_COLOR);
508 | this.primaryColor = styledAttrs
509 | .getColor(R.styleable.TextFieldBoxes_primaryColor, DEFAULT_PRIMARY_COLOR);
510 | this.secondaryColor = styledAttrs
511 | .getColor(R.styleable.TextFieldBoxes_secondaryColor, DEFAULT_TEXT_COLOR);
512 | this.panelBackgroundColor = styledAttrs
513 | .getColor(R.styleable.TextFieldBoxes_panelBackgroundColor, DEFAULT_BG_COLOR);
514 |
515 | /* Characters counter */
516 | this.maxCharacters = styledAttrs.getInt(R.styleable.TextFieldBoxes_maxCharacters, 0);
517 | this.minCharacters = styledAttrs.getInt(R.styleable.TextFieldBoxes_minCharacters, 0);
518 |
519 | /* Others */
520 | this.isManualValidateError = styledAttrs.getBoolean(R.styleable.TextFieldBoxes_manualValidateError, false);
521 | this.enabled = styledAttrs.getBoolean(R.styleable.TextFieldBoxes_enabled, true);
522 | this.iconSignifierResourceId = styledAttrs.
523 | getResourceId(R.styleable.TextFieldBoxes_iconSignifier, 0);
524 | this.endIconResourceId = styledAttrs.
525 | getResourceId(R.styleable.TextFieldBoxes_endIcon, 0);
526 | this.isResponsiveIconColor = styledAttrs
527 | .getBoolean(R.styleable.TextFieldBoxes_isResponsiveIconColor, true);
528 | this.hasClearButton = styledAttrs
529 | .getBoolean(R.styleable.TextFieldBoxes_hasClearButton, false);
530 | this.hasFocus = styledAttrs.getBoolean(R.styleable.TextFieldBoxes_hasFocus, false);
531 | this.alwaysShowHint = styledAttrs.getBoolean(R.styleable.TextFieldBoxes_alwaysShowHint, false);
532 | this.useDenseSpacing = styledAttrs.getBoolean(R.styleable.TextFieldBoxes_useDenseSpacing, false);
533 | this.rtl = styledAttrs.getBoolean(R.styleable.TextFieldBoxes_rtl, false);
534 |
535 | styledAttrs.recycle();
536 |
537 | } catch (Exception e) {
538 | e.printStackTrace();
539 | }
540 | }
541 |
542 | public void setSimpleTextChangeWatcher(SimpleTextChangedWatcher textChangeListener) {
543 | this.textChangeListener = textChangeListener;
544 | }
545 |
546 | /**
547 | * lower the labelText labelText Label when there is no text at losing focus
548 | */
549 | protected void deactivate() {
550 |
551 | if (this.editText.getText().toString().isEmpty()) {
552 |
553 | if (this.alwaysShowHint && !this.editText.getHint().toString().isEmpty()) {
554 |
555 | // If alwaysShowHint, and the hint is not empty,
556 | // keep the label on the top and EditText visible.
557 | this.editTextLayout.setAlpha(1f);
558 | this.floatingLabel.setScaleX(0.75f);
559 | this.floatingLabel.setScaleY(0.75f);
560 | this.floatingLabel.setTranslationY(-labelTopMargin +
561 | getContext().getResources().getDimensionPixelOffset(R.dimen.label_active_margin_top));
562 |
563 | } else {
564 |
565 | // If not, animate the label and hide the EditText.
566 | this.editTextLayout.setAlpha(0);
567 | ViewCompat.animate(floatingLabel)
568 | .alpha(1)
569 | .scaleX(1)
570 | .scaleY(1)
571 | .translationY(0)
572 | .setDuration(ANIMATION_DURATION);
573 | }
574 |
575 | if (this.editText.hasFocus()) {
576 | inputMethodManager.hideSoftInputFromWindow(editText.getWindowToken(), 0);
577 | this.editText.clearFocus();
578 | }
579 | }
580 | this.activated = false;
581 | }
582 |
583 | /**
584 | * raise the labelText labelText Label when gaining focus
585 | */
586 | protected void activate(boolean animated) {
587 |
588 | this.editText.setAlpha(1);
589 |
590 | if (this.editText.getText().toString().isEmpty() && !isActivated()) {
591 |
592 | this.editTextLayout.setAlpha(0f);
593 | this.floatingLabel.setScaleX(1f);
594 | this.floatingLabel.setScaleY(1f);
595 | this.floatingLabel.setTranslationY(0);
596 | }
597 |
598 | final boolean keepHint = this.alwaysShowHint && !this.editText.getHint().toString().isEmpty();
599 | if (animated && !keepHint) {
600 |
601 | ViewCompat.animate(this.editTextLayout)
602 | .alpha(1f)
603 | .setDuration(ANIMATION_DURATION);
604 |
605 | ViewCompat.animate(this.floatingLabel)
606 | .scaleX(0.75f)
607 | .scaleY(0.75f)
608 | .translationY(-labelTopMargin +
609 | getContext().getResources().getDimensionPixelOffset(R.dimen.label_active_margin_top))
610 | .setDuration(ANIMATION_DURATION);
611 | } else {
612 |
613 | this.editTextLayout.setAlpha(1f);
614 | this.floatingLabel.setScaleX(0.75f);
615 | this.floatingLabel.setScaleY(0.75f);
616 | this.floatingLabel.setTranslationY(-labelTopMargin +
617 | getContext().getResources().getDimensionPixelOffset(R.dimen.label_active_margin_top));
618 | }
619 |
620 | activated = true;
621 | }
622 |
623 | protected void makeCursorBlink() {
624 |
625 | CharSequence hintCache = this.editText.getHint();
626 | this.editText.setHint(" ");
627 | this.editText.setHint(hintCache);
628 | }
629 |
630 | /**
631 | * set the color of the labelText Label, EditText cursor, icon signifier and the underline
632 | *
633 | * @param colorRes color resource
634 | */
635 | protected void setHighlightColor(int colorRes) {
636 |
637 | this.floatingLabel.setTextColor(colorRes);
638 | setCursorDrawableColor(this.editText, colorRes);
639 |
640 | if (getIsResponsiveIconColor()) {
641 | this.iconImageButton.setColorFilter(colorRes);
642 | if (colorRes == secondaryColor) this.iconImageButton.setAlpha(0.54f);
643 | else this.iconImageButton.setAlpha(1f);
644 | }
645 |
646 | if (colorRes == DEFAULT_DISABLED_TEXT_COLOR) this.iconImageButton.setAlpha(0.35f);
647 |
648 | this.bottomLine.setBackgroundColor(colorRes);
649 | }
650 |
651 | /**
652 | * By default the field is validated each time a key is pressed and at construction,
653 | * this means a field with a minimum length requirement will start in Error state.
654 | * Set this value to true to validate only when {@link #validate()} is called.
655 | *
656 | * @param isManualValidateError the new value
657 | */
658 | protected void setManualValidateError(boolean isManualValidateError) {
659 | this.isManualValidateError = isManualValidateError;
660 | }
661 |
662 | /**
663 | * Update the onError state of this component
664 | *
665 | * @return true if valid (the inverse value of onError)
666 | */
667 | public boolean validate() { //Reverted: "validateError" has the opposite meaning and is incorrect and does not follow conventions
668 | removeError();
669 | updateCounterText(true);
670 | if (onError) {
671 | setError(null, false);
672 | }
673 | return !onError;
674 | }
675 |
676 | /**
677 | * @deprecated Pseudonym for {@link #validate()} to provide legacy support for
678 | * a bad PR.
679 | *
680 | * Note: This does NOT validate that there is an error, it does the opposite
681 | */
682 | @Deprecated
683 | public boolean validateError() {
684 | return validate();
685 | }
686 |
687 | /**
688 | * check if the TextFieldBox should use a dense spacing,
689 | * then change the layout dimens accordingly
690 | */
691 | protected void updateDimens(boolean useDenseSpacing) {
692 |
693 | final Resources res = getContext().getResources();
694 |
695 | /* Floating Label */
696 | RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) this.floatingLabel.getLayoutParams();
697 | lp.topMargin = res.getDimensionPixelOffset(
698 | useDenseSpacing ?
699 | R.dimen.dense_label_idle_margin_top :
700 | R.dimen.label_idle_margin_top
701 | );
702 | this.floatingLabel.setLayoutParams(lp);
703 |
704 | /* EditText Layout */
705 | this.inputLayout.setPadding(
706 | 0, res.getDimensionPixelOffset(
707 | useDenseSpacing ?
708 | R.dimen.dense_editTextLayout_padding_top :
709 | R.dimen.editTextLayout_padding_top
710 | ),
711 | 0, res.getDimensionPixelOffset(R.dimen.editTextLayout_padding_bottom));
712 |
713 | /* End Icon */
714 | this.endIconImageButton.setMinimumHeight(
715 | res.getDimensionPixelOffset(
716 | useDenseSpacing ?
717 | R.dimen.end_icon_min_height :
718 | R.dimen.dense_end_icon_min_height
719 | )
720 | );
721 | this.endIconImageButton.setMinimumWidth(
722 | res.getDimensionPixelOffset(
723 | useDenseSpacing ?
724 | R.dimen.end_icon_min_width :
725 | R.dimen.dense_end_icon_min_width
726 | )
727 | );
728 |
729 | /* Clear Icon */
730 | this.clearButton.setMinimumHeight(
731 | res.getDimensionPixelOffset(
732 | useDenseSpacing ?
733 | R.dimen.clear_button_min_height :
734 | R.dimen.dense_clear_button_min_height
735 | )
736 | );
737 | this.clearButton.setMinimumWidth(
738 | res.getDimensionPixelOffset(
739 | useDenseSpacing ?
740 | R.dimen.clear_button_min_width :
741 | R.dimen.dense_clear_button_min_width
742 | )
743 | );
744 |
745 | /* Bottom View */
746 | lp = (RelativeLayout.LayoutParams) this.bottomPart.getLayoutParams();
747 | lp.topMargin = res.getDimensionPixelOffset(
748 | useDenseSpacing ?
749 | R.dimen.dense_bottom_marginTop :
750 | R.dimen.bottom_marginTop
751 | );
752 | this.bottomPart.setLayoutParams(lp);
753 |
754 | /* EditText */
755 | this.editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, res.getDimension(
756 | useDenseSpacing ?
757 | R.dimen.dense_edittext_text_size :
758 | R.dimen.edittext_text_size
759 | ));
760 |
761 | this.labelTopMargin = RelativeLayout.LayoutParams.class
762 | .cast(this.floatingLabel.getLayoutParams()).topMargin;
763 | this.requestLayout();
764 | }
765 |
766 | /**
767 | * check if the character count meets the upper or lower limits,
768 | *
769 | * if performValidation and exceeds limit, setCounterError()
770 | * otherwise removeCounterError()
771 | *
772 | *
773 | *
774 | * @param performValidation - true if error state should be applied or removed by this calls See {@link
775 | * #setManualValidateError(boolean)}
NOTE: SPACE AND LINE FEED WILL NOT COUNT
776 | */
777 | protected void updateCounterText(boolean performValidation) {
778 |
779 | /* Show clear button if there is anything */
780 | if (hasClearButton) {
781 | if (this.editText.getText().toString().length() == 0) {
782 | showClearButton(false);
783 | } else {
784 | showClearButton(true);
785 | }
786 | }
787 |
788 | /* Don't Count Space & Line Feed */
789 | int length = this.editText.getText().toString().replaceAll(" ", "")
790 | .replaceAll("\n", "").length();
791 | String lengthStr = Integer.toString(length) + " / ";
792 | String counterLabelResourceStr = getResources().getString(R.string.counter_label_text_constructor);
793 | if (this.maxCharacters > 0) {
794 | if (this.minCharacters > 0) {
795 | /* MAX & MIN */
796 | this.counterLabel.setText(String.format(counterLabelResourceStr, lengthStr, Integer.toString(this.minCharacters), "-", Integer.toString(this.maxCharacters)));
797 | if (performValidation) {
798 | if (length < this.minCharacters || length > this.maxCharacters) {
799 | setCounterError();
800 | } else {
801 | removeCounterError();
802 | }
803 | }
804 | } else {
805 | /* MAX ONLY */
806 | this.counterLabel.setText(String.format(counterLabelResourceStr, lengthStr, Integer.toString(this.maxCharacters), "", ""));
807 | if (performValidation) {
808 | if (length > this.maxCharacters) {
809 | setCounterError();
810 | } else {
811 | removeCounterError();
812 | }
813 | }
814 | }
815 | } else {
816 | if (this.minCharacters > 0) {
817 | /* MIN ONLY */
818 | this.counterLabel.setText(String.format(counterLabelResourceStr, lengthStr, Integer.toString(this.minCharacters), "+", ""));
819 | if (performValidation) {
820 | if (length < this.minCharacters) {
821 | setCounterError();
822 | } else {
823 | removeCounterError();
824 | }
825 | }
826 | } else {
827 | this.counterLabel.setText("");
828 | if (performValidation) {
829 | removeCounterError();
830 | }
831 | }
832 | }
833 | }
834 |
835 | /**
836 | * check if the helper label and counter are both empty.
837 | * if true, make the bottom view VISIBLE.
838 | * otherwise, make it GONE.
839 | */
840 | protected void updateBottomViewVisibility() {
841 |
842 | if (this.helperLabel.getText().toString().isEmpty() &&
843 | this.counterLabel.getText().toString().isEmpty())
844 | this.bottomPart.setVisibility(View.GONE);
845 | else this.bottomPart.setVisibility(View.VISIBLE);
846 | }
847 |
848 | /**
849 | * set highlight color and counter Label text color to error color
850 | */
851 | protected void setCounterError() {
852 | this.onError = true;
853 | setHighlightColor(this.errorColor);
854 | this.counterLabel.setTextColor(this.errorColor);
855 | }
856 |
857 | /**
858 | * set highlight color to primary color if having focus,
859 | * otherwise set to secondaryColor
860 | * set counterLabel Label text color to DEFAULT_TEXT_COLOR
861 | */
862 | protected void removeCounterError() {
863 | this.onError = false;
864 | if (this.hasFocus) setHighlightColor(this.primaryColor);
865 | else setHighlightColor(this.secondaryColor);
866 | this.counterLabel.setTextColor(this.mCounterTextColor);
867 | }
868 |
869 | /**
870 | * set highlight color and helperLabel Label text color to errorColor
871 | * set helperLabel Label text to error message
872 | *
873 | * @param errorText optional error message
874 | * @param giveFocus whether the field will gain focus when set error on
875 | */
876 | public void setError(@Nullable String errorText, boolean giveFocus) {
877 | if (this.enabled) {
878 | this.onError = true;
879 | activate(true);
880 | setHighlightColor(this.errorColor);
881 | this.helperLabel.setTextColor(this.errorColor);
882 | if (errorText != null) {
883 | this.helperLabel.setText(errorText);
884 | updateBottomViewVisibility();
885 | }
886 | if (giveFocus) setHasFocus(true);
887 | makeCursorBlink();
888 | }
889 | }
890 |
891 | /**
892 | * set highlight to primaryColor if having focus,
893 | * otherwise set to secondaryColor
894 | * set helperLabel Label text color to DEFAULT_TEXT_COLOR
895 | *
896 | * NOTE: WILL BE CALLED WHEN THE EDITTEXT CHANGES
897 | * UNLESS YOU {@link #setManualValidateError(boolean)} TO TRUE
898 | */
899 | public void removeError() {
900 | this.onError = false;
901 | if (this.hasFocus) setHighlightColor(this.primaryColor);
902 | else setHighlightColor(this.secondaryColor);
903 | this.helperLabel.setTextColor(this.helperTextColor);
904 | this.helperLabel.setText(this.helperText);
905 | updateBottomViewVisibility();
906 | }
907 |
908 | protected void showClearButton(boolean show) {
909 |
910 | if (show) this.clearButton.setVisibility(View.VISIBLE);
911 | else this.clearButton.setVisibility(View.GONE);
912 | }
913 |
914 | private void triggerSetters() {
915 |
916 | /* Texts */
917 | setLabelText(this.labelText);
918 | setHelperText(this.helperText);
919 |
920 | /* Colors */
921 | setHelperTextColor(this.helperTextColor);
922 | setmCounterTextColor(this.mCounterTextColor);
923 | setErrorColor(this.errorColor);
924 | setPrimaryColor(this.primaryColor);
925 | setSecondaryColor(this.secondaryColor);
926 | setPanelBackgroundColor(this.panelBackgroundColor);
927 |
928 | /* Characters counter */
929 | setMaxCharacters(this.maxCharacters);
930 | setMinCharacters(this.minCharacters);
931 |
932 | /* Others */
933 | setEnabled(this.enabled);
934 | setIconSignifier(this.iconSignifierResourceId);
935 | setEndIcon(this.endIconResourceId);
936 | setIsResponsiveIconColor(this.isResponsiveIconColor);
937 | setHasClearButton(this.hasClearButton);
938 | setHasFocus(this.hasFocus);
939 | setAlwaysShowHint(this.alwaysShowHint);
940 | updateCounterText(!isManualValidateError);
941 | updateBottomViewVisibility();
942 | }
943 |
944 | /* Text Setters */
945 | public void setLabelText(String labelText) {
946 |
947 | this.labelText = labelText;
948 | this.floatingLabel.setText(this.labelText);
949 |
950 | if (labelText.isEmpty()) {
951 | this.floatingLabel.setVisibility(View.GONE);
952 | this.labelSpace.setVisibility(View.GONE);
953 | this.labelSpaceBelow.setVisibility(View.VISIBLE);
954 | } else {
955 | this.floatingLabel.setVisibility(View.VISIBLE);
956 | this.labelSpace.setVisibility(View.VISIBLE);
957 | this.labelSpaceBelow.setVisibility(View.GONE);
958 | }
959 | }
960 |
961 | public void setHelperText(String helperText) {
962 |
963 | this.helperText = helperText;
964 | this.helperLabel.setText(this.helperText);
965 | }
966 |
967 | /* Color Setters */
968 | public void setHelperTextColor(int colorRes) {
969 |
970 | this.helperTextColor = colorRes;
971 | this.helperLabel.setTextColor(this.helperTextColor);
972 | }
973 |
974 | public void setmCounterTextColor(int colorRes) {
975 |
976 | this.mCounterTextColor = colorRes;
977 | this.counterLabel.setTextColor(this.mCounterTextColor);
978 | }
979 |
980 | public void setErrorColor(int colorRes) {
981 | this.errorColor = colorRes;
982 | }
983 |
984 | /**
985 | * NOTE: the color will automatically be made lighter by 20% if it's on the DARK theme
986 | */
987 | public void setPrimaryColor(int colorRes) {
988 |
989 | this.primaryColor = colorRes;
990 | if (this.hasFocus) setHighlightColor(this.primaryColor);
991 | }
992 |
993 | public void setSecondaryColor(int colorRes) {
994 |
995 | this.secondaryColor = colorRes;
996 | if (!this.hasFocus) setHighlightColor(this.secondaryColor);
997 | }
998 |
999 | public void setPanelBackgroundColor(int colorRes) {
1000 |
1001 | this.panelBackgroundColor = colorRes;
1002 | this.panel.getBackground()
1003 | .setColorFilter(new PorterDuffColorFilter(colorRes, PorterDuff.Mode.SRC_IN));
1004 | }
1005 |
1006 | /* Characters Counter Setters */
1007 | public void setMaxCharacters(int maxCharacters) {
1008 | this.maxCharacters = maxCharacters;
1009 | updateCounterText(!isManualValidateError);
1010 | }
1011 |
1012 | /**
1013 | * remove the max character count limit by setting it to 0
1014 | */
1015 | public void removeMaxCharacters() {
1016 | this.maxCharacters = 0;
1017 | updateCounterText(!isManualValidateError);
1018 | }
1019 |
1020 | public void setMinCharacters(int minCharacters) {
1021 | this.minCharacters = minCharacters;
1022 | updateCounterText(!isManualValidateError);
1023 | }
1024 |
1025 | /**
1026 | * remove the min character count limit by setting it to 0
1027 | */
1028 | public void removeMinCharacters() {
1029 | this.minCharacters = 0;
1030 | updateCounterText(!isManualValidateError);
1031 | }
1032 |
1033 | /* Other Setters */
1034 | public void setEnabled(boolean enabled) {
1035 |
1036 | this.enabled = enabled;
1037 | if (this.enabled) {
1038 | this.editText.setEnabled(true);
1039 | this.editText.setFocusableInTouchMode(true);
1040 | this.editText.setFocusable(true);
1041 | this.helperLabel.setVisibility(View.VISIBLE);
1042 | this.counterLabel.setVisibility(View.VISIBLE);
1043 | this.bottomLine.setVisibility(View.VISIBLE);
1044 | this.panel.setEnabled(true);
1045 | this.iconImageButton.setEnabled(true);
1046 | this.iconImageButton.setClickable(true);
1047 | setHighlightColor(secondaryColor);
1048 | updateCounterText(!isManualValidateError);
1049 |
1050 | } else {
1051 | removeError();
1052 | setHasFocus(false);
1053 | this.editText.setEnabled(false);
1054 | this.editText.setFocusableInTouchMode(false);
1055 | this.editText.setFocusable(false);
1056 | this.iconImageButton.setClickable(false);
1057 | this.iconImageButton.setEnabled(false);
1058 | this.helperLabel.setVisibility(View.INVISIBLE);
1059 | this.counterLabel.setVisibility(View.INVISIBLE);
1060 | this.bottomLine.setVisibility(View.INVISIBLE);
1061 | this.panel.setEnabled(false);
1062 | setHighlightColor(DEFAULT_DISABLED_TEXT_COLOR);
1063 | }
1064 | }
1065 |
1066 | public void setIconSignifier(int resourceID) {
1067 |
1068 | this.iconSignifierResourceId = resourceID;
1069 | if (this.iconSignifierResourceId != 0) {
1070 | this.iconImageButton.setImageResource(this.iconSignifierResourceId);
1071 | this.iconImageButton.setVisibility(View.VISIBLE);
1072 | } else removeIconSignifier();
1073 | }
1074 |
1075 | public void setIconSignifier(Drawable drawable) {
1076 |
1077 | removeIconSignifier();
1078 | this.iconImageButton.setImageDrawable(drawable);
1079 | this.iconImageButton.setVisibility(View.VISIBLE);
1080 |
1081 | }
1082 |
1083 | /**
1084 | * remove the icon by setting the visibility of the image view to View.GONE
1085 | */
1086 | public void removeIconSignifier() {
1087 |
1088 | this.iconSignifierResourceId = 0;
1089 | this.iconImageButton.setVisibility(View.GONE);
1090 | }
1091 |
1092 | public void setEndIcon(int resourceID) {
1093 |
1094 | this.endIconResourceId = resourceID;
1095 | if (this.endIconResourceId != 0) {
1096 | this.endIconImageButton.setImageResource(this.endIconResourceId);
1097 | this.endIconImageButton.setVisibility(View.VISIBLE);
1098 | } else removeEndIcon();
1099 |
1100 | updateClearAndEndIconLayout();
1101 | }
1102 |
1103 | public void setEndIcon(Drawable drawable) {
1104 |
1105 | removeEndIcon();
1106 | this.endIconImageButton.setImageDrawable(drawable);
1107 | this.endIconImageButton.setVisibility(View.VISIBLE);
1108 |
1109 | updateClearAndEndIconLayout();
1110 | }
1111 |
1112 | /**
1113 | * remove the end icon by setting the visibility of the end image view to View.GONE
1114 | */
1115 | public void removeEndIcon() {
1116 | this.endIconResourceId = 0;
1117 | this.endIconImageButton.setImageDrawable(null);
1118 | this.endIconImageButton.setVisibility(View.GONE);
1119 | updateClearAndEndIconLayout();
1120 | }
1121 |
1122 | /**
1123 | * set whether the icon signifier will change its color when gaining or losing focus
1124 | * as the label and the bottomLine do.
1125 | *
1126 | * @param isResponsiveIconColor if true, the icon's color will always be HighlightColor (the same as the bottomLine)
1127 | * if false, the icon will always be in primaryColor
1128 | */
1129 | public void setIsResponsiveIconColor(boolean isResponsiveIconColor) {
1130 |
1131 | this.isResponsiveIconColor = isResponsiveIconColor;
1132 | if (this.isResponsiveIconColor) {
1133 | if (this.hasFocus) {
1134 | this.iconImageButton.setColorFilter(primaryColor);
1135 | this.iconImageButton.setAlpha(1f);
1136 | } else {
1137 | this.iconImageButton.setColorFilter(secondaryColor);
1138 | this.iconImageButton.setAlpha(0.54f);
1139 | }
1140 | } else {
1141 | this.iconImageButton.setColorFilter(primaryColor);
1142 | this.iconImageButton.setAlpha(1f);
1143 | }
1144 | }
1145 |
1146 | public void setHasClearButton(boolean hasClearButton) {
1147 | this.hasClearButton = hasClearButton;
1148 | showClearButton(hasClearButton);
1149 | updateClearAndEndIconLayout();
1150 | }
1151 |
1152 | /**
1153 | * set if the EditText is having focus
1154 | *
1155 | * @param hasFocus gain focus if true, lose if false
1156 | */
1157 | public void setHasFocus(boolean hasFocus) {
1158 |
1159 | this.hasFocus = hasFocus;
1160 | if (this.hasFocus) {
1161 | activate(true);
1162 | this.editText.requestFocus();
1163 | makeCursorBlink();
1164 |
1165 | /* if there's an error, keep the error color */
1166 | if (!this.onError && this.enabled) setHighlightColor(this.primaryColor);
1167 |
1168 | } else {
1169 | deactivate();
1170 |
1171 | /* if there's an error, keep the error color */
1172 | if (!this.onError && this.enabled) setHighlightColor(this.secondaryColor);
1173 | }
1174 | }
1175 |
1176 | public void setAlwaysShowHint(boolean alwaysShowHint) {
1177 | this.alwaysShowHint = alwaysShowHint;
1178 | }
1179 |
1180 | public void setUseDenseSpacing(boolean useDenseSpacing) {
1181 | this.useDenseSpacing = useDenseSpacing;
1182 | }
1183 |
1184 | /* Text Getters */
1185 | public String getLabelText() {
1186 | return this.labelText;
1187 | }
1188 |
1189 | public String getHelperText() {
1190 | return this.helperText;
1191 | }
1192 |
1193 | public String getCounterText() {
1194 | return this.counterLabel.getText().toString();
1195 | }
1196 |
1197 | /* Color Getters */
1198 | public int getHelperTextColor() {
1199 | return this.helperTextColor;
1200 | }
1201 |
1202 | public int getmCounterTextColor() {
1203 | return this.mCounterTextColor;
1204 | }
1205 |
1206 | public int getErrorColor() {
1207 | return this.errorColor;
1208 | }
1209 |
1210 | public int getPrimaryColor() {
1211 | return this.primaryColor;
1212 | }
1213 |
1214 | public int getSecondaryColor() {
1215 | return this.secondaryColor;
1216 | }
1217 |
1218 | public int getPanelBackgroundColor() {
1219 | return this.panelBackgroundColor;
1220 | }
1221 |
1222 | /* Characters Counter Getters */
1223 | public int getMaxCharacters() {
1224 | return this.maxCharacters;
1225 | }
1226 |
1227 | public int getMinCharacters() {
1228 | return this.minCharacters;
1229 | }
1230 |
1231 | /* View Getters */
1232 | public View getPanel() {
1233 | return this.panel;
1234 | }
1235 |
1236 | public View getBottomLine() {
1237 | return this.bottomLine;
1238 | }
1239 |
1240 | public AppCompatTextView getHelperLabel() {
1241 | return this.helperLabel;
1242 | }
1243 |
1244 | public AppCompatTextView getCounterLabel() {
1245 | return this.counterLabel;
1246 | }
1247 |
1248 | public AppCompatTextView getFloatingLabel() {
1249 | return this.floatingLabel;
1250 | }
1251 |
1252 | public AppCompatImageButton getIconImageButton() {
1253 | return this.iconImageButton;
1254 | }
1255 |
1256 | public AppCompatImageButton getEndIconImageButton() {
1257 | return this.endIconImageButton;
1258 | }
1259 |
1260 | /* Other Getters */
1261 | public boolean isActivated() {
1262 | return this.activated;
1263 | }
1264 |
1265 | public boolean isEnabled() {
1266 | return this.enabled;
1267 | }
1268 |
1269 | public boolean isOnError() {
1270 | return this.onError;
1271 | }
1272 |
1273 | public int getIconSignifierResourceId() {
1274 | return this.iconSignifierResourceId;
1275 | }
1276 |
1277 | public int getEndIconResourceId() {
1278 | return this.endIconResourceId;
1279 | }
1280 |
1281 | public boolean getIsResponsiveIconColor() {
1282 | return this.isResponsiveIconColor;
1283 | }
1284 |
1285 | public boolean getHasClearButton() {
1286 | return this.hasClearButton;
1287 | }
1288 |
1289 | public boolean getHasFocus() {
1290 | return this.hasFocus;
1291 | }
1292 |
1293 | public boolean getAlwaysShowHint() {
1294 | return this.alwaysShowHint;
1295 | }
1296 |
1297 | public boolean getUseDenseSpacing() {
1298 | return this.useDenseSpacing;
1299 | }
1300 |
1301 | /**
1302 | * set EditText cursor color
1303 | */
1304 | protected static void setCursorDrawableColor(EditText _editText, int _colorRes) {
1305 |
1306 | try {
1307 | Field fCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes");
1308 | fCursorDrawableRes.setAccessible(true);
1309 | int mCursorDrawableRes = fCursorDrawableRes.getInt(_editText);
1310 | Field fEditor = TextView.class.getDeclaredField("mEditor");
1311 | fEditor.setAccessible(true);
1312 | Object editor = fEditor.get(_editText);
1313 | Class> clazz = editor.getClass();
1314 | Field fCursorDrawable = clazz.getDeclaredField("mCursorDrawable");
1315 | fCursorDrawable.setAccessible(true);
1316 | Drawable[] drawables = new Drawable[2];
1317 | drawables[0] = ContextCompat.getDrawable(_editText.getContext(), mCursorDrawableRes);
1318 | drawables[1] = ContextCompat.getDrawable(_editText.getContext(), mCursorDrawableRes);
1319 | drawables[0].setColorFilter(_colorRes, PorterDuff.Mode.SRC_IN);
1320 | drawables[1].setColorFilter(_colorRes, PorterDuff.Mode.SRC_IN);
1321 | fCursorDrawable.set(editor, drawables);
1322 | } catch (Throwable ignored) {
1323 | }
1324 | }
1325 |
1326 | /**
1327 | * return a lighter color
1328 | *
1329 | * @param factor percentage of light applied
1330 | */
1331 | protected static int lighter(int color, float factor) {
1332 |
1333 | int red = (int) ((Color.red(color) * (1 - factor) / 255 + factor) * 255);
1334 | int green = (int) ((Color.green(color) * (1 - factor) / 255 + factor) * 255);
1335 | int blue = (int) ((Color.blue(color) * (1 - factor) / 255 + factor) * 255);
1336 | return Color.argb(Color.alpha(color), red, green, blue);
1337 | }
1338 |
1339 | protected static boolean isLight(int color) {
1340 | return Math.sqrt(
1341 | Color.red(color) * Color.red(color) * .241 +
1342 | Color.green(color) * Color.green(color) * .691 +
1343 | Color.blue(color) * Color.blue(color) * .068) > 130;
1344 | }
1345 |
1346 | /**
1347 | * adjust the alpha value of the color
1348 | *
1349 | * @return the color after adjustment
1350 | */
1351 | protected static int adjustAlpha(int color, float _toAlpha) {
1352 |
1353 | int alpha = Math.round(255 * _toAlpha);
1354 | int red = Color.red(color);
1355 | int green = Color.green(color);
1356 | int blue = Color.blue(color);
1357 | return Color.argb(alpha, red, green, blue);
1358 | }
1359 | }
1360 |
--------------------------------------------------------------------------------
/textfieldboxes/src/main/java/studio/carbonylgroup/textfieldboxes/TextInputAutoCompleteTextView.java:
--------------------------------------------------------------------------------
1 | package studio.carbonylgroup.textfieldboxes;
2 |
3 | import android.content.Context;
4 | import android.support.design.widget.TextInputLayout;
5 | import android.support.v7.widget.AppCompatAutoCompleteTextView;
6 | import android.util.AttributeSet;
7 | import android.view.ViewParent;
8 | import android.view.inputmethod.EditorInfo;
9 | import android.view.inputmethod.InputConnection;
10 |
11 | /**
12 | * Text Field Boxes
13 | * Created by CarbonylGroup on 2018/01/08
14 | */
15 | class TextInputAutoCompleteTextView extends AppCompatAutoCompleteTextView {
16 |
17 | public TextInputAutoCompleteTextView(Context context) {
18 | super(context);
19 | }
20 |
21 | public TextInputAutoCompleteTextView(Context context, AttributeSet attrs) {
22 | super(context, attrs);
23 | }
24 |
25 | public TextInputAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {
26 | super(context, attrs, defStyleAttr);
27 | }
28 |
29 | @Override
30 | public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
31 | final InputConnection ic = super.onCreateInputConnection(outAttrs);
32 | if (ic != null && outAttrs.hintText == null) {
33 | // If we don't have a hint and our parent is a TextInputLayout, use it's hint for the
34 | // EditorInfo. This allows us to display a hint in 'extract mode'.
35 | final ViewParent parent = getParent();
36 | if (parent instanceof TextInputLayout) {
37 | outAttrs.hintText = ((TextInputLayout) parent).getHint();
38 | }
39 | }
40 | return ic;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/drawable-hdpi/ic_clear_circle_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HITGIF/TextFieldBoxes/2f220b0f967005d373efe81c1a7df29978e3de7c/textfieldboxes/src/main/res/drawable-hdpi/ic_clear_circle_black_24dp.png
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/drawable-hdpi/ic_mic_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HITGIF/TextFieldBoxes/2f220b0f967005d373efe81c1a7df29978e3de7c/textfieldboxes/src/main/res/drawable-hdpi/ic_mic_black_24dp.png
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/drawable-mdpi/ic_clear_circle_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HITGIF/TextFieldBoxes/2f220b0f967005d373efe81c1a7df29978e3de7c/textfieldboxes/src/main/res/drawable-mdpi/ic_clear_circle_black_24dp.png
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/drawable-mdpi/ic_mic_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HITGIF/TextFieldBoxes/2f220b0f967005d373efe81c1a7df29978e3de7c/textfieldboxes/src/main/res/drawable-mdpi/ic_mic_black_24dp.png
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/drawable-xhdpi/ic_clear_circle_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HITGIF/TextFieldBoxes/2f220b0f967005d373efe81c1a7df29978e3de7c/textfieldboxes/src/main/res/drawable-xhdpi/ic_clear_circle_black_24dp.png
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/drawable-xhdpi/ic_mic_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HITGIF/TextFieldBoxes/2f220b0f967005d373efe81c1a7df29978e3de7c/textfieldboxes/src/main/res/drawable-xhdpi/ic_mic_black_24dp.png
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/drawable-xxhdpi/ic_clear_circle_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HITGIF/TextFieldBoxes/2f220b0f967005d373efe81c1a7df29978e3de7c/textfieldboxes/src/main/res/drawable-xxhdpi/ic_clear_circle_black_24dp.png
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/drawable-xxhdpi/ic_mic_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HITGIF/TextFieldBoxes/2f220b0f967005d373efe81c1a7df29978e3de7c/textfieldboxes/src/main/res/drawable-xxhdpi/ic_mic_black_24dp.png
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/drawable-xxxhdpi/ic_clear_circle_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HITGIF/TextFieldBoxes/2f220b0f967005d373efe81c1a7df29978e3de7c/textfieldboxes/src/main/res/drawable-xxxhdpi/ic_clear_circle_black_24dp.png
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/drawable-xxxhdpi/ic_mic_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HITGIF/TextFieldBoxes/2f220b0f967005d373efe81c1a7df29978e3de7c/textfieldboxes/src/main/res/drawable-xxxhdpi/ic_mic_black_24dp.png
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/drawable/bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/drawable/box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HITGIF/TextFieldBoxes/2f220b0f967005d373efe81c1a7df29978e3de7c/textfieldboxes/src/main/res/drawable/box.png
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/drawable/ic_clear_circle_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HITGIF/TextFieldBoxes/2f220b0f967005d373efe81c1a7df29978e3de7c/textfieldboxes/src/main/res/drawable/ic_clear_circle_black_24dp.png
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/drawable/ic_mic_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HITGIF/TextFieldBoxes/2f220b0f967005d373efe81c1a7df29978e3de7c/textfieldboxes/src/main/res/drawable/ic_mic_black_24dp.png
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/layout/text_field_boxes_layout.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
17 |
18 |
24 |
25 |
36 |
37 |
46 |
47 |
58 |
59 |
68 |
69 |
78 |
79 |
85 |
86 |
94 |
95 |
101 |
102 |
111 |
112 |
120 |
121 |
122 |
123 |
124 |
125 |
136 |
137 |
138 |
139 |
140 |
141 |
155 |
156 |
167 |
168 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/layout/text_field_boxes_layout_rtl.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
19 |
20 |
26 |
27 |
38 |
39 |
48 |
49 |
60 |
61 |
72 |
73 |
82 |
83 |
90 |
91 |
97 |
98 |
106 |
107 |
116 |
117 |
118 |
119 |
123 |
124 |
125 |
126 |
137 |
138 |
139 |
140 |
141 |
142 |
156 |
157 |
169 |
170 |
177 |
178 |
179 |
180 |
181 |
182 |
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #ff1744
5 |
6 |
7 | #72000000
8 | #2a000000
9 | #06000000
10 |
11 |
12 | #46ffffff
13 | #32ffffff
14 | #0affffff
15 |
16 |
17 |
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/values/dimen.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 20dp
6 | 20dp
7 | 8dp
8 | 18sp
9 | 40dp
10 | 40dp
11 | 40dp
12 | 40dp
13 |
14 |
15 | 16dp
16 | 16dp
17 | 4dp
18 | 16sp
19 | 38dp
20 | 38dp
21 | 38dp
22 | 38dp
23 |
24 | 16sp
25 | 12sp
26 | 12sp
27 | 4dp
28 | 8dp
29 | 16dp
30 | 16dp
31 | 8dp
32 | 5dp
33 | 8dp
34 | 8dp
35 | 2dp
36 | 8dp
37 | 24dp
38 | 24dp
39 | 16dp
40 | 20dp
41 |
42 |
--------------------------------------------------------------------------------
/textfieldboxes/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | TextFieldBoxes
3 | icon
4 | %s%s%s%s
5 |
6 |
--------------------------------------------------------------------------------
/textfieldboxes/src/test/java/studio/carbonylgroup/textfieldboxes/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package studio.carbonylgroup.textfieldboxes;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------