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