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 */ 16package android.databinding.adapters; 17 18import com.android.databinding.library.baseAdapters.R; 19 20import android.databinding.BindingAdapter; 21import android.databinding.BindingMethod; 22import android.databinding.BindingMethods; 23import android.databinding.InverseBindingAdapter; 24import android.databinding.InverseBindingListener; 25import android.graphics.drawable.Drawable; 26import android.os.Build; 27import android.text.Editable; 28import android.text.InputFilter; 29import android.text.InputType; 30import android.text.Spanned; 31import android.text.SpannableString; 32import android.text.SpannableStringBuilder; 33import android.text.TextWatcher; 34import android.text.method.DialerKeyListener; 35import android.text.method.DigitsKeyListener; 36import android.text.method.KeyListener; 37import android.text.method.PasswordTransformationMethod; 38import android.text.method.TextKeyListener; 39import android.util.Log; 40import android.util.TypedValue; 41import 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}) 55public 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