1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.databinding.adapters;
17 
18 import com.android.databinding.library.baseAdapters.R;
19 
20 import android.databinding.BindingAdapter;
21 import android.databinding.BindingMethod;
22 import android.databinding.BindingMethods;
23 import android.databinding.InverseBindingAdapter;
24 import android.databinding.InverseBindingListener;
25 import android.graphics.drawable.Drawable;
26 import android.os.Build;
27 import android.text.Editable;
28 import android.text.InputFilter;
29 import android.text.InputType;
30 import android.text.Spanned;
31 import android.text.SpannableString;
32 import android.text.SpannableStringBuilder;
33 import android.text.TextWatcher;
34 import android.text.method.DialerKeyListener;
35 import android.text.method.DigitsKeyListener;
36 import android.text.method.KeyListener;
37 import android.text.method.PasswordTransformationMethod;
38 import android.text.method.TextKeyListener;
39 import android.util.Log;
40 import android.util.TypedValue;
41 import android.widget.TextView;
42 
43 @BindingMethods({
44         @BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"),
45         @BindingMethod(type = TextView.class, attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"),
46         @BindingMethod(type = TextView.class, attribute = "android:editorExtras", method = "setInputExtras"),
47         @BindingMethod(type = TextView.class, attribute = "android:inputType", method = "setRawInputType"),
48         @BindingMethod(type = TextView.class, attribute = "android:scrollHorizontally", method = "setHorizontallyScrolling"),
49         @BindingMethod(type = TextView.class, attribute = "android:textAllCaps", method = "setAllCaps"),
50         @BindingMethod(type = TextView.class, attribute = "android:textColorHighlight", method = "setHighlightColor"),
51         @BindingMethod(type = TextView.class, attribute = "android:textColorHint", method = "setHintTextColor"),
52         @BindingMethod(type = TextView.class, attribute = "android:textColorLink", method = "setLinkTextColor"),
53         @BindingMethod(type = TextView.class, attribute = "android:onEditorAction", method = "setOnEditorActionListener"),
54 })
55 public class TextViewBindingAdapter {
56 
57     private static final String TAG = "TextViewBindingAdapters";
58     public static final int INTEGER = 0x01;
59     public static final int SIGNED = 0x03;
60     public static final int DECIMAL = 0x05;
61 
62     @BindingAdapter("android:text")
setText(TextView view, CharSequence text)63     public static void setText(TextView view, CharSequence text) {
64         final CharSequence oldText = view.getText();
65         if (text == oldText || (text == null && oldText.length() == 0)) {
66             return;
67         }
68         if (text instanceof Spanned) {
69             if (text.equals(oldText)) {
70                 return; // No change in the spans, so don't set anything.
71             }
72         } else if (!haveContentsChanged(text, oldText)) {
73             return; // No content changes, so don't set anything.
74         }
75         view.setText(text);
76     }
77 
78     @InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
getTextString(TextView view)79     public static String getTextString(TextView view) {
80         return view.getText().toString();
81     }
82 
83     @BindingAdapter({"android:autoText"})
setAutoText(TextView view, boolean autoText)84     public static void setAutoText(TextView view, boolean autoText) {
85         KeyListener listener = view.getKeyListener();
86 
87         TextKeyListener.Capitalize capitalize = TextKeyListener.Capitalize.NONE;
88 
89         int inputType = listener != null ? listener.getInputType() : 0;
90         if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
91             capitalize = TextKeyListener.Capitalize.CHARACTERS;
92         } else if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
93             capitalize = TextKeyListener.Capitalize.WORDS;
94         } else if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
95             capitalize = TextKeyListener.Capitalize.SENTENCES;
96         }
97         view.setKeyListener(TextKeyListener.getInstance(autoText, capitalize));
98     }
99 
100     @BindingAdapter({"android:capitalize"})
setCapitalize(TextView view, TextKeyListener.Capitalize capitalize)101     public static void setCapitalize(TextView view, TextKeyListener.Capitalize capitalize) {
102         KeyListener listener = view.getKeyListener();
103 
104         int inputType = listener.getInputType();
105         boolean autoText = (inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
106         view.setKeyListener(TextKeyListener.getInstance(autoText, capitalize));
107     }
108 
109     @BindingAdapter({"android:bufferType"})
setBufferType(TextView view, TextView.BufferType bufferType)110     public static void setBufferType(TextView view, TextView.BufferType bufferType) {
111         view.setText(view.getText(), bufferType);
112     }
113 
114     @BindingAdapter({"android:digits"})
setDigits(TextView view, CharSequence digits)115     public static void setDigits(TextView view, CharSequence digits) {
116         if (digits != null) {
117             view.setKeyListener(DigitsKeyListener.getInstance(digits.toString()));
118         } else if (view.getKeyListener() instanceof DigitsKeyListener) {
119             view.setKeyListener(null);
120         }
121     }
122 
123     @BindingAdapter({"android:numeric"})
setNumeric(TextView view, int numeric)124     public static void setNumeric(TextView view, int numeric) {
125         view.setKeyListener(DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
126                 (numeric & DECIMAL) != 0));
127     }
128 
129     @BindingAdapter({"android:phoneNumber"})
setPhoneNumber(TextView view, boolean phoneNumber)130     public static void setPhoneNumber(TextView view, boolean phoneNumber) {
131         if (phoneNumber) {
132             view.setKeyListener(DialerKeyListener.getInstance());
133         } else if (view.getKeyListener() instanceof DialerKeyListener) {
134             view.setKeyListener(null);
135         }
136     }
137 
setIntrinsicBounds(Drawable drawable)138     private static void setIntrinsicBounds(Drawable drawable) {
139         if (drawable != null) {
140             drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
141         }
142     }
143 
144     @BindingAdapter({"android:drawableBottom"})
setDrawableBottom(TextView view, Drawable drawable)145     public static void setDrawableBottom(TextView view, Drawable drawable) {
146         setIntrinsicBounds(drawable);
147         Drawable[] drawables = view.getCompoundDrawables();
148         view.setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawable);
149     }
150 
151     @BindingAdapter({"android:drawableLeft"})
setDrawableLeft(TextView view, Drawable drawable)152     public static void setDrawableLeft(TextView view, Drawable drawable) {
153         setIntrinsicBounds(drawable);
154         Drawable[] drawables = view.getCompoundDrawables();
155         view.setCompoundDrawables(drawable, drawables[1], drawables[2], drawables[3]);
156     }
157 
158     @BindingAdapter({"android:drawableRight"})
setDrawableRight(TextView view, Drawable drawable)159     public static void setDrawableRight(TextView view, Drawable drawable) {
160         setIntrinsicBounds(drawable);
161         Drawable[] drawables = view.getCompoundDrawables();
162         view.setCompoundDrawables(drawables[0], drawables[1], drawable,
163                 drawables[3]);
164     }
165 
166     @BindingAdapter({"android:drawableTop"})
setDrawableTop(TextView view, Drawable drawable)167     public static void setDrawableTop(TextView view, Drawable drawable) {
168         setIntrinsicBounds(drawable);
169         Drawable[] drawables = view.getCompoundDrawables();
170         view.setCompoundDrawables(drawables[0], drawable, drawables[2],
171                 drawables[3]);
172     }
173 
174     @BindingAdapter({"android:drawableStart"})
setDrawableStart(TextView view, Drawable drawable)175     public static void setDrawableStart(TextView view, Drawable drawable) {
176         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
177             setDrawableLeft(view, drawable);
178         } else {
179             setIntrinsicBounds(drawable);
180             Drawable[] drawables = view.getCompoundDrawablesRelative();
181             view.setCompoundDrawablesRelative(drawable, drawables[1], drawables[2], drawables[3]);
182         }
183     }
184 
185     @BindingAdapter({"android:drawableEnd"})
setDrawableEnd(TextView view, Drawable drawable)186     public static void setDrawableEnd(TextView view, Drawable drawable) {
187         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
188             setDrawableRight(view, drawable);
189         } else {
190             setIntrinsicBounds(drawable);
191             Drawable[] drawables = view.getCompoundDrawablesRelative();
192             view.setCompoundDrawablesRelative(drawables[0], drawables[1], drawable, drawables[3]);
193         }
194     }
195 
196     @BindingAdapter({"android:imeActionLabel"})
setImeActionLabel(TextView view, CharSequence value)197     public static void setImeActionLabel(TextView view, CharSequence value) {
198         view.setImeActionLabel(value, view.getImeActionId());
199     }
200 
201     @BindingAdapter({"android:imeActionId"})
setImeActionLabel(TextView view, int value)202     public static void setImeActionLabel(TextView view, int value) {
203         view.setImeActionLabel(view.getImeActionLabel(), value);
204     }
205 
206     @BindingAdapter({"android:inputMethod"})
setInputMethod(TextView view, CharSequence inputMethod)207     public static void setInputMethod(TextView view, CharSequence inputMethod) {
208         try {
209             Class<?> c = Class.forName(inputMethod.toString());
210             view.setKeyListener((KeyListener) c.newInstance());
211         } catch (ClassNotFoundException e) {
212             Log.e(TAG, "Could not create input method: " + inputMethod, e);
213         } catch (InstantiationException e) {
214             Log.e(TAG, "Could not create input method: " + inputMethod, e);
215         } catch (IllegalAccessException e) {
216             Log.e(TAG, "Could not create input method: " + inputMethod, e);
217         }
218     }
219 
220     @BindingAdapter({"android:lineSpacingExtra"})
setLineSpacingExtra(TextView view, float value)221     public static void setLineSpacingExtra(TextView view, float value) {
222         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
223             view.setLineSpacing(value, view.getLineSpacingMultiplier());
224         } else {
225             view.setLineSpacing(value, 1);
226         }
227     }
228 
229     @BindingAdapter({"android:lineSpacingMultiplier"})
setLineSpacingMultiplier(TextView view, float value)230     public static void setLineSpacingMultiplier(TextView view, float value) {
231         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
232             view.setLineSpacing(view.getLineSpacingExtra(), value);
233         } else {
234             view.setLineSpacing(0, value);
235         }
236     }
237 
238     @BindingAdapter({"android:maxLength"})
setMaxLength(TextView view, int value)239     public static void setMaxLength(TextView view, int value) {
240         InputFilter[] filters = view.getFilters();
241         if (filters == null) {
242             filters = new InputFilter[]{
243                     new InputFilter.LengthFilter(value)
244             };
245         } else {
246             boolean foundMaxLength = false;
247             for (int i = 0; i < filters.length; i++) {
248                 InputFilter filter = filters[i];
249                 if (filter instanceof InputFilter.LengthFilter) {
250                     foundMaxLength = true;
251                     boolean replace = true;
252                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
253                         replace = ((InputFilter.LengthFilter) filter).getMax() != value;
254                     }
255                     if (replace) {
256                         filters[i] = new InputFilter.LengthFilter(value);
257                     }
258                     break;
259                 }
260             }
261             if (!foundMaxLength) {
262                 // can't use Arrays.copyOf -- it shows up in API 9
263                 InputFilter[] oldFilters = filters;
264                 filters = new InputFilter[oldFilters.length + 1];
265                 System.arraycopy(oldFilters, 0, filters, 0, oldFilters.length);
266                 filters[filters.length - 1] = new InputFilter.LengthFilter(value);
267             }
268         }
269         view.setFilters(filters);
270     }
271 
272     @BindingAdapter({"android:password"})
setPassword(TextView view, boolean password)273     public static void setPassword(TextView view, boolean password) {
274         if (password) {
275             view.setTransformationMethod(PasswordTransformationMethod.getInstance());
276         } else if (view.getTransformationMethod() instanceof PasswordTransformationMethod) {
277             view.setTransformationMethod(null);
278         }
279     }
280 
281     @BindingAdapter({"android:shadowColor"})
setShadowColor(TextView view, int color)282     public static void setShadowColor(TextView view, int color) {
283         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
284             float dx = view.getShadowDx();
285             float dy = view.getShadowDy();
286             float r = view.getShadowRadius();
287             view.setShadowLayer(r, dx, dy, color);
288         }
289     }
290 
291     @BindingAdapter({"android:shadowDx"})
setShadowDx(TextView view, float dx)292     public static void setShadowDx(TextView view, float dx) {
293         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
294             int color = view.getShadowColor();
295             float dy = view.getShadowDy();
296             float r = view.getShadowRadius();
297             view.setShadowLayer(r, dx, dy, color);
298         }
299     }
300 
301     @BindingAdapter({"android:shadowDy"})
setShadowDy(TextView view, float dy)302     public static void setShadowDy(TextView view, float dy) {
303         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
304             int color = view.getShadowColor();
305             float dx = view.getShadowDx();
306             float r = view.getShadowRadius();
307             view.setShadowLayer(r, dx, dy, color);
308         }
309     }
310 
311     @BindingAdapter({"android:shadowRadius"})
setShadowRadius(TextView view, float r)312     public static void setShadowRadius(TextView view, float r) {
313         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
314             int color = view.getShadowColor();
315             float dx = view.getShadowDx();
316             float dy = view.getShadowDy();
317             view.setShadowLayer(r, dx, dy, color);
318         }
319     }
320 
321     @BindingAdapter({"android:textSize"})
setTextSize(TextView view, float size)322     public static void setTextSize(TextView view, float size) {
323         view.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
324     }
325 
haveContentsChanged(CharSequence str1, CharSequence str2)326     private static boolean haveContentsChanged(CharSequence str1, CharSequence str2) {
327         if ((str1 == null) != (str2 == null)) {
328             return true;
329         } else if (str1 == null) {
330             return false;
331         }
332         final int length = str1.length();
333         if (length != str2.length()) {
334             return true;
335         }
336         for (int i = 0; i < length; i++) {
337             if (str1.charAt(i) != str2.charAt(i)) {
338                 return true;
339             }
340         }
341         return false;
342     }
343 
344     @BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged",
345             "android:afterTextChanged", "android:textAttrChanged"}, requireAll = false)
setTextWatcher(TextView view, final BeforeTextChanged before, final OnTextChanged on, final AfterTextChanged after, final InverseBindingListener textAttrChanged)346     public static void setTextWatcher(TextView view, final BeforeTextChanged before,
347             final OnTextChanged on, final AfterTextChanged after,
348             final InverseBindingListener textAttrChanged) {
349         final TextWatcher newValue;
350         if (before == null && after == null && on == null && textAttrChanged == null) {
351             newValue = null;
352         } else {
353             newValue = new TextWatcher() {
354                 @Override
355                 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
356                     if (before != null) {
357                         before.beforeTextChanged(s, start, count, after);
358                     }
359                 }
360 
361                 @Override
362                 public void onTextChanged(CharSequence s, int start, int before, int count) {
363                     if (on != null) {
364                         on.onTextChanged(s, start, before, count);
365                     }
366                     if (textAttrChanged != null) {
367                         textAttrChanged.onChange();
368                     }
369                 }
370 
371                 @Override
372                 public void afterTextChanged(Editable s) {
373                     if (after != null) {
374                         after.afterTextChanged(s);
375                     }
376                 }
377             };
378         }
379         final TextWatcher oldValue = ListenerUtil.trackListener(view, newValue, R.id.textWatcher);
380         if (oldValue != null) {
381             view.removeTextChangedListener(oldValue);
382         }
383         if (newValue != null) {
384             view.addTextChangedListener(newValue);
385         }
386     }
387 
388     public interface AfterTextChanged {
afterTextChanged(Editable s)389         void afterTextChanged(Editable s);
390     }
391 
392     public interface BeforeTextChanged {
beforeTextChanged(CharSequence s, int start, int count, int after)393         void beforeTextChanged(CharSequence s, int start, int count, int after);
394     }
395 
396     public interface OnTextChanged {
onTextChanged(CharSequence s, int start, int before, int count)397         void onTextChanged(CharSequence s, int start, int before, int count);
398     }
399 }
400