Home | History | Annotate | Download | only in adapters
      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")
     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")
     79     public static String getTextString(TextView view) {
     80         return view.getText().toString();
     81     }
     82 
     83     @BindingAdapter({"android: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"})
    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"})
    110     public static void setBufferType(TextView view, TextView.BufferType bufferType) {
    111         view.setText(view.getText(), bufferType);
    112     }
    113 
    114     @BindingAdapter({"android: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"})
    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"})
    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 
    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"})
    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"})
    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"})
    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"})
    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"})
    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"})
    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"})
    197     public static void setImeActionLabel(TextView view, CharSequence value) {
    198         view.setImeActionLabel(value, view.getImeActionId());
    199     }
    200 
    201     @BindingAdapter({"android:imeActionId"})
    202     public static void setImeActionLabel(TextView view, int value) {
    203         view.setImeActionLabel(view.getImeActionLabel(), value);
    204     }
    205 
    206     @BindingAdapter({"android: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"})
    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"})
    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"})
    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"})
    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"})
    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"})
    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"})
    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"})
    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"})
    322     public static void setTextSize(TextView view, float size) {
    323         view.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
    324     }
    325 
    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)
    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 {
    389         void afterTextChanged(Editable s);
    390     }
    391 
    392     public interface BeforeTextChanged {
    393         void beforeTextChanged(CharSequence s, int start, int count, int after);
    394     }
    395 
    396     public interface OnTextChanged {
    397         void onTextChanged(CharSequence s, int start, int before, int count);
    398     }
    399 }
    400