/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.databinding.adapters; import com.android.databinding.library.baseAdapters.R; import android.databinding.BindingAdapter; import android.databinding.BindingMethod; import android.databinding.BindingMethods; import android.databinding.InverseBindingAdapter; import android.databinding.InverseBindingListener; import android.graphics.drawable.Drawable; import android.os.Build; import android.text.Editable; import android.text.InputFilter; import android.text.InputType; import android.text.Spanned; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.TextWatcher; import android.text.method.DialerKeyListener; import android.text.method.DigitsKeyListener; import android.text.method.KeyListener; import android.text.method.PasswordTransformationMethod; import android.text.method.TextKeyListener; import android.util.Log; import android.util.TypedValue; import android.widget.TextView; @BindingMethods({ @BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"), @BindingMethod(type = TextView.class, attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"), @BindingMethod(type = TextView.class, attribute = "android:editorExtras", method = "setInputExtras"), @BindingMethod(type = TextView.class, attribute = "android:inputType", method = "setRawInputType"), @BindingMethod(type = TextView.class, attribute = "android:scrollHorizontally", method = "setHorizontallyScrolling"), @BindingMethod(type = TextView.class, attribute = "android:textAllCaps", method = "setAllCaps"), @BindingMethod(type = TextView.class, attribute = "android:textColorHighlight", method = "setHighlightColor"), @BindingMethod(type = TextView.class, attribute = "android:textColorHint", method = "setHintTextColor"), @BindingMethod(type = TextView.class, attribute = "android:textColorLink", method = "setLinkTextColor"), @BindingMethod(type = TextView.class, attribute = "android:onEditorAction", method = "setOnEditorActionListener"), }) public class TextViewBindingAdapter { private static final String TAG = "TextViewBindingAdapters"; public static final int INTEGER = 0x01; public static final int SIGNED = 0x03; public static final int DECIMAL = 0x05; @BindingAdapter("android:text") public static void setText(TextView view, CharSequence text) { final CharSequence oldText = view.getText(); if (text == oldText || (text == null && oldText.length() == 0)) { return; } if (text instanceof Spanned) { if (text.equals(oldText)) { return; // No change in the spans, so don't set anything. } } else if (!haveContentsChanged(text, oldText)) { return; // No content changes, so don't set anything. } view.setText(text); } @InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged") public static String getTextString(TextView view) { return view.getText().toString(); } @BindingAdapter({"android:autoText"}) public static void setAutoText(TextView view, boolean autoText) { KeyListener listener = view.getKeyListener(); TextKeyListener.Capitalize capitalize = TextKeyListener.Capitalize.NONE; int inputType = listener != null ? listener.getInputType() : 0; if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { capitalize = TextKeyListener.Capitalize.CHARACTERS; } else if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { capitalize = TextKeyListener.Capitalize.WORDS; } else if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { capitalize = TextKeyListener.Capitalize.SENTENCES; } view.setKeyListener(TextKeyListener.getInstance(autoText, capitalize)); } @BindingAdapter({"android:capitalize"}) public static void setCapitalize(TextView view, TextKeyListener.Capitalize capitalize) { KeyListener listener = view.getKeyListener(); int inputType = listener.getInputType(); boolean autoText = (inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; view.setKeyListener(TextKeyListener.getInstance(autoText, capitalize)); } @BindingAdapter({"android:bufferType"}) public static void setBufferType(TextView view, TextView.BufferType bufferType) { view.setText(view.getText(), bufferType); } @BindingAdapter({"android:digits"}) public static void setDigits(TextView view, CharSequence digits) { if (digits != null) { view.setKeyListener(DigitsKeyListener.getInstance(digits.toString())); } else if (view.getKeyListener() instanceof DigitsKeyListener) { view.setKeyListener(null); } } @BindingAdapter({"android:numeric"}) public static void setNumeric(TextView view, int numeric) { view.setKeyListener(DigitsKeyListener.getInstance((numeric & SIGNED) != 0, (numeric & DECIMAL) != 0)); } @BindingAdapter({"android:phoneNumber"}) public static void setPhoneNumber(TextView view, boolean phoneNumber) { if (phoneNumber) { view.setKeyListener(DialerKeyListener.getInstance()); } else if (view.getKeyListener() instanceof DialerKeyListener) { view.setKeyListener(null); } } private static void setIntrinsicBounds(Drawable drawable) { if (drawable != null) { drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); } } @BindingAdapter({"android:drawableBottom"}) public static void setDrawableBottom(TextView view, Drawable drawable) { setIntrinsicBounds(drawable); Drawable[] drawables = view.getCompoundDrawables(); view.setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawable); } @BindingAdapter({"android:drawableLeft"}) public static void setDrawableLeft(TextView view, Drawable drawable) { setIntrinsicBounds(drawable); Drawable[] drawables = view.getCompoundDrawables(); view.setCompoundDrawables(drawable, drawables[1], drawables[2], drawables[3]); } @BindingAdapter({"android:drawableRight"}) public static void setDrawableRight(TextView view, Drawable drawable) { setIntrinsicBounds(drawable); Drawable[] drawables = view.getCompoundDrawables(); view.setCompoundDrawables(drawables[0], drawables[1], drawable, drawables[3]); } @BindingAdapter({"android:drawableTop"}) public static void setDrawableTop(TextView view, Drawable drawable) { setIntrinsicBounds(drawable); Drawable[] drawables = view.getCompoundDrawables(); view.setCompoundDrawables(drawables[0], drawable, drawables[2], drawables[3]); } @BindingAdapter({"android:drawableStart"}) public static void setDrawableStart(TextView view, Drawable drawable) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { setDrawableLeft(view, drawable); } else { setIntrinsicBounds(drawable); Drawable[] drawables = view.getCompoundDrawablesRelative(); view.setCompoundDrawablesRelative(drawable, drawables[1], drawables[2], drawables[3]); } } @BindingAdapter({"android:drawableEnd"}) public static void setDrawableEnd(TextView view, Drawable drawable) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { setDrawableRight(view, drawable); } else { setIntrinsicBounds(drawable); Drawable[] drawables = view.getCompoundDrawablesRelative(); view.setCompoundDrawablesRelative(drawables[0], drawables[1], drawable, drawables[3]); } } @BindingAdapter({"android:imeActionLabel"}) public static void setImeActionLabel(TextView view, CharSequence value) { view.setImeActionLabel(value, view.getImeActionId()); } @BindingAdapter({"android:imeActionId"}) public static void setImeActionLabel(TextView view, int value) { view.setImeActionLabel(view.getImeActionLabel(), value); } @BindingAdapter({"android:inputMethod"}) public static void setInputMethod(TextView view, CharSequence inputMethod) { try { Class c = Class.forName(inputMethod.toString()); view.setKeyListener((KeyListener) c.newInstance()); } catch (ClassNotFoundException e) { Log.e(TAG, "Could not create input method: " + inputMethod, e); } catch (InstantiationException e) { Log.e(TAG, "Could not create input method: " + inputMethod, e); } catch (IllegalAccessException e) { Log.e(TAG, "Could not create input method: " + inputMethod, e); } } @BindingAdapter({"android:lineSpacingExtra"}) public static void setLineSpacingExtra(TextView view, float value) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { view.setLineSpacing(value, view.getLineSpacingMultiplier()); } else { view.setLineSpacing(value, 1); } } @BindingAdapter({"android:lineSpacingMultiplier"}) public static void setLineSpacingMultiplier(TextView view, float value) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { view.setLineSpacing(view.getLineSpacingExtra(), value); } else { view.setLineSpacing(0, value); } } @BindingAdapter({"android:maxLength"}) public static void setMaxLength(TextView view, int value) { InputFilter[] filters = view.getFilters(); if (filters == null) { filters = new InputFilter[]{ new InputFilter.LengthFilter(value) }; } else { boolean foundMaxLength = false; for (int i = 0; i < filters.length; i++) { InputFilter filter = filters[i]; if (filter instanceof InputFilter.LengthFilter) { foundMaxLength = true; boolean replace = true; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { replace = ((InputFilter.LengthFilter) filter).getMax() != value; } if (replace) { filters[i] = new InputFilter.LengthFilter(value); } break; } } if (!foundMaxLength) { // can't use Arrays.copyOf -- it shows up in API 9 InputFilter[] oldFilters = filters; filters = new InputFilter[oldFilters.length + 1]; System.arraycopy(oldFilters, 0, filters, 0, oldFilters.length); filters[filters.length - 1] = new InputFilter.LengthFilter(value); } } view.setFilters(filters); } @BindingAdapter({"android:password"}) public static void setPassword(TextView view, boolean password) { if (password) { view.setTransformationMethod(PasswordTransformationMethod.getInstance()); } else if (view.getTransformationMethod() instanceof PasswordTransformationMethod) { view.setTransformationMethod(null); } } @BindingAdapter({"android:shadowColor"}) public static void setShadowColor(TextView view, int color) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { float dx = view.getShadowDx(); float dy = view.getShadowDy(); float r = view.getShadowRadius(); view.setShadowLayer(r, dx, dy, color); } } @BindingAdapter({"android:shadowDx"}) public static void setShadowDx(TextView view, float dx) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { int color = view.getShadowColor(); float dy = view.getShadowDy(); float r = view.getShadowRadius(); view.setShadowLayer(r, dx, dy, color); } } @BindingAdapter({"android:shadowDy"}) public static void setShadowDy(TextView view, float dy) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { int color = view.getShadowColor(); float dx = view.getShadowDx(); float r = view.getShadowRadius(); view.setShadowLayer(r, dx, dy, color); } } @BindingAdapter({"android:shadowRadius"}) public static void setShadowRadius(TextView view, float r) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { int color = view.getShadowColor(); float dx = view.getShadowDx(); float dy = view.getShadowDy(); view.setShadowLayer(r, dx, dy, color); } } @BindingAdapter({"android:textSize"}) public static void setTextSize(TextView view, float size) { view.setTextSize(TypedValue.COMPLEX_UNIT_PX, size); } private static boolean haveContentsChanged(CharSequence str1, CharSequence str2) { if ((str1 == null) != (str2 == null)) { return true; } else if (str1 == null) { return false; } final int length = str1.length(); if (length != str2.length()) { return true; } for (int i = 0; i < length; i++) { if (str1.charAt(i) != str2.charAt(i)) { return true; } } return false; } @BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged", "android:afterTextChanged", "android:textAttrChanged"}, requireAll = false) public static void setTextWatcher(TextView view, final BeforeTextChanged before, final OnTextChanged on, final AfterTextChanged after, final InverseBindingListener textAttrChanged) { final TextWatcher newValue; if (before == null && after == null && on == null && textAttrChanged == null) { newValue = null; } else { newValue = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { if (before != null) { before.beforeTextChanged(s, start, count, after); } } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (on != null) { on.onTextChanged(s, start, before, count); } if (textAttrChanged != null) { textAttrChanged.onChange(); } } @Override public void afterTextChanged(Editable s) { if (after != null) { after.afterTextChanged(s); } } }; } final TextWatcher oldValue = ListenerUtil.trackListener(view, newValue, R.id.textWatcher); if (oldValue != null) { view.removeTextChangedListener(oldValue); } if (newValue != null) { view.addTextChangedListener(newValue); } } public interface AfterTextChanged { void afterTextChanged(Editable s); } public interface BeforeTextChanged { void beforeTextChanged(CharSequence s, int start, int count, int after); } public interface OnTextChanged { void onTextChanged(CharSequence s, int start, int before, int count); } }