Android Textfield For Material Design (Android L).
Create New Project In Android Eclips.
—> AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.textfieldformaterialdesign" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="MNC" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="EditText"> <attr name="et_inputId" format="reference"/> <attr name="et_labelEnable" format="boolean"/> <attr name="et_labelPadding" format="reference|dimension"/> <attr name="et_labelTextSize" format="reference|dimension"/> <attr name="et_labelTextColor" format="reference|color"/> <attr name="et_labelTextAppearance" format="reference"/> <attr name="et_labelEllipsize" format="integer"> <enum name="start" value="0x00000001" /> <enum name="middle" value="0x00000002" /> <enum name="end" value="0x00000003" /> <enum name="marquee" value="0x00000004" /> </attr> <attr name="et_labelInAnim" format="reference"/> <attr name="et_labelOutAnim" format="reference"/> <attr name="et_supportMode" format="integer"> <enum name="none" value="0x00000000" /> <enum name="helper" value="0x00000001" /> <enum name="helperWithError" value="0x00000002" /> <enum name="charCounter" value="0x00000003" /> </attr> <attr name="et_supportMaxChars" format="reference|integer"/> <attr name="et_helper" format="reference|string"/> <attr name="et_error" format="reference|string"/> <attr name="et_supportPadding" format="reference|dimension"/> <attr name="et_supportTextSize" format="reference|dimension"/> <attr name="et_supportTextColor" format="reference|color"/> <attr name="et_supportTextErrorColor" format="reference|color"/> <attr name="et_supportTextAppearance" format="reference"/> <attr name="et_supportSingleLine" format="boolean"/> <attr name="et_supportMaxLines" format="reference|integer"/> <attr name="et_supportLines" format="reference|integer"/> <attr name="et_supportEllipsize" format="integer"> <enum name="start" value="0x00000001" /> <enum name="middle" value="0x00000002" /> <enum name="end" value="0x00000003" /> <enum name="marquee" value="0x00000004" /> </attr> <attr name="et_dividerColor" format="reference|color"/> <attr name="et_dividerErrorColor" format="reference|color"/> <attr name="et_dividerHeight" format="reference|dimension"/> <attr name="et_dividerPadding" format="reference|dimension"/> <attr name="et_dividerAnimDuration" format="reference|integer"/> <attr name="et_dividerCompoundPadding" format="boolean"/> <attr name="et_autoCompleteMode" format="integer"> <enum name="none" value="0"/> <enum name="single" value="1"/> <enum name="multi" value="2"/> </attr> </declare-styleable> <declare-styleable name="ContactEditText"> <attr name="cet_spanHeight" format="reference|dimension"/> <attr name="cet_spanMaxWidth" format="reference|dimension"/> <attr name="cet_spanPaddingLeft" format="reference|dimension"/> <attr name="cet_spanPaddingRight" format="reference|dimension"/> <attr name="cet_spanFontFamily" format="reference|string"/> <attr name="cet_spanTextStyle" format="integer"> <enum name="normal" value="0" /> <enum name="bold" value="1" /> <enum name="italic" value="2" /> <enum name="bold_italic" value="3" /> </attr> <attr name="cet_spanTextSize" format="reference|dimension"/> <attr name="cet_spanTextColor" format="reference|color"/> <attr name="cet_spanBackgroundColor" format="reference|color"/> <attr name="cet_spanSpacing" format="reference|dimension"/> </declare-styleable> <declare-styleable name="RippleDrawable"> <attr name="rd_backgroundColor" format="reference|color"/> <attr name="rd_backgroundAnimDuration" format="reference|integer"/> <attr name="rd_maxRippleRadius" format="reference|dimension|integer"> <enum name="match_view" value="0x00000000" /> </attr> <attr name="rd_rippleColor" format="reference|color"/> <attr name="rd_rippleAnimDuration" format="reference|integer"/> <attr name="rd_inInterpolator" format="reference"/> <attr name="rd_outInterpolator" format="reference"/> <attr name="rd_maskType" format="integer"> <enum name="rectangle" value="0x00000000" /> <enum name="oval" value="0x00000001" /> </attr> <attr name="rd_rippleType" format="integer"> <enum name="touch" value="0x00000000" /> <enum name="wave" value="0x00000001" /> </attr> <attr name="rd_cornerRadius" format="reference|dimension"/> <attr name="rd_topLeftCornerRadius" format="reference|dimension"/> <attr name="rd_topRightCornerRadius" format="reference|dimension"/> <attr name="rd_bottomLeftCornerRadius" format="reference|dimension"/> <attr name="rd_bottomRightCornerRadius" format="reference|dimension"/> <attr name="rd_padding" format="reference|dimension"/> <attr name="rd_leftPadding" format="reference|dimension"/> <attr name="rd_topPadding" format="reference|dimension"/> <attr name="rd_rightPadding" format="reference|dimension"/> <attr name="rd_bottomPadding" format="reference|dimension"/> <attr name="rd_delayClick" format="boolean"/> </declare-styleable> <declare-styleable name="ContactView"> <attr name="cv_buttonSrc" format="reference"/> <attr name="cv_buttonSize" format="reference|dimension"/> <attr name="cv_avatarSrc" format="reference"/> <attr name="cv_avatarSize" format="reference|dimension"/> <attr name="cv_spacing" format="reference|dimension"/> <attr name="cv_name" format="reference|string"/> <attr name="cv_nameTextAppearance" format="reference"/> <attr name="cv_nameTextSize" format="reference|dimension"/> <attr name="cv_nameTextColor" format="reference|color"/> <attr name="cv_address" format="reference|string"/> <attr name="cv_addressTextAppearance" format="reference"/> <attr name="cv_addressTextSize" format="reference|dimension"/> <attr name="cv_addressTextColor" format="reference|color"/> <attr name="android:minHeight" /> </declare-styleable> <declare-styleable name="RippleView"> <attr name="rd_style" /> <attr name="rd_enable" /> </declare-styleable> <attr name="rd_enable" format="boolean"/> <attr name="rd_style" format="reference"/> </resources>
colors.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorControlActivated">#FF03A9F4</color> <color name="et_divider_focused">@color/colorControlActivated</color> <color name="et_divider_not_focused">#FF6D6D6D</color> <color name="et_divider_disable">#FFC1C1C1</color> </resources>
styles.xml
<resources> <!-- Base application theme, dependent on API level. This theme is replaced by AppBaseTheme from res/values-vXX/styles.xml on newer devices. --> <style name="AppBaseTheme" parent="Theme.AppCompat.Light"> <!-- Theme customizations available in newer API levels can go in res/values-vXX/styles.xml, while customizations related to backward-compatibility can go here. --> </style> <!-- Application theme. --> <style name="AppTheme" parent="AppBaseTheme"> <!-- All customizations that are NOT specific to a particular API-level can go here. --> </style> <style name="Material.Widget.EditText.Light" parent="Material.Widget.EditText"> <item name="et_labelTextColor">@color/abc_secondary_text_material_light</item> <item name="et_supportTextColor">@color/abc_secondary_text_material_light</item> </style> <style name="ContactEditText" parent="Material.Widget.EditText.Light"> <item name="cet_spanHeight">32dp</item> <item name="cet_spanMaxWidth">150dp</item> <item name="cet_spanPaddingLeft">8dp</item> <item name="cet_spanPaddingRight">12dp</item> <item name="cet_spanTextSize">14sp</item> <item name="cet_spanTextColor">@color/abc_primary_text_material_light</item> <item name="cet_spanBackgroundColor">#FFE0E0E0</item> <item name="cet_spanSpacing">4dp</item> </style> <style name="Material.Widget.EditText"> <item name="et_labelEnable">true</item> <item name="et_labelPadding">0dp</item> <item name="et_labelTextAppearance">@style/TextAppearance.AppCompat.Caption</item> <item name="et_labelTextColor">@color/abc_secondary_text_material_dark</item> <item name="et_labelEllipsize">end</item> <item name="et_labelInAnim">@anim/abc_fade_in</item> <item name="et_labelOutAnim">@anim/abc_fade_out</item> <item name="et_dividerErrorColor">?attr/colorAccent</item> <item name="et_dividerAnimDuration">@android:integer/config_shortAnimTime</item> <item name="et_dividerHeight">2dp</item> <item name="et_dividerPadding">0dp</item> <item name="et_dividerCompoundPadding">true</item> <item name="et_supportTextAppearance">@style/TextAppearance.AppCompat.Caption</item> <item name="et_supportTextColor">@color/abc_secondary_text_material_dark</item> <item name="et_supportTextErrorColor">?attr/colorAccent</item> <item name="et_supportEllipsize">end</item> <item name="et_autoCompleteMode">none</item> </style> <style name="Material"></style> <style name="Material.Widget"></style> <style name="ReplacementContactView" parent="Material.Drawable.Ripple.Wave.Light"> <item name="rd_cornerRadius">0dp</item> <item name="rd_delayClick">false</item> <item name="android:minHeight">56dp</item> <item name="cv_buttonSize">0dp</item> <item name="cv_avatarSize">40dp</item> <item name="cv_spacing">8dp</item> <item name="cv_nameTextAppearance">@style/TextAppearance.AppCompat.Subhead</item> <item name="cv_nameTextColor">@color/abc_primary_text_material_light</item> <item name="cv_addressTextAppearance">@style/TextAppearance.AppCompat.Body1</item> <item name="cv_addressTextColor">@color/abc_primary_text_material_light</item> </style> <style name="Material.Drawable.Ripple.Wave.Light" parent="Material.Drawable.Ripple.Wave"> <item name="rd_rippleColor">#66999999</item> </style> <style name="Material.Drawable.Ripple.Wave" parent="Material.Drawable.Ripple"> <item name="rd_maxRippleRadius">48dp</item> <item name="rd_rippleColor">#3FCCCCCC</item> <item name="rd_rippleAnimDuration">200</item> <item name="rd_rippleType">wave</item> </style> <style name="Material.Drawable.Ripple"> <item name="rd_enable">true</item> <item name="rd_inInterpolator">@android:anim/decelerate_interpolator</item> <item name="rd_outInterpolator">@android:anim/decelerate_interpolator</item> <item name="rd_maskType">rectangle</item> <item name="rd_cornerRadius">2dp</item> <item name="rd_padding">0dp</item> <item name="rd_delayClick">false</item> </style> <style name="Material.Drawable"></style> <style name="SelectedContactView" parent="Material.Drawable.Ripple.Wave.Light"> <item name="android:background">#FF00B8D4</item> <item name="rd_cornerRadius">0dp</item> <item name="rd_delayClick">false</item> <item name="android:minHeight">72dp</item> <item name="cv_buttonSrc">@drawable/ic_cancel_white_24dp</item> <item name="cv_buttonSize">48dp</item> <item name="cv_nameTextColor">#FFFFFFFF</item> <item name="cv_addressTextColor">#FFFFFFFF</item> <item name="cv_avatarSize">40dp</item> <item name="cv_spacing">8dp</item> <item name="cv_nameTextAppearance">@style/TextAppearance.AppCompat.Subhead</item> <item name="cv_addressTextAppearance">@style/TextAppearance.AppCompat.Body1</item> </style> <style name="ContactView"> <item name="android:minHeight">56dp</item> <item name="cv_buttonSize">0dp</item> <item name="cv_avatarSize">40dp</item> <item name="cv_spacing">8dp</item> <item name="cv_nameTextAppearance">@style/TextAppearance.AppCompat.Subhead</item> <item name="cv_nameTextColor">@color/abc_primary_text_material_light</item> <item name="cv_addressTextAppearance">@style/TextAppearance.AppCompat.Body1</item> <item name="cv_addressTextColor">@color/abc_primary_text_material_light</item> </style> </resources>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" android:scrollbarStyle="outsideOverlay"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="8dp"> <com.rey.material.app.ContactEditText style="@style/ContactEditText" android:id="@+id/textfield_tv" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="#FF000000" android:textAppearance="@style/Base.TextAppearance.AppCompat.Body1" android:inputType="textEmailSubject|textMultiLine" android:hint="Contact" android:imeOptions="actionNext|flagNoEnterAction|flagNoExtractUi" android:dropDownSelector="@drawable/abc_list_selector_background_transition_holo_light" app:et_inputId="@+id/textfield_et_contact_input" app:et_supportMode="none" app:et_autoCompleteMode="multi" app:et_dividerPadding="4dp" android:completionThreshold="2"/> <com.rey.material.widget.EditText style="@style/Material.Widget.EditText.Light" android:id="@+id/textfield_et_label" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="#FF000000" android:textAppearance="@style/Base.TextAppearance.AppCompat.Body1" android:inputType="text" android:hint="Input with label" app:et_inputId="@+id/textfield_et_label_input" app:et_supportMode="none" /> <com.rey.material.widget.EditText style="@style/Material.Widget.EditText.Light" android:id="@+id/textfield_et_char_counter" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:textColor="#FF000000" android:textAppearance="@style/Base.TextAppearance.AppCompat.Body1" android:inputType="textMultiLine" android:hint="Input with char counter" app:et_inputId="@+id/textfield_et_char_counter_input" app:et_labelTextColor="@drawable/color_label" app:et_supportMode="charCounter" app:et_supportMaxChars="20" app:et_supportTextErrorColor="#FFFF0000" app:et_dividerErrorColor="#FFFF0000"/> <com.rey.material.widget.EditText style="@style/Material.Widget.EditText.Light" android:id="@+id/textfield_et_helper" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:textColor="#FF000000" android:textAppearance="@style/Base.TextAppearance.AppCompat.Body1" android:inputType="textPassword" android:hint="Input with helper" app:et_inputId="@+id/textfield_et_helper_input" app:et_labelTextColor="@drawable/color_label" app:et_supportMode="helper" app:et_supportTextErrorColor="#FFFF0000" app:et_helper="Enter password" app:et_dividerErrorColor="#FFFF0000"/> <com.rey.material.widget.EditText style="@style/Material.Widget.EditText.Light" android:id="@+id/textfield_et_helper_error" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:textColor="#FF000000" android:textAppearance="@style/Base.TextAppearance.AppCompat.Body1" android:inputType="textPassword" android:hint="Input with helper + error" app:et_inputId="@+id/textfield_et_label_error_input" app:et_labelTextColor="@drawable/color_label" app:et_supportMode="helperWithError" app:et_supportTextErrorColor="#FFFF0000" app:et_helper="Enter password" app:et_dividerErrorColor="#FFFF0000"/> <com.rey.material.widget.EditText style="@style/Material.Widget.EditText.Light" android:id="@+id/textfield_et_disable" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:textColor="#FF000000" android:textAppearance="@style/Base.TextAppearance.AppCompat.Body1" android:inputType="text" android:hint="Disabled" android:text="Input text" android:enabled="false" app:et_inputId="@+id/textfield_et_disable_input" app:et_labelTextColor="@drawable/color_label" app:et_supportMode="none"/> </LinearLayout> </ScrollView>
row_contact_replace.xml
<?xml version="1.0" encoding="utf-8"?> <com.rey.material.app.ContactView xmlns:android="http://schemas.android.com/apk/res/android" style="@style/ReplacementContactView" android:layout_width="match_parent" android:layout_height="wrap_content"/>
row_contact_selected.xml
<?xml version="1.0" encoding="utf-8"?> <com.rey.material.app.ContactView xmlns:android="http://schemas.android.com/apk/res/android" style="@style/SelectedContactView" android:layout_width="match_parent" android:layout_height="wrap_content"/>
color_label.xml
<?xml version="1.0" encoding="UTF-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_enabled="true" android:state_focused="false" android:color="@color/et_divider_not_focused" /> <item android:state_enabled="true" android:state_focused="true" android:color="@color/et_divider_focused" /> <item android:state_enabled="false" android:color="@color/et_divider_disable" /> </selector>
MainActivity.java
package com.example.textfieldformaterialdesign; import android.app.Activity; import android.os.Bundle; import android.view.KeyEvent; import android.view.View; import com.rey.material.app.ContactEditText; import com.rey.material.widget.EditText; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final EditText et_helper = (EditText)findViewById(R.id.textfield_et_helper); et_helper.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if(keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) et_helper.setError("Password is incorrect."); return false; } }); et_helper.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if(hasFocus) et_helper.setError(null); } }); final EditText et_helper_error = (EditText)findViewById(R.id.textfield_et_helper_error); et_helper_error.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if(keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) et_helper_error.setError("Password is incorrect."); return false; } }); et_helper_error.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if(hasFocus) et_helper_error.setError(null); } }); ContactEditText a = (ContactEditText) findViewById(R.id.textfield_tv); } }
ContactEditText.java
package com.rey.material.app; import java.util.ArrayList; import java.util.HashMap; import android.content.Context; import android.content.res.TypedArray; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.provider.ContactsContract; import android.support.annotation.NonNull; import android.text.Editable; import android.text.Selection; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.BaseAdapter; import android.widget.Filter; import android.widget.Filterable; import android.widget.MultiAutoCompleteTextView; import android.widget.PopupWindow; import android.widget.TextView; import com.example.textfieldformaterialdesign.R; import com.rey.material.text.style.ContactChipSpan; import com.rey.material.util.ThemeUtil; import com.rey.material.util.TypefaceUtil; import com.rey.material.widget.EditText; import com.rey.material.widget.ListPopupWindow; import com.rey.material.widget.ListView; import com.squareup.picasso.Picasso; import com.squareup.picasso.Target; /** * Created by Rey on 3/2/2015. */ public class ContactEditText extends EditText{ private MultiAutoCompleteTextView.Tokenizer mTokenizer; private HashMap<String, Recipient> mRecipientMap; private int mDefaultAvatarId = R.drawable.ic_user; private int mSpanHeight; private int mSpanMaxWidth; private int mSpanPaddingLeft; private int mSpanPaddingRight; private Typeface mSpanTypeface; private int mSpanTextSize; private int mSpanTextColor; private int mSpanBackgroundColor; private int mSpanSpacing; private ContactReplaceAdapter mReplacementAdapter; private ListPopupWindow mReplacementPopup; private RecipientSpan mSelectedSpan; private RecipientSpan mTouchedSpan; public ContactEditText(Context context) { super(context); init(context, null, 0, 0); } public ContactEditText(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public ContactEditText(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public ContactEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, defStyleRes); } private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ContactEditText, defStyleAttr, defStyleRes); mSpanHeight = a.getDimensionPixelSize(R.styleable.ContactEditText_cet_spanHeight, ThemeUtil.dpToPx(context, 32)); mSpanMaxWidth = a.getDimensionPixelSize(R.styleable.ContactEditText_cet_spanMaxWidth, ThemeUtil.dpToPx(context, 150)); mSpanPaddingLeft = a.getDimensionPixelOffset(R.styleable.ContactEditText_cet_spanPaddingLeft, ThemeUtil.dpToPx(context, 8)); mSpanPaddingRight = a.getDimensionPixelOffset(R.styleable.ContactEditText_cet_spanPaddingRight, ThemeUtil.dpToPx(context, 12)); mSpanTypeface = Typeface.DEFAULT; mSpanTextSize = a.getDimensionPixelSize(R.styleable.ContactEditText_cet_spanTextSize, ThemeUtil.spToPx(context, 14)); mSpanTextColor = a.getColor(R.styleable.ContactEditText_cet_spanTextColor, 0xFF000000); mSpanBackgroundColor = a.getColor(R.styleable.ContactEditText_cet_spanBackgroundColor, 0xFFE0E0E0); mSpanSpacing = a.getDimensionPixelOffset(R.styleable.ContactEditText_cet_spanSpacing, ThemeUtil.dpToPx(context, 4)); String familyName = a.getString(R.styleable.ContactEditText_cet_spanFontFamily); int style = a.getInteger(R.styleable.ContactEditText_cet_spanTextStyle, Typeface.NORMAL); mSpanTypeface = TypefaceUtil.load(context, familyName, style); a.recycle(); mRecipientMap = new HashMap<>(); ContactSuggestionAdapter adapter = new ContactSuggestionAdapter(); setAdapter(adapter); setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer()); addTextChangedListener(new ContactTextWatcher()); setLineSpacing(mSpanSpacing, 1); } public Recipient[] getRecipients(){ RecipientSpan[] spans = getText().getSpans(0, getText().length(), RecipientSpan.class); if(spans == null || spans.length == 0) return null; Recipient[] recipients = new Recipient[spans.length]; for(int i = 0; i < spans.length; i++) recipients[i] = spans[i].getRecipient(); return recipients; } public void setRecipients(Recipient[] recipients){ mRecipientMap.clear(); if(recipients == null){ setText(null); return; } SpannableStringBuilder ssb = new SpannableStringBuilder(); String separator = ", "; for(Recipient recipient : recipients){ int start = ssb.length(); ssb.append(recipient.number) .append(separator); int end = ssb.length(); ssb.setSpan(new RecipientSpan(recipient), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); mRecipientMap.put(recipient.number, recipient); } setText(ssb, TextView.BufferType.SPANNABLE); setSelection(ssb.length()); } @Override public boolean dispatchTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: mTouchedSpan = getTouchedSpan(event); if (mTouchedSpan != null) return true; break; case MotionEvent.ACTION_MOVE: if(mTouchedSpan != null){ if(mTouchedSpan != getTouchedSpan(event)) mTouchedSpan = null; return true; } break; case MotionEvent.ACTION_UP: if (mTouchedSpan != null) { onSpanClick(mTouchedSpan); mTouchedSpan = null; return true; } break; case MotionEvent.ACTION_CANCEL: if (mTouchedSpan != null) { mTouchedSpan = null; return true; } break; } return super.dispatchTouchEvent(event); } private RecipientSpan getTouchedSpan(MotionEvent event) { int off = getOffsetForPosition(event.getX(), event.getY()); RecipientSpan[] spans = getText().getSpans(off, off, RecipientSpan.class); if (spans.length > 0) { float x = convertToLocalHorizontalCoordinate(event.getX()); for(int i = 0; i < spans.length; i++) if(spans[i].mX <= x && spans[i].mX + spans[i].mWidth >= x) return spans[i]; } return null; } @Override public void setTokenizer(MultiAutoCompleteTextView.Tokenizer t) { mTokenizer = t; super.setTokenizer(t); } @Override protected void replaceText(CharSequence text) { clearComposingText(); int end = getSelectionEnd(); int start = mTokenizer.findTokenStart(getText(), end); getText().replace(start, end, mTokenizer.terminateToken(text)); end = getSelectionEnd(); Recipient recipient = mRecipientMap.get(text.toString()); getText().setSpan(new RecipientSpan(recipient), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } private void onSpanClick(RecipientSpan span){ if(span != mSelectedSpan) { dismissReplacementPopup(); mSelectedSpan = span; if(mReplacementAdapter == null) mReplacementAdapter = new ContactReplaceAdapter(mSelectedSpan.getRecipient()); else mReplacementAdapter.setRecipient(mSelectedSpan.getRecipient()); mReplacementPopup = new ListPopupWindow(getContext()); mReplacementPopup.setOnDismissListener(new PopupWindow.OnDismissListener() { @Override public void onDismiss() { mReplacementPopup = null; mSelectedSpan = null; } }); mReplacementPopup.setAnchorView(this); mReplacementPopup.setModal(true); mReplacementPopup.setAdapter(mReplacementAdapter); mReplacementPopup.show(); mReplacementPopup.getListView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { ListView lv = mReplacementPopup.getListView(); ViewTreeObserver observer = lv.getViewTreeObserver(); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) observer.removeOnGlobalLayoutListener(this); else observer.removeGlobalOnLayoutListener(this); View v = lv.getChildAt(0); v.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); mReplacementPopup.setContentWidth(v.getMeasuredWidth()); int[] popupLocation = new int[2]; lv.getLocationOnScreen(popupLocation); int[] inputLocation = new int[2]; mInputView.getLocationOnScreen(inputLocation); Drawable background = mReplacementPopup.getPopup().getBackground(); Rect backgroundPadding = new Rect(); int verticalOffset; int horizontalOffset = inputLocation[0] + (int)mSelectedSpan.mX - (popupLocation[0] + backgroundPadding.left); if(background != null) background.getPadding(backgroundPadding); if(inputLocation[1] < popupLocation[1]) //popup show at bottom verticalOffset = inputLocation[1] + mSelectedSpan.mY - (popupLocation[1] + backgroundPadding.top); else verticalOffset = inputLocation[1] + mSelectedSpan.mY + mSpanHeight - (popupLocation[1] + lv.getHeight() - backgroundPadding.bottom); mReplacementPopup.setVerticalOffset(verticalOffset); mReplacementPopup.setHorizontalOffset(horizontalOffset); mReplacementPopup.show(); } }); } } private void removeSpan(RecipientSpan span){ Editable text = getText(); int start = text.getSpanStart(span); int end = text.getSpanEnd(span); text.delete(start, end); text.removeSpan(span); } private void replaceSpan(RecipientSpan span, Recipient newRecipient){ Editable text = getText(); int start = text.getSpanStart(span); int end = text.getSpanEnd(span); String replace = newRecipient.number; text.replace(start, end - 2, newRecipient.number, 0, replace.length()); span.setRecipient(newRecipient); text.setSpan(span, start, start + replace.length() + 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } private void dismissReplacementPopup(){ if(mReplacementPopup != null && mReplacementPopup.isShowing()){ mReplacementPopup.dismiss(); mReplacementPopup = null; } } @Override protected Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); ss.recipients = getRecipients(); return ss; } @Override protected void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); setRecipients(ss.recipients); requestLayout(); } static class SavedState extends BaseSavedState { Recipient[] recipients; /** * Constructor called from {@link ContactEditText#onSaveInstanceState()} */ SavedState(Parcelable superState) { super(superState); } /** * Constructor called from {@link #CREATOR} */ private SavedState(Parcel in) { super(in); int length = in.readInt(); if(length > 0){ recipients = new Recipient[length]; in.readTypedArray(recipients, Recipient.CREATOR); } } @Override public void writeToParcel(@NonNull Parcel out, int flags) { super.writeToParcel(out, flags); int length = recipients == null ? 0 : recipients.length; out.writeInt(length); if(length > 0) out.writeTypedArray(recipients, flags); } @Override public String toString() { return "ContactEditText.SavedState{" + Integer.toHexString(System.identityHashCode(this)) + "}"; } public static final Creator<SavedState> CREATOR = new Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; } class ContactSuggestionAdapter extends BaseAdapter implements Filterable { private final String COLS[] = new String[]{ ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY, Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME_PRIMARY : ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER, }; private ArrayList<Recipient> mItems; public ContactSuggestionAdapter() {} @Override public int getCount() { return mItems == null ? 0 : mItems.size(); } @Override public Object getItem(int position) { return mItems == null ? null : mItems.get(position); } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { ContactView v = (ContactView)convertView; if (v == null) v = new ContactView(getContext(), null, 0, R.style.ContactView); Recipient recipient = (Recipient) getItem(position); v.setNameText(recipient.name); v.setAddressText(recipient.number); if(TextUtils.isEmpty(recipient.lookupKey)) v.setAvatarResource(mDefaultAvatarId); else Picasso.with(getContext()) .load(Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, recipient.lookupKey)) .placeholder(mDefaultAvatarId) .into(v); return v; } @Override public Filter getFilter() { return contactFilter; } Filter contactFilter = new Filter() { @Override public CharSequence convertResultToString(Object resultValue) { Recipient recipient = (Recipient) resultValue; mRecipientMap.put(recipient.number, recipient); return recipient.number; } @Override protected FilterResults performFiltering(CharSequence constraint) { FilterResults results = new FilterResults(); if (constraint != null) { String selection = ContactsContract.CommonDataKinds.Phone.NUMBER + " LIKE ? OR " + COLS[1] + " LIKE ?"; String[] selectionArgs = new String[]{"%" + constraint + "%", "%" + constraint + "%"}; String sortOrder = COLS[1] + " COLLATE LOCALIZED ASC"; Cursor cursor = getContext().getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, COLS, selection, selectionArgs, sortOrder); if (cursor.getCount() > 0) { ArrayList<Recipient> values = new ArrayList<>(); while (cursor.moveToNext()) { Recipient recipient = new Recipient(); recipient.lookupKey = cursor.getString(0); recipient.name = cursor.getString(1); recipient.number = cursor.getString(2); values.add(recipient); } results.values = values; results.count = values.size(); } cursor.close(); } return results; } @Override protected void publishResults(CharSequence constraint, FilterResults results) { mItems = (ArrayList<Recipient>) results.values; notifyDataSetChanged(); } }; } class ContactReplaceAdapter extends BaseAdapter implements OnClickListener { Recipient[] mItems; private final String COLS[] = new String[]{ ContactsContract.CommonDataKinds.Phone.NUMBER, }; public ContactReplaceAdapter(Recipient recipient){ queryNumber(recipient); } public void setRecipient(Recipient recipient){ queryNumber(recipient); } private void queryNumber(Recipient recipient){ String selection = ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY + "=?"; String[] selectionArgs = new String[]{recipient.lookupKey}; Cursor cursor = getContext().getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, COLS, selection, selectionArgs, null); if (cursor.getCount() > 0) { mItems = new Recipient[cursor.getCount()]; mItems[0] = recipient; int index = 1; while (cursor.moveToNext()) { String number = cursor.getString(0); if(!number.equals(recipient.number)){ Recipient newRecipient = new Recipient(); newRecipient.lookupKey = recipient.lookupKey; newRecipient.name = recipient.name; newRecipient.number = number; if(index == mItems.length){ Recipient[] newItems = new Recipient[mItems.length + 1]; System.arraycopy(mItems, 0, newItems, 0, mItems.length); mItems = newItems; } mItems[index] = newRecipient; index++; } } } else mItems = new Recipient[]{recipient}; cursor.close(); notifyDataSetChanged(); } @Override public void onClick(View v) { int position = (Integer)v.getTag(); if(position == 0) removeSpan(mSelectedSpan); else replaceSpan(mSelectedSpan, (Recipient)mReplacementAdapter.getItem(position)); Selection.setSelection(getText(), getText().length()); dismissReplacementPopup(); } @Override public int getCount() { return mItems == null ? 0 : mItems.length; } @Override public Object getItem(int position) { return mItems == null ? null : mItems[position]; } @Override public long getItemId(int position) { return 0; } @Override public int getViewTypeCount() { return 2; } @Override public int getItemViewType(int position) { return position == 0 ? 0 : 1; } @Override public View getView(int position, View convertView, ViewGroup parent) { ContactView v = (ContactView)convertView; if(v == null) { v = (ContactView)LayoutInflater.from(parent.getContext()).inflate(position == 0 ? R.layout.row_contact_selected : R.layout.row_contact_replace, parent, false); v.setOnClickListener(this); } v.setTag(position); Recipient recipient = (Recipient)getItem(position); v.setNameText(position == 0 ? recipient.name : null); v.setAddressText(recipient.number); if(TextUtils.isEmpty(recipient.lookupKey)) v.setAvatarResource(mDefaultAvatarId); else Picasso.with(getContext()) .load(Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, recipient.lookupKey)) .placeholder(mDefaultAvatarId) .into(v); return v; } } class RecipientSpan extends ContactChipSpan implements Target{ private Recipient mRecipient; int mWidth; float mX; int mY; public RecipientSpan(Recipient recipient) { super(TextUtils.isEmpty(recipient.name) ? recipient.number : recipient.name, mSpanHeight, mSpanMaxWidth, mSpanPaddingLeft, mSpanPaddingRight, mSpanTypeface, mSpanTextColor, mSpanTextSize, mSpanBackgroundColor); mRecipient = recipient; if(TextUtils.isEmpty(recipient.lookupKey)) setImageResource(mDefaultAvatarId); else Picasso.with(getContext()) .load(Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, recipient.lookupKey)) .placeholder(mDefaultAvatarId) .into(this); } public void setRecipient(Recipient recipient){ mRecipient = recipient; } public Recipient getRecipient(){ return mRecipient; } public void setImageResource(int id){ if(id == 0) return; Bitmap bm = BitmapFactory.decodeResource(getContext().getResources(), id); setImage(bm); } public void setImageDrawable(Drawable drawable) { if(drawable == null) return; if (drawable instanceof BitmapDrawable) setImage(((BitmapDrawable) drawable).getBitmap()); else{ Bitmap bm = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bm); drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); drawable.draw(canvas); setImage(bm); } } @Override public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { mWidth = super.getSize(paint, text, start, end, fm) + mSpanSpacing; return mWidth; } @Override public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { mX = x; mY = top; super.draw(canvas, text, start, end, x, top, y, bottom, paint); } @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { setImage(bitmap); ContactEditText.this.invalidate(); } public void onBitmapFailed(Drawable errorDrawable) { setImageDrawable(errorDrawable); } public void onPrepareLoad(Drawable placeHolderDrawable) { setImageDrawable(placeHolderDrawable); } @Override public void onBitmapFailed() { // TODO Auto-generated method stub } } class ContactTextWatcher implements TextWatcher{ @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) {} @Override public void afterTextChanged(Editable s) { dismissReplacementPopup(); } } }
ContactView.java
package com.rey.material.app; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Shader; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.annotation.NonNull; import android.support.v4.view.GravityCompat; import android.text.TextUtils; import android.util.AttributeSet; import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import com.example.textfieldformaterialdesign.R; import com.rey.material.drawable.BlankDrawable; import com.rey.material.util.ThemeUtil; import com.rey.material.widget.ImageButton; import com.rey.material.widget.RippleManager; import com.squareup.picasso.Picasso; import com.squareup.picasso.Target; /** * Created by Administrator on 3/2/2015. */ public class ContactView extends FrameLayout implements Target{ private TextView mNameView; private TextView mAddressView; private AvatarDrawable mAvatarDrawable; private ImageButton mButton; private int mAvatarSize; private int mSpacing; private int mMinHeight; private int mButtonSize; private RippleManager mRippleManager = new RippleManager(); public ContactView(Context context) { super(context); init(context, null, 0, 0); } public ContactView(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public ContactView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public ContactView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, defStyleRes); } private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ setWillNotDraw(false); mRippleManager.onCreate(this, context, attrs, defStyleAttr, defStyleRes); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ContactView, defStyleAttr, defStyleRes); mAvatarSize = a.getDimensionPixelSize(R.styleable.ContactView_cv_avatarSize, ThemeUtil.dpToPx(context, 40)); mSpacing = a.getDimensionPixelOffset(R.styleable.ContactView_cv_spacing, ThemeUtil.dpToPx(context, 8)); mMinHeight = a.getDimensionPixelOffset(R.styleable.ContactView_android_minHeight, 0); int avatarSrc = a.getResourceId(R.styleable.ContactView_cv_avatarSrc, 0); mNameView = new TextView(context); mNameView.setGravity(GravityCompat.START); mNameView.setSingleLine(true); mNameView.setEllipsize(TextUtils.TruncateAt.END); int nameTextSize = a.getDimensionPixelSize(R.styleable.ContactView_cv_nameTextSize, 0); ColorStateList nameTextColor = a.getColorStateList(R.styleable.ContactView_cv_nameTextColor); int nameTextAppearance = a.getResourceId(R.styleable.ContactView_cv_nameTextAppearance, 0); if(nameTextAppearance > 0) mNameView.setTextAppearance(context, nameTextAppearance); if(nameTextSize > 0) mNameView.setTextSize(TypedValue.COMPLEX_UNIT_PX, nameTextSize); if(nameTextColor != null) mNameView.setTextColor(nameTextColor); setNameText(a.getString(R.styleable.ContactView_cv_name)); mAddressView = new TextView(context); mAddressView.setGravity(GravityCompat.START); mAddressView.setSingleLine(true); mAddressView.setEllipsize(TextUtils.TruncateAt.END); int addressTextSize = a.getDimensionPixelSize(R.styleable.ContactView_cv_addressTextSize, 0); ColorStateList addressTextColor = a.getColorStateList(R.styleable.ContactView_cv_addressTextColor); int addressTextAppearance = a.getResourceId(R.styleable.ContactView_cv_addressTextAppearance, 0); if(addressTextAppearance > 0) mAddressView.setTextAppearance(context, addressTextAppearance); if(addressTextSize > 0) mAddressView.setTextSize(TypedValue.COMPLEX_UNIT_PX, addressTextSize); if(addressTextColor != null) mAddressView.setTextColor(addressTextColor); setAddressText(a.getString(R.styleable.ContactView_cv_address)); mButtonSize = a.getDimensionPixelOffset(R.styleable.ContactView_cv_buttonSize, 0); if(mButtonSize > 0){ mButton = new ImageButton(context); int resId = a.getResourceId(R.styleable.ContactView_cv_buttonSrc, 0); if(resId != 0) mButton.setImageResource(resId); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) mButton.setBackground(BlankDrawable.getInstance()); else mButton.setBackgroundDrawable(BlankDrawable.getInstance()); mButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE); mButton.setFocusableInTouchMode(false); mButton.setFocusable(false); mButton.setClickable(false); } a.recycle(); addView(mNameView); addView(mAddressView); if(mButton != null) addView(mButton); mAvatarDrawable = new AvatarDrawable(); if(avatarSrc != 0) setAvatarResource(avatarSrc); } @Override public void setOnClickListener(OnClickListener l) { if(l == mRippleManager) super.setOnClickListener(l); else{ mRippleManager.setOnClickListener(l); setOnClickListener(mRippleManager); } } @Override public boolean onTouchEvent(@NonNull MotionEvent event) { boolean result = super.onTouchEvent(event); return mRippleManager.onTouchEvent(event) || result; } public void setAvatarBitmap(Bitmap bm){ mAvatarDrawable.setImage(bm); invalidate(); } public void setAvatarResource(int id){ if(id == 0) return; Bitmap bm = BitmapFactory.decodeResource(getContext().getResources(), id); setAvatarBitmap(bm); } public void setAvatarDrawable(Drawable drawable) { if(drawable == null) return; if (drawable instanceof BitmapDrawable) setAvatarBitmap(((BitmapDrawable)drawable).getBitmap()); else{ Bitmap bm = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bm); drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); drawable.draw(canvas); setAvatarBitmap(bm); } } public void setNameText(CharSequence name){ mNameView.setText(name); mNameView.setVisibility(TextUtils.isEmpty(name) ? View.GONE : View.VISIBLE); } public void setAddressText(CharSequence address){ mAddressView.setText(address); mAddressView.setVisibility(TextUtils.isEmpty(address) ? View.GONE : View.VISIBLE); } @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { setAvatarBitmap(bitmap); } public void onBitmapFailed(Drawable errorDrawable) { setAvatarDrawable(errorDrawable); } public void onPrepareLoad(Drawable placeHolderDrawable) { setAvatarDrawable(placeHolderDrawable); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int nonTextWidth = mAvatarSize + mSpacing * 3 + mButtonSize; int ws = MeasureSpec.makeMeasureSpec(widthSize - nonTextWidth, widthMode == MeasureSpec.UNSPECIFIED ? widthMode : MeasureSpec.AT_MOST); int hs = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); mNameView.measure(ws, hs); mAddressView.measure(ws, hs); if(mButton != null) mButton.measure(MeasureSpec.makeMeasureSpec(mButtonSize, MeasureSpec.EXACTLY), hs); int width = widthMode == MeasureSpec.EXACTLY ? widthSize : Math.max(mNameView.getMeasuredWidth(), mAddressView.getMeasuredWidth()) + nonTextWidth; int height = Math.max(mAvatarSize + mSpacing * 2, mNameView.getMeasuredHeight() + mAddressView.getMeasuredHeight()); switch (heightMode){ case MeasureSpec.EXACTLY: height = heightSize; break; case MeasureSpec.AT_MOST: height = Math.min(height, heightSize); break; } height = Math.max(mMinHeight, height); if(mButton != null) mButton.measure(MeasureSpec.makeMeasureSpec(mButtonSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); setMeasuredDimension(width, height); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { int childRight = right - left; int childBottom = bottom - top; int childLeft = 0; int childTop = 0; int y = (childBottom - mAvatarSize) / 2; childLeft += mSpacing; mAvatarDrawable.setBounds(childLeft, y, childLeft + mAvatarSize, y + mAvatarSize); childLeft += mAvatarSize + mSpacing; if(mButton != null){ mButton.layout(childRight - mButtonSize, childTop, childRight, childBottom); childRight -= mButtonSize; } if(mNameView.getVisibility() == View.VISIBLE){ if(mAddressView.getVisibility() == View.VISIBLE){ childTop = (childBottom - mNameView.getMeasuredHeight() - mAddressView.getMeasuredHeight()) / 2; mNameView.layout(childLeft, childTop, childRight - mSpacing, childTop + mNameView.getMeasuredHeight()); childTop += mNameView.getMeasuredHeight(); mAddressView.layout(childLeft, childTop, childRight - mSpacing, childTop + mAddressView.getMeasuredHeight()); } else{ childTop = (childBottom - mNameView.getMeasuredHeight()) / 2; mNameView.layout(childLeft, childTop, childRight - mSpacing, childTop + mNameView.getMeasuredHeight()); } } else if(mAddressView.getVisibility() == View.VISIBLE){ childTop = (childBottom - mAddressView.getMeasuredHeight()) / 2; mAddressView.layout(childLeft, childTop, childRight - mSpacing, childTop + mAddressView.getMeasuredHeight()); } } @Override public void draw(Canvas canvas) { super.draw(canvas); mAvatarDrawable.draw(canvas); } private class AvatarDrawable extends Drawable{ private Paint mPaint; private BitmapShader mBitmapShader; private Bitmap mBitmap; private Matrix mMatrix; public AvatarDrawable(){ mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setStyle(Paint.Style.FILL); mMatrix = new Matrix(); } public void setImage(Bitmap bm){ if(mBitmap != bm){ mBitmap = bm; if(mBitmap != null) { mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); updateMatrix(); } invalidateSelf(); } } private void updateMatrix(){ if(mBitmap == null) return; Rect bounds = getBounds(); if(bounds.width() == 0 || bounds.height() == 0) return; mMatrix.reset(); float scale = bounds.height() / (float)Math.min(mBitmap.getWidth(), mBitmap.getHeight()); mMatrix.setScale(scale, scale, 0, 0); mMatrix.postTranslate(bounds.exactCenterX() - mBitmap.getWidth() * scale / 2, bounds.exactCenterY() - mBitmap.getHeight() * scale / 2); mBitmapShader.setLocalMatrix(mMatrix); } @Override protected void onBoundsChange(Rect bounds) { updateMatrix(); } @Override public void draw(Canvas canvas) { if(mBitmap != null){ Rect bounds = getBounds(); float x = bounds.exactCenterX(); float y = bounds.exactCenterY(); float radius = bounds.height() / 2f; mPaint.setShader(mBitmapShader); canvas.drawCircle(x, y, radius, mPaint); } } @Override public void setAlpha(int alpha) { mPaint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter cf) { mPaint.setColorFilter(cf); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } } @Override public void onBitmapFailed() { // TODO Auto-generated method stub } }
Recipient.java
package com.rey.material.app; import android.os.Parcel; import android.os.Parcelable; /** * Created by Rey on 3/2/2015. */ public class Recipient implements Parcelable{ String name; String number; String lookupKey; public Recipient() { } private Recipient(Parcel in) { this.name = in.readString(); this.number = in.readString(); this.lookupKey = in.readString(); } @Override public String toString(){ return Recipient.class.getSimpleName() + "[name = " + name + ", number = " + number + ", key = " + lookupKey + "]"; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.name); dest.writeString(this.number); dest.writeString(this.lookupKey); } public static final Creator<Recipient> CREATOR = new Creator<Recipient>() { public Recipient createFromParcel(Parcel source) { return new Recipient(source); } public Recipient[] newArray(int size) { return new Recipient[size]; } }; }
BlankDrawable.java
package com.rey.material.drawable; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; /** * A drawable that draw nothing. * @author Rey * */ public class BlankDrawable extends Drawable { private static BlankDrawable mInstance; public static BlankDrawable getInstance(){ if(mInstance == null) synchronized (BlankDrawable.class) { if(mInstance == null) mInstance = new BlankDrawable(); } return mInstance; } @Override public void draw(Canvas canvas) {} @Override public void setAlpha(int alpha) {} @Override public void setColorFilter(ColorFilter cf) {} @Override public int getOpacity() { return PixelFormat.TRANSPARENT; } }
DividerDrawable.java
package com.rey.material.drawable; import android.content.res.ColorStateList; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.DashPathEffect; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PathEffect; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.os.SystemClock; import com.rey.material.util.ViewUtil; public class DividerDrawable extends Drawable implements Animatable{ private boolean mRunning = false; private long mStartTime; private float mAnimProgress; private int mAnimDuration; private Paint mPaint; private ColorStateList mColorStateList; private int mHeight; private int mPrevColor; private int mCurColor; private boolean mEnable = true; private PathEffect mPathEffect; private Path mPath; private boolean mInEditMode = false; private boolean mAnimEnable = true; private int mPaddingLeft; private int mPaddingRight; public DividerDrawable(int height, ColorStateList colorStateList, int animDuration){ this(height, 0, 0, colorStateList, animDuration); } public DividerDrawable(int height, int paddingLeft, int paddingRight, ColorStateList colorStateList, int animDuration){ mHeight = height; mPaddingLeft = paddingLeft; mPaddingRight = paddingRight; mAnimDuration = animDuration; mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(mHeight); mPaint.setStrokeCap(Paint.Cap.ROUND); mPaint.setStrokeJoin(Paint.Join.ROUND); mPath = new Path(); mAnimEnable = false; setColor(colorStateList); mAnimEnable = true; } public void setPadding(int left, int right){ if(mPaddingLeft != left || mPaddingRight != right){ mPaddingLeft = left; mPaddingRight = right; invalidateSelf(); } } public int getPaddingLeft(){ return mPaddingLeft; } public int getPaddingRight(){ return mPaddingRight; } public void setInEditMode(boolean b){ mInEditMode = b; } public void setAnimEnable(boolean b){ mAnimEnable = b; } public void setColor(ColorStateList colorStateList){ mColorStateList = colorStateList; onStateChange(getState()); } private PathEffect getPathEffect(){ if(mPathEffect == null) mPathEffect = new DashPathEffect(new float[]{0.2f, mHeight * 2}, 0f); return mPathEffect; } @Override public void draw(Canvas canvas) { if(mHeight == 0) return; Rect bounds = getBounds(); float y = bounds.bottom - mHeight / 2; if(!isRunning()){ mPath.reset(); mPath.moveTo(bounds.left + mPaddingLeft, y); mPath.lineTo(bounds.right - mPaddingRight, y); mPaint.setPathEffect(mEnable ? null : getPathEffect()); mPaint.setColor(mCurColor); canvas.drawPath(mPath, mPaint); } else{ float centerX = (bounds.right + bounds.left - mPaddingRight + mPaddingLeft) / 2f; float start = centerX * (1f - mAnimProgress) + (bounds.left + mPaddingLeft) * mAnimProgress; float end = centerX * (1f - mAnimProgress) + (bounds.right + mPaddingRight) * mAnimProgress; mPaint.setPathEffect(null); if(mAnimProgress < 1f){ mPaint.setColor(mPrevColor); mPath.reset(); mPath.moveTo(bounds.left + mPaddingLeft, y); mPath.lineTo(start, y); mPath.moveTo(bounds.right - mPaddingRight, y); mPath.lineTo(end, y); canvas.drawPath(mPath, mPaint); } mPaint.setColor(mCurColor); mPath.reset(); mPath.moveTo(start, y); mPath.lineTo(end, y); canvas.drawPath(mPath, mPaint); } } @Override public void setAlpha(int alpha) { mPaint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter cf) { mPaint.setColorFilter(cf); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } @Override public boolean isStateful() { return true; } @Override protected boolean onStateChange(int[] state) { mEnable = ViewUtil.hasState(state, android.R.attr.state_enabled); int color = mColorStateList.getColorForState(state, mCurColor); if(mCurColor != color){ if(!mInEditMode && mAnimEnable && mEnable){ mPrevColor = isRunning() ? mPrevColor : mCurColor; mCurColor = color; start(); } else{ mPrevColor = color; mCurColor = color; } return true; } else if(!isRunning()) mPrevColor = color; return false; } private void resetAnimation(){ mStartTime = SystemClock.uptimeMillis(); mAnimProgress = 0f; } @Override public void start() { resetAnimation(); scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); invalidateSelf(); } @Override public void stop() { mRunning = false; unscheduleSelf(mUpdater); invalidateSelf(); } @Override public boolean isRunning() { return mRunning; } @Override public void scheduleSelf(Runnable what, long when) { mRunning = true; super.scheduleSelf(what, when); } private final Runnable mUpdater = new Runnable() { @Override public void run() { update(); } }; private void update(){ long curTime = SystemClock.uptimeMillis(); mAnimProgress = Math.min(1f, (float)(curTime - mStartTime) / mAnimDuration); if(mAnimProgress == 1f) mRunning = false; if(isRunning()) scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); invalidateSelf(); } }
RippleDrawable.java
package com.rey.material.drawable; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Path.Direction; import android.graphics.PixelFormat; import android.graphics.PointF; import android.graphics.RadialGradient; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.os.SystemClock; import android.util.AttributeSet; import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.animation.AccelerateInterpolator; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import com.example.textfieldformaterialdesign.R; import com.rey.material.util.ColorUtil; import com.rey.material.util.ThemeUtil; import com.rey.material.util.ViewUtil; public class RippleDrawable extends Drawable implements Animatable, OnTouchListener { private boolean mRunning = false; private Paint mShaderPaint; private Paint mFillPaint; private Mask mMask; private RadialGradient mInShader; private RadialGradient mOutShader; private Matrix mMatrix; private int mAlpha = 255; private Drawable mBackgroundDrawable; private RectF mBackgroundBounds; private Path mBackground; private int mBackgroundAnimDuration; private int mBackgroundColor; private float mBackgroundAlphaPercent; private PointF mRipplePoint; private float mRippleRadius; private int mRippleType; private int mMaxRippleRadius; private int mRippleAnimDuration; private int mRippleColor; private float mRippleAlphaPercent; private boolean mDelayClick; private Interpolator mInInterpolator; private Interpolator mOutInterpolator; private long mStartTime; private int mState = STATE_OUT; private static final int STATE_OUT = 0; private static final int STATE_PRESS = 1; private static final int STATE_HOVER = 2; private static final int STATE_RELEASE_ON_HOLD = 3; private static final int STATE_RELEASE = 4; private static final int TYPE_TOUCH_MATCH_VIEW = -1; private static final int TYPE_TOUCH = 0; private static final int TYPE_WAVE = 1; private static final float[] GRADIENT_STOPS = new float[]{0f, 0.99f, 1f}; private static final float GRADIENT_RADIUS = 16; private RippleDrawable(Drawable backgroundDrawable, int backgroundAnimDuration, int backgroundColor, int rippleType, boolean delayClick, int maxRippleRadius, int rippleAnimDuration, int rippleColor, Interpolator inInterpolator, Interpolator outInterpolator, int type, int topLeftCornerRadius, int topRightCornerRadius, int bottomRightCornerRadius, int bottomLeftCornerRadius, int left, int top, int right, int bottom){ setBackgroundDrawable(backgroundDrawable); mBackgroundAnimDuration = backgroundAnimDuration; mBackgroundColor = backgroundColor; mRippleType = rippleType; setDelayClick(delayClick); mMaxRippleRadius = maxRippleRadius; mRippleAnimDuration = rippleAnimDuration; mRippleColor = rippleColor; if(mRippleType == TYPE_TOUCH && mMaxRippleRadius <= 0) mRippleType = TYPE_TOUCH_MATCH_VIEW; mInInterpolator = inInterpolator; mOutInterpolator = outInterpolator; setMask(type, topLeftCornerRadius, topRightCornerRadius, bottomRightCornerRadius, bottomLeftCornerRadius, left, top, right, bottom); mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mFillPaint.setStyle(Paint.Style.FILL); mShaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mShaderPaint.setStyle(Paint.Style.FILL); mBackground = new Path(); mBackgroundBounds = new RectF(); mRipplePoint = new PointF(); mMatrix = new Matrix(); mInShader = new RadialGradient(0, 0, GRADIENT_RADIUS, new int[]{mRippleColor, mRippleColor, 0}, GRADIENT_STOPS, Shader.TileMode.CLAMP); if(mRippleType == TYPE_WAVE) mOutShader = new RadialGradient(0, 0, GRADIENT_RADIUS, new int[]{0, ColorUtil.getColor(mRippleColor, 0f), mRippleColor}, GRADIENT_STOPS, Shader.TileMode.CLAMP); } public void setBackgroundDrawable(Drawable backgroundDrawable){ mBackgroundDrawable = backgroundDrawable; if(mBackgroundDrawable != null) mBackgroundDrawable.setBounds(getBounds()); } public boolean isDelayClick(){ return mDelayClick; } public void setDelayClick(boolean enable){ mDelayClick = enable; } public void setMask(int type, int topLeftCornerRadius, int topRightCornerRadius, int bottomRightCornerRadius, int bottomLeftCornerRadius, int left, int top, int right, int bottom){ mMask = new Mask(type, topLeftCornerRadius, topRightCornerRadius, bottomRightCornerRadius, bottomLeftCornerRadius, left, top, right, bottom); } @Override public void setAlpha(int alpha) { mAlpha = alpha; } @Override public void setColorFilter(ColorFilter filter) { mFillPaint.setColorFilter(filter); mShaderPaint.setColorFilter(filter); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } public long getClickDelayTime(){ if(mState == STATE_RELEASE_ON_HOLD) return (mDelayClick ? 2 : 1) * Math.max(mBackgroundAnimDuration, mRippleAnimDuration) - (SystemClock.uptimeMillis() - mStartTime); else if(mState == STATE_RELEASE) return (mDelayClick ? Math.max(mBackgroundAnimDuration, mRippleAnimDuration) : 0) - (SystemClock.uptimeMillis() - mStartTime); return -1; } private void setRippleState(int state){ if(mState != state){ mState = state; if(mState != STATE_OUT){ if(mState != STATE_HOVER) start(); else stop(); } else stop(); } } private boolean setRippleEffect(float x, float y, float radius){ if(mRipplePoint.x != x || mRipplePoint.y != y || mRippleRadius != radius){ mRipplePoint.set(x, y); mRippleRadius = radius; radius = mRippleRadius / GRADIENT_RADIUS; mMatrix.reset(); mMatrix.postTranslate(x, y); mMatrix.postScale(radius, radius, x, y); mInShader.setLocalMatrix(mMatrix); if(mOutShader != null) mOutShader.setLocalMatrix(mMatrix); return true; } return false; } @Override protected void onBoundsChange(Rect bounds) { if(mBackgroundDrawable != null) mBackgroundDrawable.setBounds(bounds); mBackgroundBounds.set(bounds.left + mMask.left, bounds.top + mMask.top, bounds.right - mMask.right, bounds.bottom - mMask.bottom); mBackground.reset(); switch (mMask.type) { case Mask.TYPE_OVAL: mBackground.addOval(mBackgroundBounds, Direction.CW); break; case Mask.TYPE_RECTANGLE: mBackground.addRoundRect(mBackgroundBounds, mMask.cornerRadius, Direction.CW); break; } } @Override public boolean isStateful() { return mBackgroundDrawable != null && mBackgroundDrawable.isStateful(); } @Override protected boolean onStateChange(int[] state) { return mBackgroundDrawable != null && mBackgroundDrawable.setState(state); } @Override public void draw(Canvas canvas) { if(mBackgroundDrawable != null) mBackgroundDrawable.draw(canvas); switch (mRippleType) { case TYPE_TOUCH: case TYPE_TOUCH_MATCH_VIEW: drawTouch(canvas); break; case TYPE_WAVE: drawWave(canvas); break; } } private void drawTouch(Canvas canvas){ if(mState != STATE_OUT){ if(mBackgroundAlphaPercent > 0){ mFillPaint.setColor(mBackgroundColor); mFillPaint.setAlpha(Math.round(mAlpha * mBackgroundAlphaPercent)); canvas.drawPath(mBackground, mFillPaint); } if(mRippleRadius > 0 && mRippleAlphaPercent > 0){ mShaderPaint.setAlpha(Math.round(mAlpha * mRippleAlphaPercent)); mShaderPaint.setShader(mInShader); canvas.drawPath(mBackground, mShaderPaint); } } } private void drawWave(Canvas canvas){ if(mState != STATE_OUT){ if(mState == STATE_RELEASE){ if(mRippleRadius == 0){ mFillPaint.setColor(mRippleColor); canvas.drawPath(mBackground, mFillPaint); } else{ mShaderPaint.setShader(mOutShader); canvas.drawPath(mBackground, mShaderPaint); } } else if(mRippleRadius > 0){ mShaderPaint.setShader(mInShader); canvas.drawPath(mBackground, mShaderPaint); } } } private int getMaxRippleRadius(float x, float y){ float x1 = x < mBackgroundBounds.centerX() ? mBackgroundBounds.right : mBackgroundBounds.left; float y1 = y < mBackgroundBounds.centerY() ? mBackgroundBounds.bottom : mBackgroundBounds.top; return (int)Math.round(Math.sqrt(Math.pow(x1 - x, 2) + Math.pow(y1 - y, 2))); } @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: if(mState == STATE_OUT || mState == STATE_RELEASE){ if(mRippleType == TYPE_WAVE || mRippleType == TYPE_TOUCH_MATCH_VIEW) mMaxRippleRadius = getMaxRippleRadius(event.getX(), event.getY()); setRippleEffect(event.getX(), event.getY(), 0); setRippleState(STATE_PRESS); } else if(mRippleType == TYPE_TOUCH){ if(setRippleEffect(event.getX(), event.getY(), mRippleRadius)) invalidateSelf(); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if(mState != STATE_OUT){ if(mState == STATE_HOVER){ if(mRippleType == TYPE_WAVE || mRippleType == TYPE_TOUCH_MATCH_VIEW) setRippleEffect(mRipplePoint.x, mRipplePoint.y, 0); setRippleState(STATE_RELEASE); } else setRippleState(STATE_RELEASE_ON_HOLD); } break; } return true; } //Animation: based on http://cyrilmottier.com/2012/11/27/actionbar-on-the-move/ public void cancel(){ setRippleState(STATE_OUT); } private void resetAnimation(){ mStartTime = SystemClock.uptimeMillis(); } @Override public void start() { if(isRunning()) return; resetAnimation(); scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); invalidateSelf(); } @Override public void stop() { if(!isRunning()) return; mRunning = false; unscheduleSelf(mUpdater); invalidateSelf(); } @Override public boolean isRunning() { return mRunning; } @Override public void scheduleSelf(Runnable what, long when) { mRunning = true; super.scheduleSelf(what, when); } private final Runnable mUpdater = new Runnable() { @Override public void run() { switch (mRippleType) { case TYPE_TOUCH: case TYPE_TOUCH_MATCH_VIEW: updateTouch(); break; case TYPE_WAVE: updateWave(); break; } } }; private void updateTouch(){ if(mState != STATE_RELEASE){ float backgroundProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mBackgroundAnimDuration); mBackgroundAlphaPercent = mInInterpolator.getInterpolation(backgroundProgress) * Color.alpha(mBackgroundColor) / 255f; float touchProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mRippleAnimDuration); mRippleAlphaPercent = mInInterpolator.getInterpolation(touchProgress); setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * mInInterpolator.getInterpolation(touchProgress)); if(backgroundProgress == 1f && touchProgress == 1f){ mStartTime = SystemClock.uptimeMillis(); setRippleState(mState == STATE_PRESS ? STATE_HOVER : STATE_RELEASE); } } else{ float backgroundProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mBackgroundAnimDuration); mBackgroundAlphaPercent = (1f - mOutInterpolator.getInterpolation(backgroundProgress)) * Color.alpha(mBackgroundColor) / 255f; float touchProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mRippleAnimDuration); mRippleAlphaPercent = 1f - mOutInterpolator.getInterpolation(touchProgress); setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * (1f + 0.5f * mOutInterpolator.getInterpolation(touchProgress))); if(backgroundProgress == 1f && touchProgress == 1f) setRippleState(STATE_OUT); } if(isRunning()) scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); invalidateSelf(); } private void updateWave(){ float progress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mRippleAnimDuration); if(mState != STATE_RELEASE){ setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * mInInterpolator.getInterpolation(progress)); if(progress == 1f){ mStartTime = SystemClock.uptimeMillis(); if(mState == STATE_PRESS) setRippleState(STATE_HOVER); else{ setRippleEffect(mRipplePoint.x, mRipplePoint.y, 0); setRippleState(STATE_RELEASE); } } } else{ setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * mOutInterpolator.getInterpolation(progress)); if(progress == 1f) setRippleState(STATE_OUT); } if(isRunning()) scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); invalidateSelf(); } public static class Mask{ public static final int TYPE_RECTANGLE = 0; public static final int TYPE_OVAL = 1; final int type; final float[] cornerRadius = new float[8]; final int left; final int top; final int right; final int bottom; public Mask(int type, int topLeftCornerRadius, int topRightCornerRadius, int bottomRightCornerRadius, int bottomLeftCornerRadius, int left, int top, int right, int bottom){ this.type = type; cornerRadius[0] = topLeftCornerRadius; cornerRadius[1] = topLeftCornerRadius; cornerRadius[2] = topRightCornerRadius; cornerRadius[3] = topRightCornerRadius; cornerRadius[4] = bottomRightCornerRadius; cornerRadius[5] = bottomRightCornerRadius; cornerRadius[6] = bottomLeftCornerRadius; cornerRadius[7] = bottomLeftCornerRadius; this.left = left; this.top = top; this.right = right; this.bottom = bottom; } } public static class Builder{ private Drawable mBackgroundDrawable; private int mBackgroundAnimDuration = 200; private int mBackgroundColor; private int mRippleType; private int mMaxRippleRadius; private int mRippleAnimDuration = 400; private int mRippleColor; private boolean mDelayClick; private Interpolator mInInterpolator; private Interpolator mOutInterpolator; private int mMaskType; private int mMaskTopLeftCornerRadius; private int mMaskTopRightCornerRadius; private int mMaskBottomLeftCornerRadius; private int mMaskBottomRightCornerRadius; private int mMaskLeft; private int mMaskTop; private int mMaskRight; private int mMaskBottom; public Builder(){} public Builder(Context context, int defStyleRes){ this(context, null, 0, defStyleRes); } public Builder(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RippleDrawable, defStyleAttr, defStyleRes); int type, resId; backgroundColor(a.getColor(R.styleable.RippleDrawable_rd_backgroundColor, 0)); backgroundAnimDuration(a.getInteger(R.styleable.RippleDrawable_rd_backgroundAnimDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime))); rippleType(a.getInteger(R.styleable.RippleDrawable_rd_rippleType, RippleDrawable.TYPE_TOUCH)); delayClick(a.getBoolean(R.styleable.RippleDrawable_rd_delayClick, false)); type = ThemeUtil.getType(a, R.styleable.RippleDrawable_rd_maxRippleRadius); if(type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) maxRippleRadius(a.getInteger(R.styleable.RippleDrawable_rd_maxRippleRadius, -1)); else maxRippleRadius(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_maxRippleRadius, ThemeUtil.dpToPx(context, 48))); rippleColor(a.getColor(R.styleable.RippleDrawable_rd_rippleColor, ThemeUtil.colorControlHighlight(context, 0))); rippleAnimDuration(a.getInteger(R.styleable.RippleDrawable_rd_rippleAnimDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime))); if((resId = a.getResourceId(R.styleable.RippleDrawable_rd_inInterpolator, 0)) != 0) inInterpolator(AnimationUtils.loadInterpolator(context, resId)); if((resId = a.getResourceId(R.styleable.RippleDrawable_rd_outInterpolator, 0)) != 0) outInterpolator(AnimationUtils.loadInterpolator(context, resId)); maskType(a.getInteger(R.styleable.RippleDrawable_rd_maskType, Mask.TYPE_RECTANGLE)); cornerRadius(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_cornerRadius, 0)); topLeftCornerRadius(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_topLeftCornerRadius, mMaskTopLeftCornerRadius)); topRightCornerRadius(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_topRightCornerRadius, mMaskTopRightCornerRadius)); bottomRightCornerRadius(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_bottomRightCornerRadius, mMaskBottomRightCornerRadius)); bottomLeftCornerRadius(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_bottomLeftCornerRadius, mMaskBottomLeftCornerRadius)); padding(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_padding, 0)); left(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_leftPadding, mMaskLeft)); right(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_rightPadding, mMaskRight)); top(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_topPadding, mMaskTop)); bottom(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_bottomPadding, mMaskBottom)); a.recycle(); } public RippleDrawable build(){ if(mInInterpolator == null) mInInterpolator = new AccelerateInterpolator(); if(mOutInterpolator == null) mOutInterpolator = new DecelerateInterpolator(); return new RippleDrawable(mBackgroundDrawable, mBackgroundAnimDuration, mBackgroundColor, mRippleType, mDelayClick, mMaxRippleRadius, mRippleAnimDuration, mRippleColor, mInInterpolator, mOutInterpolator, mMaskType, mMaskTopLeftCornerRadius, mMaskTopRightCornerRadius, mMaskBottomRightCornerRadius, mMaskBottomLeftCornerRadius, mMaskLeft, mMaskTop, mMaskRight, mMaskBottom); } public Builder backgroundDrawable(Drawable drawable){ mBackgroundDrawable = drawable; return this; } public Builder backgroundAnimDuration(int duration){ mBackgroundAnimDuration = duration; return this; } public Builder backgroundColor(int color){ mBackgroundColor = color; return this; } public Builder rippleType(int type){ mRippleType = type; return this; } public Builder delayClick(boolean enable){ mDelayClick = enable; return this; } public Builder maxRippleRadius(int radius){ mMaxRippleRadius = radius; return this; } public Builder rippleAnimDuration(int duration){ mRippleAnimDuration = duration; return this; } public Builder rippleColor(int color){ mRippleColor = color; return this; } public Builder inInterpolator(Interpolator interpolator){ mInInterpolator = interpolator; return this; } public Builder outInterpolator(Interpolator interpolator){ mOutInterpolator = interpolator; return this; } public Builder maskType(int type){ mMaskType = type; return this; } public Builder cornerRadius(int radius){ mMaskTopLeftCornerRadius = radius; mMaskTopRightCornerRadius = radius; mMaskBottomLeftCornerRadius = radius; mMaskBottomRightCornerRadius = radius; return this; } public Builder topLeftCornerRadius(int radius){ mMaskTopLeftCornerRadius = radius; return this; } public Builder topRightCornerRadius(int radius){ mMaskTopRightCornerRadius = radius; return this; } public Builder bottomLeftCornerRadius(int radius){ mMaskBottomLeftCornerRadius = radius; return this; } public Builder bottomRightCornerRadius(int radius){ mMaskBottomRightCornerRadius = radius; return this; } public Builder padding(int padding){ mMaskLeft = padding; mMaskTop = padding; mMaskRight = padding; mMaskBottom = padding; return this; } public Builder left(int padding){ mMaskLeft = padding; return this; } public Builder top(int padding){ mMaskTop = padding; return this; } public Builder right(int padding){ mMaskRight = padding; return this; } public Builder bottom(int padding){ mMaskBottom = padding; return this; } } }
ToolbarRippleDrawable.java
package com.rey.material.drawable; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Path.Direction; import android.graphics.PixelFormat; import android.graphics.PointF; import android.graphics.RadialGradient; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.os.SystemClock; import android.util.AttributeSet; import android.view.animation.AccelerateInterpolator; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import com.example.textfieldformaterialdesign.R; import com.rey.material.util.ColorUtil; import com.rey.material.util.ThemeUtil; import com.rey.material.util.ViewUtil; public class ToolbarRippleDrawable extends Drawable implements Animatable { private boolean mRunning = false; private Paint mShaderPaint; private Paint mFillPaint; private RadialGradient mInShader; private RadialGradient mOutShader; private Matrix mMatrix; private int mAlpha = 255; private RectF mBackgroundBounds; private Path mBackground; private int mBackgroundAnimDuration; private int mBackgroundColor; private float mBackgroundAlphaPercent; private PointF mRipplePoint; private float mRippleRadius; private int mRippleType; private int mMaxRippleRadius; private int mRippleAnimDuration; private int mRippleColor; private float mRippleAlphaPercent; private boolean mDelayClick; private Interpolator mInInterpolator; private Interpolator mOutInterpolator; private long mStartTime; private boolean mPressed = false; private int mState = STATE_OUT; private static final int STATE_OUT = 0; private static final int STATE_PRESS = 1; private static final int STATE_HOVER = 2; private static final int STATE_RELEASE_ON_HOLD = 3; private static final int STATE_RELEASE = 4; private static final int TYPE_TOUCH_MATCH_VIEW = -1; private static final int TYPE_TOUCH = 0; private static final int TYPE_WAVE = 1; private static final float[] GRADIENT_STOPS = new float[]{0f, 0.99f, 1f}; private static final float GRADIENT_RADIUS = 16; private ToolbarRippleDrawable(int backgroundAnimDuration, int backgroundColor, int rippleType, boolean delayClick, int maxTouchRadius, int touchAnimDuration, int touchColor, Interpolator inInterpolator, Interpolator outInterpolator){ mBackgroundAnimDuration = backgroundAnimDuration; mBackgroundColor = backgroundColor; mRippleType = rippleType; mMaxRippleRadius = maxTouchRadius; mRippleAnimDuration = touchAnimDuration; mRippleColor = touchColor; mDelayClick = delayClick; if(mRippleType == TYPE_TOUCH && mMaxRippleRadius <= 0) mRippleType = TYPE_TOUCH_MATCH_VIEW; mInInterpolator = inInterpolator; mOutInterpolator = outInterpolator; mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mFillPaint.setStyle(Paint.Style.FILL); mShaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mShaderPaint.setStyle(Paint.Style.FILL); mBackground = new Path(); mBackgroundBounds = new RectF(); mRipplePoint = new PointF(); mMatrix = new Matrix(); mInShader = new RadialGradient(0, 0, GRADIENT_RADIUS, new int[]{mRippleColor, mRippleColor, 0}, GRADIENT_STOPS, Shader.TileMode.CLAMP); if(mRippleType == TYPE_WAVE) mOutShader = new RadialGradient(0, 0, GRADIENT_RADIUS, new int[]{0, ColorUtil.getColor(mRippleColor, 0f), mRippleColor}, GRADIENT_STOPS, Shader.TileMode.CLAMP); } public boolean isDelayClick(){ return mDelayClick; } public void setDelayClick(boolean enable){ mDelayClick = enable; } @Override public void setAlpha(int alpha) { mAlpha = alpha; } @Override public void setColorFilter(ColorFilter filter) { mFillPaint.setColorFilter(filter); mShaderPaint.setColorFilter(filter); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } public long getClickDelayTime(){ if(mState == STATE_RELEASE_ON_HOLD) return (mDelayClick ? 2 : 1) * Math.max(mBackgroundAnimDuration, mRippleAnimDuration) - (SystemClock.uptimeMillis() - mStartTime); else if(mState == STATE_RELEASE) return (mDelayClick ? Math.max(mBackgroundAnimDuration, mRippleAnimDuration) : 0) - (SystemClock.uptimeMillis() - mStartTime); return -1; } private void setRippleState(int state){ if(mState != state){ mState = state; if(mState != STATE_OUT){ if(mState != STATE_HOVER) start(); else stop(); } else stop(); } } private boolean setRippleEffect(float x, float y, float radius){ if(mRipplePoint.x != x || mRipplePoint.y != y || mRippleRadius != radius){ mRipplePoint.set(x, y); mRippleRadius = radius; radius = mRippleRadius / GRADIENT_RADIUS; mMatrix.reset(); mMatrix.postTranslate(x, y); mMatrix.postScale(radius, radius, x, y); mInShader.setLocalMatrix(mMatrix); if(mOutShader != null) mOutShader.setLocalMatrix(mMatrix); return true; } return false; } @Override protected void onBoundsChange(Rect bounds) { mBackgroundBounds.set(bounds.left, bounds.top, bounds.right, bounds.bottom); mBackground.reset(); mBackground.addRect(mBackgroundBounds, Direction.CW); } @Override public boolean isStateful() { return true; } @Override protected boolean onStateChange(int[] state) { boolean pressed = ViewUtil.hasState(state, android.R.attr.state_pressed); if(mPressed != pressed){ mPressed = pressed; if(mPressed){ Rect bounds = getBounds(); if(mState == STATE_OUT || mState == STATE_RELEASE){ if(mRippleType == TYPE_WAVE || mRippleType == TYPE_TOUCH_MATCH_VIEW) mMaxRippleRadius = getMaxRippleRadius(bounds.exactCenterX(), bounds.exactCenterY()); setRippleEffect(bounds.exactCenterX(), bounds.exactCenterY(), 0); setRippleState(STATE_PRESS); } else if(mRippleType == TYPE_TOUCH) setRippleEffect(bounds.exactCenterX(), bounds.exactCenterY(), mRippleRadius); } else{ if(mState != STATE_OUT){ if(mState == STATE_HOVER){ if(mRippleType == TYPE_WAVE|| mRippleType == TYPE_TOUCH_MATCH_VIEW) setRippleEffect(mRipplePoint.x, mRipplePoint.y, 0); setRippleState(STATE_RELEASE); } else setRippleState(STATE_RELEASE_ON_HOLD); } } return true; } return false; } @Override public void draw(Canvas canvas) { switch (mRippleType) { case TYPE_TOUCH: case TYPE_TOUCH_MATCH_VIEW: drawTouch(canvas); break; case TYPE_WAVE: drawWave(canvas); break; } } private void drawTouch(Canvas canvas){ if(mState != STATE_OUT){ if(mBackgroundAlphaPercent > 0){ mFillPaint.setColor(mBackgroundColor); mFillPaint.setAlpha(Math.round(mAlpha * mBackgroundAlphaPercent)); canvas.drawPath(mBackground, mFillPaint); } if(mRippleRadius > 0 && mRippleAlphaPercent > 0){ mShaderPaint.setAlpha(Math.round(mAlpha * mRippleAlphaPercent)); mShaderPaint.setShader(mInShader); canvas.drawPath(mBackground, mShaderPaint); } } } private void drawWave(Canvas canvas){ if(mState != STATE_OUT){ if(mState == STATE_RELEASE){ if(mRippleRadius == 0){ mFillPaint.setColor(mRippleColor); canvas.drawPath(mBackground, mFillPaint); } else{ mShaderPaint.setShader(mOutShader); canvas.drawPath(mBackground, mShaderPaint); } } else if(mRippleRadius > 0){ mShaderPaint.setShader(mInShader); canvas.drawPath(mBackground, mShaderPaint); } } } private int getMaxRippleRadius(float x, float y){ float x1 = x < mBackgroundBounds.centerX() ? mBackgroundBounds.right : mBackgroundBounds.left; float y1 = y < mBackgroundBounds.centerY() ? mBackgroundBounds.bottom : mBackgroundBounds.top; return (int)Math.round(Math.sqrt(Math.pow(x1 - x, 2) + Math.pow(y1 - y, 2))); } //Animation: based on http://cyrilmottier.com/2012/11/27/actionbar-on-the-move/ public void cancel(){ setRippleState(STATE_OUT); } private void resetAnimation(){ mStartTime = SystemClock.uptimeMillis(); } @Override public void start() { if(isRunning()) return; resetAnimation(); scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); invalidateSelf(); } @Override public void stop() { if(!isRunning()) return; mRunning = false; unscheduleSelf(mUpdater); invalidateSelf(); } @Override public boolean isRunning() { return mRunning; } @Override public void scheduleSelf(Runnable what, long when) { mRunning = true; super.scheduleSelf(what, when); } private final Runnable mUpdater = new Runnable() { @Override public void run() { switch (mRippleType) { case TYPE_TOUCH: case TYPE_TOUCH_MATCH_VIEW: updateTouch(); break; case TYPE_WAVE: updateWave(); break; } } }; private void updateTouch(){ if(mState != STATE_RELEASE){ float backgroundProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mBackgroundAnimDuration); mBackgroundAlphaPercent = mInInterpolator.getInterpolation(backgroundProgress) * Color.alpha(mBackgroundColor) / 255f; float touchProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mRippleAnimDuration); mRippleAlphaPercent = mInInterpolator.getInterpolation(touchProgress); setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * mInInterpolator.getInterpolation(touchProgress)); if(backgroundProgress == 1f && touchProgress == 1f){ mStartTime = SystemClock.uptimeMillis(); setRippleState(mState == STATE_PRESS ? STATE_HOVER : STATE_RELEASE); } } else{ float backgroundProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mBackgroundAnimDuration); mBackgroundAlphaPercent = (1f - mOutInterpolator.getInterpolation(backgroundProgress)) * Color.alpha(mBackgroundColor) / 255f; float touchProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mRippleAnimDuration); mRippleAlphaPercent = 1f - mOutInterpolator.getInterpolation(touchProgress); setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * (1f + 0.5f * mOutInterpolator.getInterpolation(touchProgress))); if(backgroundProgress == 1f && touchProgress == 1f) setRippleState(STATE_OUT); } if(isRunning()) scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); invalidateSelf(); } private void updateWave(){ float progress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mRippleAnimDuration); if(mState != STATE_RELEASE){ setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * mInInterpolator.getInterpolation(progress)); if(progress == 1f){ mStartTime = SystemClock.uptimeMillis(); if(mState == STATE_PRESS) setRippleState(STATE_HOVER); else{ setRippleEffect(mRipplePoint.x, mRipplePoint.y, 0); setRippleState(STATE_RELEASE); } } } else{ setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * mOutInterpolator.getInterpolation(progress)); if(progress == 1f) setRippleState(STATE_OUT); } if(isRunning()) scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); invalidateSelf(); } public static class Builder{ private int mBackgroundAnimDuration = 200; private int mBackgroundColor; private int mRippleType; private int mMaxRippleRadius; private int mRippleAnimDuration = 400; private int mRippleColor; private boolean mDelayClick; private Interpolator mInInterpolator; private Interpolator mOutInterpolator; public Builder(){} public Builder(Context context, int defStyleRes){ this(context, null, 0, defStyleRes); } public Builder(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RippleDrawable, defStyleAttr, defStyleRes); int resId; backgroundColor(a.getColor(R.styleable.RippleDrawable_rd_backgroundColor, 0)); backgroundAnimDuration(a.getInteger(R.styleable.RippleDrawable_rd_backgroundAnimDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime))); rippleType(a.getInteger(R.styleable.RippleDrawable_rd_rippleType, ToolbarRippleDrawable.TYPE_TOUCH)); delayClick(a.getBoolean(R.styleable.RippleDrawable_rd_delayClick, false)); maxRippleRadius(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_maxRippleRadius, ThemeUtil.dpToPx(context, 48))); rippleColor(a.getColor(R.styleable.RippleDrawable_rd_rippleColor, ThemeUtil.colorControlHighlight(context, 0))); rippleAnimDuration(a.getInteger(R.styleable.RippleDrawable_rd_rippleAnimDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime))); if((resId = a.getResourceId(R.styleable.RippleDrawable_rd_inInterpolator, 0)) != 0) inInterpolator(AnimationUtils.loadInterpolator(context, resId)); if((resId = a.getResourceId(R.styleable.RippleDrawable_rd_outInterpolator, 0)) != 0) outInterpolator(AnimationUtils.loadInterpolator(context, resId)); a.recycle(); } public ToolbarRippleDrawable build(){ if(mInInterpolator == null) mInInterpolator = new AccelerateInterpolator(); if(mOutInterpolator == null) mOutInterpolator = new DecelerateInterpolator(); return new ToolbarRippleDrawable(mBackgroundAnimDuration, mBackgroundColor, mRippleType, mDelayClick, mMaxRippleRadius, mRippleAnimDuration, mRippleColor, mInInterpolator, mOutInterpolator); } public Builder backgroundAnimDuration(int duration){ mBackgroundAnimDuration = duration; return this; } public Builder backgroundColor(int color){ mBackgroundColor = color; return this; } public Builder rippleType(int type){ mRippleType = type; return this; } public Builder delayClick(boolean enable){ mDelayClick = enable; return this; } public Builder maxRippleRadius(int radius){ mMaxRippleRadius = radius; return this; } public Builder rippleAnimDuration(int duration){ mRippleAnimDuration = duration; return this; } public Builder rippleColor(int color){ mRippleColor = color; return this; } public Builder inInterpolator(Interpolator interpolator){ mInInterpolator = interpolator; return this; } public Builder outInterpolator(Interpolator interpolator){ mOutInterpolator = interpolator; return this; } } }
ContactChipSpan.java
package com.rey.material.text.style; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.Typeface; import android.text.BoringLayout; import android.text.Layout; import android.text.TextPaint; import android.text.TextUtils; import android.text.style.ReplacementSpan; /** * Created by Rey on 1/21/2015. */ public class ContactChipSpan extends ReplacementSpan { private Paint mPaint; private int mPaddingLeft; private int mPaddingRight; private int mBackgroundColor; private int mHeight; private int mWidth; private CharSequence mContactName; private BoringLayout mBoringLayout; private BoringLayout.Metrics mMetrics; private TextPaint mTextPaint; private RectF mRect; private BitmapShader mBitmapShader; private Bitmap mBitmap; private Matrix mMatrix; public ContactChipSpan(CharSequence name, int height, int maxWidth, int paddingLeft, int paddingRight, Typeface typeface, int textColor, int textSize, int backgroundColor) { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(textColor); mPaint.setTypeface(typeface); mPaint.setTextSize(textSize); mTextPaint = new TextPaint(mPaint); mMetrics = new BoringLayout.Metrics(); mRect = new RectF(); mMatrix = new Matrix(); mContactName = name; mPaddingLeft = paddingLeft; mPaddingRight = paddingRight; mBackgroundColor = backgroundColor; mHeight = height; mWidth = Math.round(Math.min(maxWidth, mPaint.measureText(name, 0, name.length()) + paddingLeft + paddingRight + height)); int outerWidth = Math.max(0, mWidth - mPaddingLeft - mPaddingRight - mHeight); mMetrics = BoringLayout.isBoring(mContactName, mTextPaint, mMetrics); mBoringLayout = BoringLayout.make(mContactName, mTextPaint, outerWidth, Layout.Alignment.ALIGN_NORMAL, 1f, 1f, mMetrics, true, TextUtils.TruncateAt.END, outerWidth); } public void setImage(Bitmap bm){ if(mBitmap != bm){ mBitmap = bm; if(mBitmap != null) { mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); mMatrix.reset(); float scale = mHeight / (float)Math.min(mBitmap.getWidth(), mBitmap.getHeight()); mMatrix.setScale(scale, scale, 0, 0); mMatrix.postTranslate((mHeight - mBitmap.getWidth() * scale) / 2, (mHeight - mBitmap.getHeight() * scale) / 2); mBitmapShader.setLocalMatrix(mMatrix); } } } @Override public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { if (fm != null) { int cy = (fm.ascent + fm.descent) / 2; fm.ascent = Math.min(fm.ascent, cy - mHeight / 2); fm.descent = Math.max(fm.descent, cy + mHeight / 2); fm.top = Math.min(fm.top, fm.ascent); fm.bottom = Math.max(fm.bottom, fm.descent); } return mWidth; } @Override public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { canvas.save(); canvas.translate(x, top); float halfHeight = mHeight / 2f; mPaint.setShader(null); mPaint.setColor(mBackgroundColor); mRect.set(1, 0, mHeight + 1, mHeight); canvas.drawArc(mRect, 90, 180, true, mPaint); mRect.set(mWidth - mHeight, 0, mWidth, mHeight); canvas.drawArc(mRect, 270, 180, true, mPaint); mRect.set(halfHeight, 0, mWidth - halfHeight, mHeight); canvas.drawRect(mRect, mPaint); if(mBitmap != null){ mPaint.setShader(mBitmapShader); canvas.drawCircle(halfHeight, halfHeight, halfHeight, mPaint); } if(mContactName != null && mBoringLayout != null) { canvas.translate(mHeight + mPaddingLeft, (mHeight - mBoringLayout.getHeight()) / 2f); mBoringLayout.draw(canvas); } canvas.restore(); } }
ColorUtil.java
package com.rey.material.util; import android.graphics.Color; public class ColorUtil { private static int getMiddleValue(int prev, int next, float factor){ return Math.round(prev + (next - prev) * factor); } public static int getMiddleColor(int prevColor, int curColor, float factor){ if(prevColor == curColor) return curColor; if(factor == 0f) return prevColor; else if(factor == 1f) return curColor; int a = getMiddleValue(Color.alpha(prevColor), Color.alpha(curColor), factor); int r = getMiddleValue(Color.red(prevColor), Color.red(curColor), factor); int g = getMiddleValue(Color.green(prevColor), Color.green(curColor), factor); int b = getMiddleValue(Color.blue(prevColor), Color.blue(curColor), factor); return Color.argb(a, r, g, b); } public static int getColor(int baseColor, float alphaPercent){ int alpha = Math.round(Color.alpha(baseColor) * alphaPercent); return (baseColor & 0x00FFFFFF) | (alpha << 24); } }
ThemeUtil.java
package com.rey.material.util; import android.annotation.TargetApi; import android.content.Context; import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.os.Build; import android.support.v7.internal.widget.TintTypedArray; import android.util.TypedValue; import com.example.textfieldformaterialdesign.R; public class ThemeUtil { private static TypedValue value; public static int dpToPx(Context context, int dp){ return (int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics()) + 0.5f); } public static int spToPx(Context context, int sp){ return (int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, context.getResources().getDisplayMetrics()) + 0.5f); } private static int getColor(Context context, int id, int defaultValue){ if(value == null) value = new TypedValue(); try{ Theme theme = context.getTheme(); if(theme != null && theme.resolveAttribute(id, value, true)){ if (value.type >= TypedValue.TYPE_FIRST_INT && value.type <= TypedValue.TYPE_LAST_INT) return value.data; else if (value.type == TypedValue.TYPE_STRING) return context.getResources().getColor(value.resourceId); } } catch(Exception ex){} return defaultValue; } public static int windowBackground(Context context, int defaultValue){ return getColor(context, android.R.attr.windowBackground, defaultValue); } public static int textColorPrimary(Context context, int defaultValue){ return getColor(context, android.R.attr.textColorPrimary, defaultValue); } public static int textColorSecondary(Context context, int defaultValue){ return getColor(context, android.R.attr.textColorSecondary, defaultValue); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public static int colorPrimary(Context context, int defaultValue){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return getColor(context, android.R.attr.colorPrimary, defaultValue); return getColor(context, R.attr.colorPrimary, defaultValue); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public static int colorPrimaryDark(Context context, int defaultValue){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return getColor(context, android.R.attr.colorPrimaryDark, defaultValue); return getColor(context, R.attr.colorPrimaryDark, defaultValue); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public static int colorAccent(Context context, int defaultValue){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return getColor(context, android.R.attr.colorAccent, defaultValue); return getColor(context, R.attr.colorAccent, defaultValue); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public static int colorControlNormal(Context context, int defaultValue){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return getColor(context, android.R.attr.colorControlNormal, defaultValue); return getColor(context, R.attr.colorControlNormal, defaultValue); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public static int colorControlActivated(Context context, int defaultValue){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return getColor(context, android.R.attr.colorControlActivated, defaultValue); return getColor(context, R.attr.colorControlActivated, defaultValue); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public static int colorControlHighlight(Context context, int defaultValue){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return getColor(context, android.R.attr.colorControlHighlight, defaultValue); return getColor(context, R.attr.colorControlHighlight, defaultValue); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public static int colorButtonNormal(Context context, int defaultValue){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return getColor(context, android.R.attr.colorButtonNormal, defaultValue); return getColor(context, R.attr.colorButtonNormal, defaultValue); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public static int colorSwitchThumbNormal(Context context, int defaultValue){ return getColor(context, R.attr.colorSwitchThumbNormal, defaultValue); } public static int getType(TypedArray array, int index){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return array.getType(index); else{ TypedValue value = array.peekValue(index); return value == null ? TypedValue.TYPE_NULL : value.type; } } public static CharSequence getString(TypedArray array, int index, CharSequence defaultValue){ String result = array.getString(index); return result == null ? defaultValue : result; } public static CharSequence getString(TintTypedArray array, int index, CharSequence defaultValue){ String result = array.getString(index); return result == null ? defaultValue : result; } }
TypefaceUtil.java
package com.rey.material.util; import android.content.Context; import android.graphics.Typeface; import java.util.HashMap; /** * Created by Rey on 12/23/2014. */ public class TypefaceUtil { private static final HashMap<String, Typeface> sCachedFonts = new HashMap<String, Typeface>(); private static final String PREFIX_ASSET = "asset:"; private TypefaceUtil() { } /** * @param familyName if start with 'asset:' prefix, then load font from asset folder. * @return */ public static Typeface load(Context context, String familyName, int style) { if(familyName != null && familyName.startsWith(PREFIX_ASSET)) synchronized (sCachedFonts) { try { if (!sCachedFonts.containsKey(familyName)) { final Typeface typeface = Typeface.createFromAsset(context.getAssets(), familyName); sCachedFonts.put(familyName, typeface); return typeface; } } catch (Exception e) { return Typeface.DEFAULT; } return sCachedFonts.get(familyName); } return Typeface.create(familyName, style); } }
ViewUtil.java
package com.rey.material.util; import java.util.concurrent.atomic.AtomicInteger; import android.annotation.SuppressLint; public class ViewUtil { public static final long FRAME_DURATION = 1000 / 60; private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1); @SuppressLint("NewApi") public static int generateViewId() { if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { for (;;) { final int result = sNextGeneratedId.get(); // aapt-generated IDs have the high byte nonzero; clamp to the range under that. int newValue = result + 1; if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0. if (sNextGeneratedId.compareAndSet(result, newValue)) return result; } } else return android.view.View.generateViewId(); } public static boolean hasState(int[] states, int state){ if(states == null) return false; for (int state1 : states) if (state1 == state) return true; return false; } }
EditText.java
package com.rey.material.widget; import java.io.IOException; import java.util.ArrayList; import java.util.Locale; import org.xmlpull.v1.XmlPullParserException; import android.annotation.TargetApi; import android.content.ClipboardManager; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.view.GravityCompat; import android.text.Editable; import android.text.InputFilter; import android.text.Layout; import android.text.Spannable; import android.text.TextPaint; import android.text.TextUtils; import android.text.TextUtils.TruncateAt; import android.text.TextWatcher; import android.text.method.KeyListener; import android.text.method.MovementMethod; import android.text.method.PasswordTransformationMethod; import android.text.method.TransformationMethod; import android.text.style.URLSpan; import android.util.AttributeSet; import android.util.TypedValue; import android.view.ActionMode; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.widget.AdapterView; import android.widget.AutoCompleteTextView; import android.widget.Filter; import android.widget.Filterable; import android.widget.FrameLayout; import android.widget.ListAdapter; import android.widget.MultiAutoCompleteTextView; import android.widget.Scroller; import com.example.textfieldformaterialdesign.R; import com.rey.material.drawable.DividerDrawable; import com.rey.material.util.ThemeUtil; import com.rey.material.util.ViewUtil; public class EditText extends FrameLayout { private boolean mLabelEnable; private int mSupportMode; private int mAutoCompleteMode; public static final int SUPPORT_MODE_NONE = 0; public static final int SUPPORT_MODE_HELPER = 1; public static final int SUPPORT_MODE_HELPER_WITH_ERROR = 2; public static final int SUPPORT_MODE_CHAR_COUNTER = 3; public static final int AUTOCOMPLETE_MODE_NONE = 0; public static final int AUTOCOMPLETE_MODE_SINGLE = 1; public static final int AUTOCOMPLETE_MODE_MULTI = 2; private ColorStateList mDividerColors; private ColorStateList mDividerErrorColors; private boolean mDividerCompoundPadding; private ColorStateList mSupportColors; private ColorStateList mSupportErrorColors; private int mSupportMaxChars; private CharSequence mSupportHelper; private CharSequence mSupportError; private int mLabelInAnimId; private int mLabelOutAnimId; protected LabelView mLabelView; protected android.widget.EditText mInputView; protected LabelView mSupportView; private DividerDrawable mDivider; private TextView.OnSelectionChangedListener mOnSelectionChangedListener; public EditText(Context context) { super(context); init(context, null, 0, 0); } public EditText(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public EditText(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public EditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, defStyleRes); } @SuppressWarnings("deprecation") @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ applyStyle(context, attrs, defStyleAttr, defStyleRes); } public void applyStyle(int resId){ applyStyle(getContext(), null, 0, resId); } private void applyStyle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ CharSequence text = mInputView == null ? null : mInputView.getText(); CharSequence supportHelper = getHelper(); CharSequence supportError = getError(); removeAllViews(); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.EditText, defStyleAttr, defStyleRes); mLabelEnable = a.getBoolean(R.styleable.EditText_et_labelEnable, false); mSupportMode = a.getInteger(R.styleable.EditText_et_supportMode, SUPPORT_MODE_NONE); mAutoCompleteMode = a.getInteger(R.styleable.EditText_et_autoCompleteMode, AUTOCOMPLETE_MODE_NONE); switch (mAutoCompleteMode){ case AUTOCOMPLETE_MODE_SINGLE: mInputView = new InternalAutoCompleteTextView(context, attrs, defStyleAttr); break; case AUTOCOMPLETE_MODE_MULTI: mInputView = new InternalMultiAutoCompleteTextView(context, attrs, defStyleAttr); break; default: mInputView = new InternalEditText(context, attrs, defStyleAttr); break; } int inputId = a.getResourceId(R.styleable.EditText_et_inputId, 0); mInputView.setId(inputId != 0 ? inputId : ViewUtil.generateViewId()); mInputView.setVisibility(View.VISIBLE); mInputView.setFocusableInTouchMode(true); mDividerColors = a.getColorStateList(R.styleable.EditText_et_dividerColor); mDividerErrorColors = a.getColorStateList(R.styleable.EditText_et_dividerErrorColor); if(mDividerColors == null){ int[][] states = new int[][]{ new int[]{-android.R.attr.state_focused}, new int[]{android.R.attr.state_focused, android.R.attr.state_enabled}, }; int[] colors = new int[]{ ThemeUtil.colorControlNormal(context, 0xFF000000), ThemeUtil.colorControlActivated(context, 0xFF000000), }; mDividerColors = new ColorStateList(states, colors); } if(mDividerErrorColors == null) mDividerErrorColors = ColorStateList.valueOf(ThemeUtil.colorAccent(context, 0xFFFF0000)); int dividerHeight = a.getDimensionPixelOffset(R.styleable.EditText_et_dividerHeight, 0); int dividerPadding = a.getDimensionPixelOffset(R.styleable.EditText_et_dividerPadding, 0); int dividerAnimDuration = a.getInteger(R.styleable.EditText_et_dividerAnimDuration, context.getResources().getInteger(android.R.integer.config_shortAnimTime)); mDividerCompoundPadding = a.getBoolean(R.styleable.EditText_et_dividerCompoundPadding, true); mInputView.setPadding(0, 0, 0, dividerPadding + dividerHeight); mDivider = new DividerDrawable(dividerHeight, mDividerCompoundPadding ? mInputView.getTotalPaddingLeft() : 0, mDividerCompoundPadding ? mInputView.getTotalPaddingRight() : 0, mDividerColors, dividerAnimDuration); mDivider.setInEditMode(isInEditMode()); mDivider.setAnimEnable(false); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) mInputView.setBackground(mDivider); else mInputView.setBackgroundDrawable(mDivider); mDivider.setAnimEnable(true); if(text != null) mInputView.setText(text); mInputView.addTextChangedListener(new InputTextWatcher()); addView(mInputView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); if(mLabelEnable){ mLabelView = new LabelView(context); mLabelView.setGravity(GravityCompat.START); mLabelView.setSingleLine(true); int labelPadding = a.getDimensionPixelOffset(R.styleable.EditText_et_labelPadding, 0); int labelTextSize = a.getDimensionPixelSize(R.styleable.EditText_et_labelTextSize, 0); ColorStateList labelTextColor = a.getColorStateList(R.styleable.EditText_et_labelTextColor); int labelTextAppearance = a.getResourceId(R.styleable.EditText_et_labelTextAppearance, 0); int labelEllipsize = a.getInteger(R.styleable.EditText_et_labelEllipsize, 0); mLabelInAnimId = a.getResourceId(R.styleable.EditText_et_labelInAnim, 0); mLabelOutAnimId = a.getResourceId(R.styleable.EditText_et_labelOutAnim, 0); mLabelView.setPadding(mDivider.getPaddingLeft(), 0, mDivider.getPaddingRight(), labelPadding); if(labelTextAppearance > 0) mLabelView.setTextAppearance(context, labelTextAppearance); if(labelTextSize > 0) mLabelView.setTextSize(TypedValue.COMPLEX_UNIT_PX, labelTextSize); if(labelTextColor != null) mLabelView.setTextColor(labelTextColor); switch (labelEllipsize) { case 1: mLabelView.setEllipsize(TruncateAt.START); break; case 2: mLabelView.setEllipsize(TruncateAt.MIDDLE); break; case 3: mLabelView.setEllipsize(TruncateAt.END); break; case 4: mLabelView.setEllipsize(TruncateAt.MARQUEE); break; default: mLabelView.setEllipsize(TruncateAt.END); break; } addView(mLabelView, 0, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); } if(mSupportMode != SUPPORT_MODE_NONE){ mSupportView = new LabelView(context); int supportPadding = a.getDimensionPixelOffset(R.styleable.EditText_et_supportPadding, 0); int supportTextSize = a.getDimensionPixelSize(R.styleable.EditText_et_supportTextSize, 0); mSupportColors = a.getColorStateList(R.styleable.EditText_et_supportTextColor); mSupportErrorColors = a.getColorStateList(R.styleable.EditText_et_supportTextErrorColor); int supportTextAppearance = a.getResourceId(R.styleable.EditText_et_supportTextAppearance, 0); int supportEllipsize = a.getInteger(R.styleable.EditText_et_supportEllipsize, 0); int supportMaxLines = a.getInteger(R.styleable.EditText_et_supportMaxLines, 0); int supportLines = a.getInteger(R.styleable.EditText_et_supportLines, 0); boolean supportSingleLine = a.getBoolean(R.styleable.EditText_et_supportSingleLine, false); mSupportView.setPadding(mDivider.getPaddingLeft(), supportPadding, mDivider.getPaddingRight(), 0); mSupportView.setTextSize(TypedValue.COMPLEX_UNIT_PX, supportTextSize); mSupportView.setTextColor(mSupportColors); if(supportTextAppearance > 0) mSupportView.setTextAppearance(context, supportTextAppearance); mSupportView.setSingleLine(supportSingleLine); if(supportMaxLines > 0) mSupportView.setMaxLines(supportMaxLines); if(supportLines > 0) mSupportView.setLines(supportLines); switch (supportEllipsize) { case 1: mSupportView.setEllipsize(TruncateAt.START); break; case 2: mSupportView.setEllipsize(TruncateAt.MIDDLE); break; case 3: mSupportView.setEllipsize(TruncateAt.END); break; case 4: mSupportView.setEllipsize(TruncateAt.MARQUEE); break; default: mSupportView.setEllipsize(TruncateAt.END); break; } switch (mSupportMode) { case SUPPORT_MODE_CHAR_COUNTER: mSupportMaxChars = a.getInteger(R.styleable.EditText_et_supportMaxChars, 0); mSupportView.setGravity(GravityCompat.END); updateCharCounter(mInputView.getText().length()); break; case SUPPORT_MODE_HELPER: case SUPPORT_MODE_HELPER_WITH_ERROR: mSupportView.setGravity(GravityCompat.START); mSupportHelper = ThemeUtil.getString(a, R.styleable.EditText_et_helper, supportHelper); setError(ThemeUtil.getString(a, R.styleable.EditText_et_error, supportError)); break; } addView(mSupportView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); } a.recycle(); if(mLabelEnable){ mLabelView.setText(mInputView.getHint()); mLabelView.setVisibility(TextUtils.isEmpty(mInputView.getText().toString()) ? View.INVISIBLE : View.VISIBLE); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int tempWidthSpec = widthMode == MeasureSpec.UNSPECIFIED ? widthMeasureSpec : MeasureSpec.makeMeasureSpec(widthSize - getPaddingLeft() - getPaddingRight(), widthMode); int tempHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); int labelWidth = 0; int labelHeight = 0; int inputWidth = 0; int inputHeight = 0; int supportWidth = 0; int supportHeight = 0; if(mLabelView != null){ mLabelView.measure(tempWidthSpec, tempHeightSpec); labelWidth = mLabelView.getMeasuredWidth(); labelHeight = mLabelView.getMeasuredHeight(); } mInputView.measure(tempWidthSpec, tempHeightSpec); inputWidth = mInputView.getMeasuredWidth(); inputHeight = mInputView.getMeasuredHeight(); if(mSupportView != null){ mSupportView.measure(tempWidthSpec, tempHeightSpec); supportWidth = mSupportView.getMeasuredWidth(); supportHeight = mSupportView.getMeasuredHeight(); } int width = 0; int height = 0; switch (widthMode) { case MeasureSpec.UNSPECIFIED: width = Math.max(labelWidth, Math.max(inputWidth, supportWidth)) + getPaddingLeft() + getPaddingRight(); break; case MeasureSpec.AT_MOST: width = Math.min(widthSize, Math.max(labelWidth, Math.max(inputWidth, supportWidth)) + getPaddingLeft() + getPaddingRight()); break; case MeasureSpec.EXACTLY: width = widthSize; break; } switch (heightMode) { case MeasureSpec.UNSPECIFIED: height = labelHeight + inputHeight + supportHeight + getPaddingTop() + getPaddingBottom(); break; case MeasureSpec.AT_MOST: height = Math.min(heightSize, labelHeight + inputHeight + supportHeight + getPaddingTop() + getPaddingBottom()); break; case MeasureSpec.EXACTLY: height = heightSize; break; } setMeasuredDimension(width, height); tempWidthSpec = MeasureSpec.makeMeasureSpec(width - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY); if(mLabelView != null) mLabelView.measure(tempWidthSpec, tempHeightSpec); mInputView.measure(tempWidthSpec, tempHeightSpec); if(mSupportView != null) mSupportView.measure(tempWidthSpec, tempHeightSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childLeft = getPaddingLeft(); int childRight = r - l - getPaddingRight(); int childTop = getPaddingTop(); int childBottom = b - t - getPaddingBottom(); if(mLabelView != null){ mLabelView.layout(childLeft, childTop, childRight, childTop + mLabelView.getMeasuredHeight()); childTop += mLabelView.getMeasuredHeight(); } if(mSupportView != null){ mSupportView.layout(childLeft, childBottom - mSupportView.getMeasuredHeight(), childRight, childBottom); childBottom -= mSupportView.getMeasuredHeight(); } mInputView.layout(childLeft, childTop, childRight, childBottom); } public void setHelper(CharSequence helper){ mSupportHelper = helper; setError(mSupportError); } public CharSequence getHelper(){ return mSupportHelper; } public void setError(CharSequence error){ mSupportError = error; if(mSupportMode != SUPPORT_MODE_HELPER && mSupportMode != SUPPORT_MODE_HELPER_WITH_ERROR) return; if(mSupportError != null){ mSupportView.setTextColor(mSupportErrorColors); mDivider.setColor(mDividerErrorColors); mSupportView.setText(mSupportMode == SUPPORT_MODE_HELPER ? mSupportError : TextUtils.concat(mSupportHelper, ", ", mSupportError)); } else{ mSupportView.setTextColor(mSupportColors); mDivider.setColor(mDividerColors); mSupportView.setText(mSupportHelper); } } public CharSequence getError(){ return mSupportError; } public void clearError(){ setError(null); } private void updateCharCounter(int count){ if(count == 0){ mSupportView.setTextColor(mSupportColors); mDivider.setColor(mDividerColors); mSupportView.setText(null); } else{ if(mSupportMaxChars > 0){ mSupportView.setTextColor(count > mSupportMaxChars ? mSupportErrorColors : mSupportColors); mDivider.setColor(count > mSupportMaxChars ? mDividerErrorColors : mDividerColors); mSupportView.setText(count + " / " + mSupportMaxChars); } else mSupportView.setText(String.valueOf(count)); } } /* protected method of AutoCompleteTextView */ /** * <p>Converts the selected item from the drop down list into a sequence * of character that can be used in the edit box.</p> * * @param selectedItem the item selected by the user for completion * * @return a sequence of characters representing the selected suggestion */ protected CharSequence convertSelectionToString(Object selectedItem) { switch (mAutoCompleteMode){ case AUTOCOMPLETE_MODE_SINGLE: return ((InternalAutoCompleteTextView)mInputView).superConvertSelectionToString(selectedItem); case AUTOCOMPLETE_MODE_MULTI: return ((InternalMultiAutoCompleteTextView)mInputView).superConvertSelectionToString(selectedItem); default: return null; } } /** * <p>Starts filtering the content of the drop down list. The filtering * pattern is the content of the edit box. Subclasses should override this * method to filter with a different pattern, for instance a substring of * <code>text</code>.</p> * * @param text the filtering pattern * @param keyCode the last character inserted in the edit box; beware that * this will be null when text is being added through a soft input method. */ protected void performFiltering(CharSequence text, int keyCode) { switch (mAutoCompleteMode){ case AUTOCOMPLETE_MODE_SINGLE: ((InternalAutoCompleteTextView)mInputView).superPerformFiltering(text, keyCode); break; case AUTOCOMPLETE_MODE_MULTI: ((InternalMultiAutoCompleteTextView)mInputView).superPerformFiltering(text, keyCode); break; } } /** * <p>Performs the text completion by replacing the current text by the * selected item. Subclasses should override this method to avoid replacing * the whole content of the edit box.</p> * * @param text the selected suggestion in the drop down list */ protected void replaceText(CharSequence text) { switch (mAutoCompleteMode){ case AUTOCOMPLETE_MODE_SINGLE: ((InternalAutoCompleteTextView)mInputView).superReplaceText(text); break; case AUTOCOMPLETE_MODE_MULTI: ((InternalMultiAutoCompleteTextView)mInputView).superReplaceText(text); break; } } /** * Returns the Filter obtained from {@link Filterable#getFilter}, * or <code>null</code> if {@link #setAdapter} was not called with * a Filterable. */ protected Filter getFilter() { switch (mAutoCompleteMode){ case AUTOCOMPLETE_MODE_SINGLE: return ((InternalAutoCompleteTextView)mInputView).superGetFilter(); case AUTOCOMPLETE_MODE_MULTI: return ((InternalMultiAutoCompleteTextView)mInputView).superGetFilter(); default: return null; } } /** * <p>Starts filtering the content of the drop down list. The filtering * pattern is the specified range of text from the edit box. Subclasses may * override this method to filter with a different pattern, for * instance a smaller substring of <code>text</code>.</p> */ protected void performFiltering(CharSequence text, int start, int end, int keyCode) { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_MULTI) ((InternalMultiAutoCompleteTextView)mInputView).superPerformFiltering(text, start, end, keyCode); } /* public method of AutoCompleteTextView */ /** * <p>Sets the optional hint text that is displayed at the bottom of the * the matching list. This can be used as a cue to the user on how to * best use the list, or to provide extra information.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @param hint the text to be displayed to the user * * @see #getCompletionHint() * * @attr ref android.R.styleable#AutoCompleteTextView_completionHint */ public void setCompletionHint(CharSequence hint) { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).setCompletionHint(hint); } /** * Gets the optional hint text displayed at the bottom of the the matching list. * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @return The hint text, if any * * @see #setCompletionHint(CharSequence) * * @attr ref android.R.styleable#AutoCompleteTextView_completionHint */ public CharSequence getCompletionHint() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) return null; return ((AutoCompleteTextView)mInputView).getCompletionHint(); } /** * <p>Returns the current width for the auto-complete drop down list. This can * be a fixed width, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill the screen, or * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @return the width for the drop down list * * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth */ public int getDropDownWidth() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return 0; return ((AutoCompleteTextView)mInputView).getDropDownWidth(); } /** * <p>Sets the current width for the auto-complete drop down list. This can * be a fixed width, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill the screen, or * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @param width the width to use * * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth */ public void setDropDownWidth(int width) { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).setDropDownWidth(width); } /** * <p>Returns the current height for the auto-complete drop down list. This can * be a fixed height, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill * the screen, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the height * of the drop down's content.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @return the height for the drop down list * * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight */ public int getDropDownHeight() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return 0; return ((AutoCompleteTextView)mInputView).getDropDownHeight(); } /** * <p>Sets the current height for the auto-complete drop down list. This can * be a fixed height, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill * the screen, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the height * of the drop down's content.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @param height the height to use * * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight */ public void setDropDownHeight(int height) { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).setDropDownHeight(height); } /** * <p>Returns the id for the view that the auto-complete drop down list is anchored to.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @return the view's id, or {@link View#NO_ID} if none specified * * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor */ public int getDropDownAnchor() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return 0; return ((AutoCompleteTextView)mInputView).getDropDownAnchor(); } /** * <p>Sets the view to which the auto-complete drop down list should anchor. The view * corresponding to this id will not be loaded until the next time it is needed to avoid * loading a view which is not yet instantiated.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @param id the id to anchor the drop down list view to * * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor */ public void setDropDownAnchor(int id) { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).setDropDownAnchor(id); } /** * <p>Gets the background of the auto-complete drop-down list.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @return the background drawable * * @attr ref android.R.styleable#PopupWindow_popupBackground */ public Drawable getDropDownBackground() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return null; return ((AutoCompleteTextView)mInputView).getDropDownBackground(); } /** * <p>Sets the background of the auto-complete drop-down list.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @param d the drawable to set as the background * * @attr ref android.R.styleable#PopupWindow_popupBackground */ public void setDropDownBackgroundDrawable(Drawable d) { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).setDropDownBackgroundDrawable(d); } /** * <p>Sets the background of the auto-complete drop-down list.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @param id the id of the drawable to set as the background * * @attr ref android.R.styleable#PopupWindow_popupBackground */ public void setDropDownBackgroundResource(int id) { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).setDropDownBackgroundResource(id); } /** * <p>Sets the vertical offset used for the auto-complete drop-down list.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @param offset the vertical offset * * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset */ public void setDropDownVerticalOffset(int offset) { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).setDropDownVerticalOffset(offset); } /** * <p>Gets the vertical offset used for the auto-complete drop-down list.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @return the vertical offset * * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset */ public int getDropDownVerticalOffset() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return 0; return ((AutoCompleteTextView)mInputView).getDropDownVerticalOffset(); } /** * <p>Sets the horizontal offset used for the auto-complete drop-down list.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @param offset the horizontal offset * * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset */ public void setDropDownHorizontalOffset(int offset) { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).setDropDownHorizontalOffset(offset); } /** * <p>Gets the horizontal offset used for the auto-complete drop-down list.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @return the horizontal offset * * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset */ public int getDropDownHorizontalOffset() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return 0; return ((AutoCompleteTextView)mInputView).getDropDownHorizontalOffset(); } /** * <p>Returns the number of characters the user must type before the drop * down list is shown.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @return the minimum number of characters to type to show the drop down * * @see #setThreshold(int) * * @attr ref android.R.styleable#AutoCompleteTextView_completionThreshold */ public int getThreshold() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return 0; return ((AutoCompleteTextView)mInputView).getThreshold(); } /** * <p>Specifies the minimum number of characters the user has to type in the * edit box before the drop down list is shown.</p> * * <p>When <code>threshold</code> is less than or equals 0, a threshold of * 1 is applied.</p> * * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @param threshold the number of characters to type before the drop down * is shown * * @see #getThreshold() * * @attr ref android.R.styleable#AutoCompleteTextView_completionThreshold */ public void setThreshold(int threshold) { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).setThreshold(threshold); } /** * <p>Sets the listener that will be notified when the user clicks an item * in the drop down list.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @param l the item click listener */ public void setOnItemClickListener(AdapterView.OnItemClickListener l) { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).setOnItemClickListener(l); } /** * <p>Sets the listener that will be notified when the user selects an item * in the drop down list.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @param l the item selected listener */ public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener l) { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).setOnItemSelectedListener(l); } /** * <p>Returns the listener that is notified whenever the user clicks an item * in the drop down list.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @return the item click listener */ public AdapterView.OnItemClickListener getOnItemClickListener() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return null; return ((AutoCompleteTextView)mInputView).getOnItemClickListener(); } /** * <p>Returns the listener that is notified whenever the user selects an * item in the drop down list.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @return the item selected listener */ public AdapterView.OnItemSelectedListener getOnItemSelectedListener() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return null; return ((AutoCompleteTextView)mInputView).getOnItemSelectedListener(); } /** * <p>Returns a filterable list adapter used for auto completion.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @return a data adapter used for auto completion */ public ListAdapter getAdapter() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return null; return ((AutoCompleteTextView)mInputView).getAdapter(); } /** * <p>Changes the list of data used for auto completion. The provided list * must be a filterable list adapter.</p> * * <p>The caller is still responsible for managing any resources used by the adapter. * Notably, when the AutoCompleteTextView is closed or released, the adapter is not notified. * A common case is the use of {@link android.widget.CursorAdapter}, which * contains a {@link android.database.Cursor} that must be closed. This can be done * automatically (see * {@link android.app.Activity#startManagingCursor(android.database.Cursor) * startManagingCursor()}), * or by manually closing the cursor when the AutoCompleteTextView is dismissed.</p> * * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @param adapter the adapter holding the auto completion data * * @see #getAdapter() * @see android.widget.Filterable * @see android.widget.ListAdapter */ public <T extends ListAdapter & Filterable> void setAdapter(T adapter) { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).setAdapter(adapter); } /** * Returns <code>true</code> if the amount of text in the field meets * or exceeds the {@link #getThreshold} requirement. You can override * this to impose a different standard for when filtering will be * triggered. * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> */ public boolean enoughToFilter() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return false; return ((AutoCompleteTextView)mInputView).enoughToFilter(); } /** * <p>Indicates whether the popup menu is showing.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @return true if the popup menu is showing, false otherwise */ public boolean isPopupShowing() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return false; return ((AutoCompleteTextView)mInputView).isPopupShowing(); } /** * <p>Clear the list selection. This may only be temporary, as user input will often bring * it back. * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> */ public void clearListSelection() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).clearListSelection(); } /** * Set the position of the dropdown view selection. * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @param position The position to move the selector to. */ public void setListSelection(int position) { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).setListSelection(position); } /** * Get the position of the dropdown view selection, if there is one. Returns * {@link android.widget.ListView#INVALID_POSITION ListView.INVALID_POSITION} if there is no dropdown or if * there is no selection. * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @return the position of the current selection, if there is one, or * {@link android.widget.ListView#INVALID_POSITION ListView.INVALID_POSITION} if not. * * @see android.widget.ListView#getSelectedItemPosition() */ public int getListSelection() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return 0; return ((AutoCompleteTextView)mInputView).getListSelection(); } /** * <p>Performs the text completion by converting the selected item from * the drop down list into a string, replacing the text box's content with * this string and finally dismissing the drop down menu.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> */ public void performCompletion() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).performCompletion(); } /** * Identifies whether the view is currently performing a text completion, so subclasses * can decide whether to respond to text changed events. * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> */ public boolean isPerformingCompletion() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return false; return ((AutoCompleteTextView)mInputView).isPerformingCompletion(); } /** <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> */ public void onFilterComplete(int count) { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_SINGLE) ((InternalAutoCompleteTextView)mInputView).superOnFilterComplete(count); else if(mAutoCompleteMode == AUTOCOMPLETE_MODE_MULTI) ((InternalMultiAutoCompleteTextView)mInputView).superOnFilterComplete(count); } /** * <p>Closes the drop down if present on screen.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> */ public void dismissDropDown() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).dismissDropDown(); } /** * <p>Displays the drop down on screen.</p> * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> */ public void showDropDown() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).showDropDown(); } /** * Sets the validator used to perform text validation. * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @param validator The validator used to validate the text entered in this widget. * * @see #getValidator() * @see #performValidation() */ public void setValidator(AutoCompleteTextView.Validator validator) { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).setValidator(validator); } /** * Returns the Validator set with {@link #setValidator}, * or <code>null</code> if it was not set. * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @see #setValidator(android.widget.AutoCompleteTextView.Validator) * @see #performValidation() */ public AutoCompleteTextView.Validator getValidator() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return null; return ((AutoCompleteTextView)mInputView).getValidator(); } /** * If a validator was set on this view and the current string is not valid, * ask the validator to fix it. * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_SINGLE or AUTOCOMPLETE_MODE_MULTI</p> * * @see #getValidator() * @see #setValidator(android.widget.AutoCompleteTextView.Validator) */ public void performValidation() { if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return; ((AutoCompleteTextView)mInputView).performValidation(); } /** * Sets the Tokenizer that will be used to determine the relevant * range of the text where the user is typing. * <p>Only work when autoCompleMode is AUTOCOMPLETE_MODE_MULTI</p> */ public void setTokenizer(MultiAutoCompleteTextView.Tokenizer t) { if(mAutoCompleteMode != AUTOCOMPLETE_MODE_MULTI) return; ((MultiAutoCompleteTextView)mInputView).setTokenizer(t); } /* public method of EditText */ @Override public void setEnabled(boolean enabled){ mInputView.setEnabled(enabled); } /** * Convenience for {@link android.text.Selection#extendSelection}. */ public void extendSelection (int index){ mInputView.extendSelection(index); } public Editable getText (){ return mInputView.getText(); } public void selectAll (){ mInputView.selectAll(); } public void setEllipsize (TruncateAt ellipsis){ mInputView.setEllipsize(ellipsis); } /** * Convenience for {@link android.text.Selection#setSelection(android.text.Spannable, int)}. */ public void setSelection (int index){ mInputView.setSelection(index); } /** * Convenience for {@link android.text.Selection#setSelection(android.text.Spannable, int, int)}. */ public void setSelection (int start, int stop){ mInputView.setSelection(start, stop); } public void setText (CharSequence text, android.widget.TextView.BufferType type){ mInputView.setText(text, type); } /** * Adds a TextWatcher to the list of those whose methods are called * whenever this TextView's text changes. * <p> * In 1.0, the {@link android.text.TextWatcher#afterTextChanged} method was erroneously * not called after {@link #setText} calls. Now, doing {@link #setText} * if there are any text changed listeners forces the buffer type to * Editable if it would not otherwise be and does call this method. */ public void addTextChangedListener(TextWatcher textWatcher){ mInputView.addTextChangedListener(textWatcher); } /** * Convenience method: Append the specified text to the TextView's * display buffer, upgrading it to BufferType.EDITABLE if it was * not already editable. */ public final void append (CharSequence text){ mInputView.append(text); } /** * Convenience method: Append the specified text slice to the TextView's * display buffer, upgrading it to BufferType.EDITABLE if it was * not already editable. */ public void append (CharSequence text, int start, int end){ mInputView.append(text, start, end); } public void beginBatchEdit (){ mInputView.beginBatchEdit(); } /** * Move the point, specified by the offset, into the view if it is needed. * This has to be called after layout. Returns true if anything changed. */ public boolean bringPointIntoView (int offset){ return mInputView.bringPointIntoView(offset); } public void cancelLongPress (){ mInputView.cancelLongPress(); } /** * Use {@link android.view.inputmethod.BaseInputConnection#removeComposingSpans * BaseInputConnection.removeComposingSpans()} to remove any IME composing * state from this text view. */ public void clearComposingText (){ mInputView.clearComposingText(); } @Override public void computeScroll (){ mInputView.computeScroll(); } @Override public void debug (int depth){ mInputView.debug(depth); } /** * Returns true, only while processing a touch gesture, if the initial * touch down event caused focus to move to the text view and as a result * its selection changed. Only valid while processing the touch gesture * of interest, in an editable text view. */ public boolean didTouchFocusSelect (){ return mInputView.didTouchFocusSelect(); } public void endBatchEdit (){ mInputView.endBatchEdit(); } /** * If this TextView contains editable content, extract a portion of it * based on the information in <var>request</var> in to <var>outText</var>. * @return Returns true if the text was successfully extracted, else false. */ public boolean extractText (ExtractedTextRequest request, ExtractedText outText){ return mInputView.extractText(request, outText); } @Override @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public void findViewsWithText (ArrayList<View> outViews, CharSequence searched, int flags){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) mInputView.findViewsWithText(outViews, searched, flags); } /** * Gets the autolink mask of the text. See {@link * android.text.util.Linkify#ALL Linkify.ALL} and peers for * possible values. * * @attr ref android.R.styleable#TextView_autoLink */ public final int getAutoLinkMask (){ return mInputView.getAutoLinkMask(); } @Override public int getBaseline (){ return mInputView.getBaseline(); } /** * Returns the padding between the compound drawables and the text. * * @attr ref android.R.styleable#TextView_drawablePadding */ public int getCompoundDrawablePadding (){ return mInputView.getCompoundDrawablePadding(); } /** * Returns drawables for the left, top, right, and bottom borders. * * @attr ref android.R.styleable#TextView_drawableLeft * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableRight * @attr ref android.R.styleable#TextView_drawableBottom */ public Drawable[] getCompoundDrawables (){ return mInputView.getCompoundDrawables(); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public Drawable[] getCompoundDrawablesRelative (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) return mInputView.getCompoundDrawablesRelative(); return mInputView.getCompoundDrawables(); } /** * Returns the bottom padding of the view, plus space for the bottom * Drawable if any. */ public int getCompoundPaddingBottom (){ return mInputView.getCompoundPaddingBottom(); } /** * Returns the end padding of the view, plus space for the end * Drawable if any. */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public int getCompoundPaddingEnd (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) return mInputView.getCompoundPaddingEnd(); return mInputView.getCompoundPaddingRight(); } /** * Returns the left padding of the view, plus space for the left * Drawable if any. */ public int getCompoundPaddingLeft (){ return mInputView.getCompoundPaddingLeft(); } /** * Returns the right padding of the view, plus space for the right * Drawable if any. */ public int getCompoundPaddingRight (){ return mInputView.getCompoundPaddingRight(); } /** * Returns the start padding of the view, plus space for the start * Drawable if any. */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public int getCompoundPaddingStart (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) return mInputView.getCompoundPaddingStart(); return mInputView.getCompoundPaddingLeft(); } /** * Returns the top padding of the view, plus space for the top * Drawable if any. */ public int getCompoundPaddingTop (){ return mInputView.getCompoundPaddingTop(); } /** * <p>Return the current color selected to paint the hint text.</p> * * @return Returns the current hint text color. */ public final int getCurrentHintTextColor (){ return mInputView.getCurrentHintTextColor(); } /** * <p>Return the current color selected for normal text.</p> * * @return Returns the current text color. */ public final int getCurrentTextColor (){ return mInputView.getCurrentTextColor(); } /** * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. * * @return The current custom selection callback. */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public ActionMode.Callback getCustomSelectionActionModeCallback (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) return mInputView.getCustomSelectionActionModeCallback(); return null; } /** * Return the text the TextView is displaying as an Editable object. If * the text is not editable, null is returned. * * @see #getText */ public Editable getEditableText (){ return mInputView.getEditableText(); } /** * Returns where, if anywhere, words that are longer than the view * is wide should be ellipsized. */ public TruncateAt getEllipsize (){ return mInputView.getEllipsize(); } /** * Returns the extended bottom padding of the view, including both the * bottom Drawable if any and any extra space to keep more than maxLines * of text from showing. It is only valid to call this after measuring. */ public int getExtendedPaddingBottom (){ return mInputView.getExtendedPaddingBottom(); } /** * Returns the extended top padding of the view, including both the * top Drawable if any and any extra space to keep more than maxLines * of text from showing. It is only valid to call this after measuring. */ public int getExtendedPaddingTop (){ return mInputView.getExtendedPaddingTop(); } /** * Returns the current list of input filters. * * @attr ref android.R.styleable#TextView_maxLength */ public InputFilter[] getFilters (){ return mInputView.getFilters(); } @Override public void getFocusedRect (@NonNull Rect r){ mInputView.getFocusedRect(r); } /** * @return the currently set font feature settings. Default is null. * * @see #setFontFeatureSettings(String) * @see android.graphics.Paint#setFontFeatureSettings */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public String getFontFeatureSettings (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return mInputView.getFontFeatureSettings(); return null; } /** * Return whether this text view is including its entire text contents * in frozen icicles. * * @return Returns true if text is included, false if it isn't. * * @see #setFreezesText */ public boolean getFreezesText (){ return mInputView.getFreezesText(); } /** * Returns the horizontal and vertical alignment of this TextView. * * @see android.view.Gravity * @attr ref android.R.styleable#TextView_gravity */ public int getGravity (){ return mInputView.getGravity(); } /** * @return the color used to display the selection highlight * * @see #setHighlightColor(int) * * @attr ref android.R.styleable#TextView_textColorHighlight */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public int getHighlightColor (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return mInputView.getHighlightColor(); return 0; } /** * Returns the hint that is displayed when the text of the TextView * is empty. * * @attr ref android.R.styleable#TextView_hint */ public CharSequence getHint (){ return mInputView.getHint(); } /** * @return the color of the hint text, for the different states of this TextView. * * @see #setHintTextColor(android.content.res.ColorStateList) * @see #setHintTextColor(int) * @see #setTextColor(android.content.res.ColorStateList) * @see #setLinkTextColor(android.content.res.ColorStateList) * * @attr ref android.R.styleable#TextView_textColorHint */ public final ColorStateList getHintTextColors (){ return mInputView.getHintTextColors(); } /** * Get the IME action ID previous set with {@link #setImeActionLabel}. * * @see #setImeActionLabel * @see android.view.inputmethod.EditorInfo */ public int getImeActionId (){ return mInputView.getImeActionId(); } /** * Get the IME action label previous set with {@link #setImeActionLabel}. * * @see #setImeActionLabel * @see android.view.inputmethod.EditorInfo */ public CharSequence getImeActionLabel (){ return mInputView.getImeActionLabel(); } /** * Get the type of the IME editor. * * @see #setImeOptions(int) * @see android.view.inputmethod.EditorInfo */ public int getImeOptions (){ return mInputView.getImeOptions(); } /** * Gets whether the TextView includes extra top and bottom padding to make * room for accents that go above the normal ascent and descent. * * @see #setIncludeFontPadding(boolean) * * @attr ref android.R.styleable#TextView_includeFontPadding */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public boolean getIncludeFontPadding (){ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && mInputView.getIncludeFontPadding(); } /** * Retrieve the input extras currently associated with the text view, which * can be viewed as well as modified. * * @param create If true, the extras will be created if they don't already * exist. Otherwise, null will be returned if none have been created. * @see #setInputExtras(int) * @see android.view.inputmethod.EditorInfo#extras * @attr ref android.R.styleable#TextView_editorExtras */ public Bundle getInputExtras (boolean create){ return mInputView.getInputExtras(create); } /** * Get the type of the editable content. * * @see #setInputType(int) * @see android.text.InputType */ public int getInputType (){ return mInputView.getInputType(); } /** * @return the current key listener for this TextView. * This will frequently be null for non-EditText TextViews. * * @attr ref android.R.styleable#TextView_numeric * @attr ref android.R.styleable#TextView_digits * @attr ref android.R.styleable#TextView_phoneNumber * @attr ref android.R.styleable#TextView_inputMethod * @attr ref android.R.styleable#TextView_capitalize * @attr ref android.R.styleable#TextView_autoText */ public final KeyListener getKeyListener (){ return mInputView.getKeyListener(); } /** * @return the Layout that is currently being used to display the text. * This can be null if the text or width has recently changes. */ public final Layout getLayout (){ return mInputView.getLayout(); } /** * @return the extent by which text is currently being letter-spaced. * This will normally be 0. * * @see #setLetterSpacing(float) * @see android.graphics.Paint#setLetterSpacing */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public float getLetterSpacing (){ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? mInputView.getLetterSpacing() : 0; } /** * Return the baseline for the specified line (0...getLineCount() - 1) * If bounds is not null, return the top, left, right, bottom extents * of the specified line in it. If the internal Layout has not been built, * return 0 and set bounds to (0, 0, 0, 0) * @param line which line to examine (0..getLineCount() - 1) * @param bounds Optional. If not null, it returns the extent of the line * @return the Y-coordinate of the baseline */ public int getLineBounds (int line, Rect bounds){ return mInputView.getLineBounds(line, bounds); } /** * Return the number of lines of text, or 0 if the internal Layout has not * been built. */ public int getLineCount (){ return mInputView.getLineCount(); } /** * @return the height of one standard line in pixels. Note that markup * within the text can cause individual lines to be taller or shorter * than this height, and the layout may contain additional first- * or last-line padding. */ public int getLineHeight (){ return mInputView.getLineHeight(); } /** * Gets the line spacing extra space * * @return the extra space that is added to the height of each lines of this TextView. * * @see #setLineSpacing(float, float) * @see #getLineSpacingMultiplier() * * @attr ref android.R.styleable#TextView_lineSpacingExtra */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public float getLineSpacingExtra (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return mInputView.getLineSpacingExtra(); return 0f; } /** * Gets the line spacing multiplier * * @return the value by which each line's height is multiplied to get its actual height. * * @see #setLineSpacing(float, float) * @see #getLineSpacingExtra() * * @attr ref android.R.styleable#TextView_lineSpacingMultiplier */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public float getLineSpacingMultiplier (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return mInputView.getLineSpacingMultiplier(); return 0f; } /** * @return the list of colors used to paint the links in the text, for the different states of * this TextView * * @see #setLinkTextColor(android.content.res.ColorStateList) * @see #setLinkTextColor(int) * * @attr ref android.R.styleable#TextView_textColorLink */ public final ColorStateList getLinkTextColors (){ return mInputView.getLinkTextColors(); } /** * Returns whether the movement method will automatically be set to * {@link android.text.method.LinkMovementMethod} if {@link #setAutoLinkMask} has been * set to nonzero and links are detected in {@link #setText}. * The default is true. * * @attr ref android.R.styleable#TextView_linksClickable */ public final boolean getLinksClickable (){ return mInputView.getLinksClickable(); } /** * Gets the number of times the marquee animation is repeated. Only meaningful if the * TextView has marquee enabled. * * @return the number of times the marquee animation is repeated. -1 if the animation * repeats indefinitely * * @see #setMarqueeRepeatLimit(int) * * @attr ref android.R.styleable#TextView_marqueeRepeatLimit */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public int getMarqueeRepeatLimit (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return mInputView.getMarqueeRepeatLimit(); return -1; } /** * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}). * * @see #setMaxEms(int) * @see #setEms(int) * * @attr ref android.R.styleable#TextView_maxEms */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public int getMaxEms (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return mInputView.getMaxEms(); return -1; } /** * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}. * * @see #setMaxHeight(int) * * @attr ref android.R.styleable#TextView_maxHeight */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public int getMaxHeight (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return mInputView.getMaxHeight(); return -1; } /** * @return the maximum number of lines displayed in this TextView, or -1 if the maximum * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}. * * @see #setMaxLines(int) * * @attr ref android.R.styleable#TextView_maxLines */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public int getMaxLines (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return mInputView.getMaxLines(); return -1; } /** * @return the maximum width of the TextView, in pixels or -1 if the maximum width * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}). * * @see #setMaxWidth(int) * @see #setWidth(int) * * @attr ref android.R.styleable#TextView_maxWidth */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public int getMaxWidth (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return mInputView.getMaxWidth(); return -1; } /** * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}). * * @see #setMinEms(int) * @see #setEms(int) * * @attr ref android.R.styleable#TextView_minEms */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public int getMinEms (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return mInputView.getMinEms(); return -1; } /** * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}. * * @see #setMinHeight(int) * * @attr ref android.R.styleable#TextView_minHeight */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public int getMinHeight (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return mInputView.getMinHeight(); return -1; } /** * @return the minimum number of lines displayed in this TextView, or -1 if the minimum * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}. * * @see #setMinLines(int) * * @attr ref android.R.styleable#TextView_minLines */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public int getMinLines (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return mInputView.getMinLines(); return -1; } /** * @return the minimum width of the TextView, in pixels or -1 if the minimum width * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}). * * @see #setMinWidth(int) * @see #setWidth(int) * * @attr ref android.R.styleable#TextView_minWidth */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public int getMinWidth (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return mInputView.getMinWidth(); return -1; } /** * @return the movement method being used for this TextView. * This will frequently be null for non-EditText TextViews. */ public final MovementMethod getMovementMethod (){ return mInputView.getMovementMethod(); } /** * Get the character offset closest to the specified absolute position. A typical use case is to * pass the result of {@link android.view.MotionEvent#getX()} and {@link android.view.MotionEvent#getY()} to this method. * * @param x The horizontal absolute position of a point on screen * @param y The vertical absolute position of a point on screen * @return the character offset for the character whose position is closest to the specified * position. Returns -1 if there is no layout. */ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public int getOffsetForPosition (float x, float y){ if (getLayout() == null) return -1; final int line = getLineAtCoordinate(y); final int offset = getOffsetAtCoordinate(line, x); return offset; } protected float convertToLocalHorizontalCoordinate(float x) { x -= getTotalPaddingLeft(); // Clamp the position to inside of the view. x = Math.max(0.0f, x); x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); x += getScrollX(); return x; } protected int getLineAtCoordinate(float y) { y -= getTotalPaddingTop(); // Clamp the position to inside of the view. y = Math.max(0.0f, y); y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); y += getScrollY(); return getLayout().getLineForVertical((int) y); } protected int getOffsetAtCoordinate(int line, float x) { x = convertToLocalHorizontalCoordinate(x); return getLayout().getOffsetForHorizontal(line, x); } /** * @return the base paint used for the text. Please use this only to * consult the Paint's properties and not to change them. */ public TextPaint getPaint (){ return mInputView.getPaint(); } /** * @return the flags on the Paint being used to display the text. * @see android.graphics.Paint#getFlags */ public int getPaintFlags (){ return mInputView.getPaintFlags(); } /** * Get the private type of the content. * * @see #setPrivateImeOptions(String) * @see android.view.inputmethod.EditorInfo#privateImeOptions */ public String getPrivateImeOptions (){ return mInputView.getPrivateImeOptions(); } /** * Convenience for {@link android.text.Selection#getSelectionEnd}. */ public int getSelectionEnd (){ return mInputView.getSelectionEnd(); } /** * Convenience for {@link android.text.Selection#getSelectionStart}. */ public int getSelectionStart (){ return mInputView.getSelectionStart(); } /** * @return the color of the shadow layer * * @see #setShadowLayer(float, float, float, int) * * @attr ref android.R.styleable#TextView_shadowColor */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public int getShadowColor (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return mInputView.getShadowColor(); return 0; } /** * @return the horizontal offset of the shadow layer * * @see #setShadowLayer(float, float, float, int) * * @attr ref android.R.styleable#TextView_shadowDx */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public float getShadowDx (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return mInputView.getShadowDx(); return 0; } /** * @return the vertical offset of the shadow layer * * @see #setShadowLayer(float, float, float, int) * * @attr ref android.R.styleable#TextView_shadowDy */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public float getShadowDy (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return mInputView.getShadowDy(); return 0; } /** * Gets the radius of the shadow layer. * * @return the radius of the shadow layer. If 0, the shadow layer is not visible * * @see #setShadowLayer(float, float, float, int) * * @attr ref android.R.styleable#TextView_shadowRadius */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public float getShadowRadius (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return mInputView.getShadowRadius(); return 0; } /** * Returns whether the soft input method will be made visible when this * TextView gets focused. The default is true. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public final boolean getShowSoftInputOnFocus (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return mInputView.getShowSoftInputOnFocus(); return true; } /** * Gets the text colors for the different states (normal, selected, focused) of the TextView. * * @see #setTextColor(android.content.res.ColorStateList) * @see #setTextColor(int) * * @attr ref android.R.styleable#TextView_textColor */ public final ColorStateList getTextColors (){ return mInputView.getTextColors(); } /** * Get the default {@link java.util.Locale} of the text in this TextView. * @return the default {@link java.util.Locale} of the text in this TextView. */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public Locale getTextLocale (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) return mInputView.getTextLocale(); return Locale.getDefault(); } /** * @return the extent by which text is currently being stretched * horizontally. This will usually be 1. */ public float getTextScaleX (){ return mInputView.getTextScaleX(); } /** * @return the size (in pixels) of the default text size in this TextView. */ public float getTextSize (){ return mInputView.getTextSize(); } /** * Returns the total bottom padding of the view, including the bottom * Drawable if any, the extra space to keep more than maxLines * from showing, and the vertical offset for gravity, if any. */ public int getTotalPaddingBottom (){ return getPaddingBottom() + mInputView.getTotalPaddingBottom() + (mSupportMode != SUPPORT_MODE_NONE ? mSupportView.getHeight() : 0); } /** * Returns the total end padding of the view, including the end * Drawable if any. */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public int getTotalPaddingEnd (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) return getPaddingEnd() + mInputView.getTotalPaddingEnd(); return getTotalPaddingRight(); } /** * Returns the total left padding of the view, including the left * Drawable if any. */ public int getTotalPaddingLeft (){ return getPaddingLeft() + mInputView.getTotalPaddingLeft(); } /** * Returns the total right padding of the view, including the right * Drawable if any. */ public int getTotalPaddingRight (){ return getPaddingRight() + mInputView.getTotalPaddingRight(); } /** * Returns the total start padding of the view, including the start * Drawable if any. */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public int getTotalPaddingStart (){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) return getPaddingStart() + mInputView.getTotalPaddingStart(); return getTotalPaddingLeft(); } /** * Returns the total top padding of the view, including the top * Drawable if any, the extra space to keep more than maxLines * from showing, and the vertical offset for gravity, if any. */ public int getTotalPaddingTop (){ return getPaddingTop() + mInputView.getTotalPaddingTop() + (mLabelEnable ? mLabelView.getHeight() : 0); } /** * @return the current transformation method for this TextView. * This will frequently be null except for single-line and password * fields. * * @attr ref android.R.styleable#TextView_password * @attr ref android.R.styleable#TextView_singleLine */ public final TransformationMethod getTransformationMethod (){ return mInputView.getTransformationMethod(); } /** * @return the current typeface and style in which the text is being * displayed. * * @see #setTypeface(android.graphics.Typeface) * * @attr ref android.R.styleable#TextView_fontFamily * @attr ref android.R.styleable#TextView_typeface * @attr ref android.R.styleable#TextView_textStyle */ public Typeface getTypeface (){ return mInputView.getTypeface(); } /** * Returns the list of URLSpans attached to the text * (by {@link android.text.util.Linkify} or otherwise) if any. You can call * {@link android.text.style.URLSpan#getURL} on them to find where they link to * or use {@link android.text.Spanned#getSpanStart} and {@link android.text.Spanned#getSpanEnd} * to find the region of the text they are attached to. */ public URLSpan[] getUrls (){ return mInputView.getUrls(); } @Override @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public boolean hasOverlappingRendering (){ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && mInputView.hasOverlappingRendering(); } /** * Return true iff there is a selection inside this text view. */ public boolean hasSelection (){ return mInputView.hasSelection(); } /** * @return whether or not the cursor is visible (assuming this TextView is editable) * * @see #setCursorVisible(boolean) * * @attr ref android.R.styleable#TextView_cursorVisible */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public boolean isCursorVisible (){ return Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN || mInputView.isCursorVisible(); } /** * Returns whether this text view is a current input method target. The * default implementation just checks with {@link android.view.inputmethod.InputMethodManager}. */ public boolean isInputMethodTarget (){ return mInputView.isInputMethodTarget(); } /** * Return whether or not suggestions are enabled on this TextView. The suggestions are generated * by the IME or by the spell checker as the user types. This is done by adding * {@link android.text.style.SuggestionSpan}s to the text. * * When suggestions are enabled (default), this list of suggestions will be displayed when the * user asks for them on these parts of the text. This value depends on the inputType of this * TextView. * * The class of the input type must be {@link android.text.InputType#TYPE_CLASS_TEXT}. * * In addition, the type variation must be one of * {@link android.text.InputType#TYPE_TEXT_VARIATION_NORMAL}, * {@link android.text.InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, * {@link android.text.InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, * {@link android.text.InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or * {@link android.text.InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. * * And finally, the {@link android.text.InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set. * * @return true if the suggestions popup window is enabled, based on the inputType. */ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public boolean isSuggestionsEnabled (){ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && mInputView.isSuggestionsEnabled(); } /** * * Returns the state of the {@code textIsSelectable} flag (See * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag * to allow users to select and copy text in a non-editable TextView, the content of an * {@link EditText} can always be selected, independently of the value of this flag. * <p> * * @return True if the text displayed in this TextView can be selected by the user. * * @attr ref android.R.styleable#TextView_textIsSelectable */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public boolean isTextSelectable (){ return Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB || mInputView.isTextSelectable(); } /** * Returns the length, in characters, of the text managed by this TextView */ public int length (){ return mInputView.length(); } /** * Move the cursor, if needed, so that it is at an offset that is visible * to the user. This will not move the cursor if it represents more than * one character (a selection range). This will only work if the * TextView contains spannable text; otherwise it will do nothing. * * @return True if the cursor was actually moved, false otherwise. */ public boolean moveCursorToVisibleOffset (){ return mInputView.moveCursorToVisibleOffset(); } /** * Called by the framework in response to a text completion from * the current input method, provided by it calling * {@link android.view.inputmethod.InputConnection#commitCompletion * InputConnection.commitCompletion()}. The default implementation does * nothing; text views that are supporting auto-completion should override * this to do their desired behavior. * * @param text The auto complete text the user has selected. */ public void onCommitCompletion (CompletionInfo text){ if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) ((InternalEditText)mInputView).superOnCommitCompletion(text); else if(mAutoCompleteMode == AUTOCOMPLETE_MODE_SINGLE) ((InternalAutoCompleteTextView)mInputView).superOnCommitCompletion(text); else ((InternalMultiAutoCompleteTextView)mInputView).superOnCommitCompletion(text); } /** * Called by the framework in response to a text auto-correction (such as fixing a typo using a * a dictionnary) from the current input method, provided by it calling * {@link android.view.inputmethod.InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default * implementation flashes the background of the corrected word to provide feedback to the user. * * @param info The auto correct info about the text that was corrected. */ public void onCommitCorrection (CorrectionInfo info){ if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) ((InternalEditText)mInputView).superOnCommitCorrection(info); else if(mAutoCompleteMode == AUTOCOMPLETE_MODE_SINGLE) ((InternalAutoCompleteTextView)mInputView).superOnCommitCorrection(info); else ((InternalMultiAutoCompleteTextView)mInputView).superOnCommitCorrection(info); } @Override public InputConnection onCreateInputConnection (EditorInfo outAttrs){ if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return ((InternalEditText)mInputView).superOnCreateInputConnection(outAttrs); else if(mAutoCompleteMode == AUTOCOMPLETE_MODE_SINGLE) return ((InternalAutoCompleteTextView)mInputView).superOnCreateInputConnection(outAttrs); else return ((InternalMultiAutoCompleteTextView)mInputView).superOnCreateInputConnection(outAttrs); } /** * Called when an attached input method calls * {@link android.view.inputmethod.InputConnection#performEditorAction(int) * InputConnection.performEditorAction()} * for this text view. The default implementation will call your action * listener supplied to {@link #setOnEditorActionListener}, or perform * a standard operation for {@link android.view.inputmethod.EditorInfo#IME_ACTION_NEXT * EditorInfo.IME_ACTION_NEXT}, {@link android.view.inputmethod.EditorInfo#IME_ACTION_PREVIOUS * EditorInfo.IME_ACTION_PREVIOUS}, or {@link android.view.inputmethod.EditorInfo#IME_ACTION_DONE * EditorInfo.IME_ACTION_DONE}. * * <p>For backwards compatibility, if no IME options have been set and the * text view would not normally advance focus on enter, then * the NEXT and DONE actions received here will be turned into an enter * key down/up pair to go through the normal key handling. * * @param actionCode The code of the action being performed. * * @see #setOnEditorActionListener */ public void onEditorAction (int actionCode){ if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) ((InternalEditText)mInputView).superOnEditorAction(actionCode); else if(mAutoCompleteMode == AUTOCOMPLETE_MODE_SINGLE) ((InternalAutoCompleteTextView)mInputView).superOnEditorAction(actionCode); else ((InternalMultiAutoCompleteTextView)mInputView).superOnEditorAction(actionCode); } @Override public boolean onKeyDown (int keyCode, KeyEvent event){ if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return ((InternalEditText)mInputView).superOnKeyDown(keyCode, event); else if(mAutoCompleteMode == AUTOCOMPLETE_MODE_SINGLE) return ((InternalAutoCompleteTextView)mInputView).superOnKeyDown(keyCode, event); else return ((InternalMultiAutoCompleteTextView)mInputView).superOnKeyDown(keyCode, event); } @Override public boolean onKeyMultiple (int keyCode, int repeatCount, KeyEvent event){ if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return ((InternalEditText)mInputView).superOnKeyMultiple(keyCode, repeatCount, event); else if(mAutoCompleteMode == AUTOCOMPLETE_MODE_SINGLE) return ((InternalAutoCompleteTextView)mInputView).superOnKeyMultiple(keyCode, repeatCount, event); else return ((InternalMultiAutoCompleteTextView)mInputView).superOnKeyMultiple(keyCode, repeatCount, event); } @Override public boolean onKeyPreIme (int keyCode, KeyEvent event){ if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return ((InternalEditText)mInputView).superOnKeyPreIme(keyCode, event); else if(mAutoCompleteMode == AUTOCOMPLETE_MODE_SINGLE) return ((InternalAutoCompleteTextView)mInputView).superOnKeyPreIme(keyCode, event); else return ((InternalMultiAutoCompleteTextView)mInputView).superOnKeyPreIme(keyCode, event); } @Override public boolean onKeyShortcut (int keyCode, KeyEvent event){ if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return ((InternalEditText)mInputView).superOnKeyShortcut(keyCode, event); else if(mAutoCompleteMode == AUTOCOMPLETE_MODE_SINGLE) return ((InternalAutoCompleteTextView)mInputView).superOnKeyShortcut(keyCode, event); else return ((InternalMultiAutoCompleteTextView)mInputView).superOnKeyShortcut(keyCode, event); } @Override public boolean onKeyUp (int keyCode, KeyEvent event){ if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) return ((InternalEditText)mInputView).superOnKeyUp(keyCode, event); else if(mAutoCompleteMode == AUTOCOMPLETE_MODE_SINGLE) return ((InternalAutoCompleteTextView)mInputView).superOnKeyUp(keyCode, event); else return ((InternalMultiAutoCompleteTextView)mInputView).superOnKeyUp(keyCode, event); } public void setOnSelectionChangedListener(TextView.OnSelectionChangedListener listener){ mOnSelectionChangedListener = listener; } /** * This method is called when the selection has changed, in case any * subclasses would like to know. * * @param selStart The new selection start location. * @param selEnd The new selection end location. */ protected void onSelectionChanged(int selStart, int selEnd) { if(mInputView == null) return; if(mAutoCompleteMode == AUTOCOMPLETE_MODE_NONE) ((InternalEditText)mInputView).superOnSelectionChanged(selStart, selEnd); else if(mAutoCompleteMode == AUTOCOMPLETE_MODE_SINGLE) ((InternalAutoCompleteTextView)mInputView).superOnSelectionChanged(selStart, selEnd); else ((InternalMultiAutoCompleteTextView)mInputView).superOnSelectionChanged(selStart, selEnd); if(mOnSelectionChangedListener != null) mOnSelectionChangedListener.onSelectionChanged(this, selStart, selEnd); } /** * Removes the specified TextWatcher from the list of those whose * methods are called * whenever this TextView's text changes. */ public void removeTextChangedListener (TextWatcher watcher){ mInputView.removeTextChangedListener(watcher); } /** * Sets the properties of this field to transform input to ALL CAPS * display. This may use a "small caps" formatting if available. * This setting will be ignored if this field is editable or selectable. * * This call replaces the current transformation method. Disabling this * will not necessarily restore the previous behavior from before this * was enabled. * * @see #setTransformationMethod(android.text.method.TransformationMethod) * @attr ref android.R.styleable#TextView_textAllCaps */ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public void setAllCaps (boolean allCaps){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) mInputView.setAllCaps(allCaps); } /** * Sets the autolink mask of the text. See {@link * android.text.util.Linkify#ALL Linkify.ALL} and peers for * possible values. * * @attr ref android.R.styleable#TextView_autoLink */ public final void setAutoLinkMask (int mask){ mInputView.setAutoLinkMask(mask); } /** * Sets the size of the padding between the compound drawables and * the text. * * @attr ref android.R.styleable#TextView_drawablePadding */ public void setCompoundDrawablePadding (int pad){ mInputView.setCompoundDrawablePadding(pad); if(mDividerCompoundPadding) { mDivider.setPadding(mInputView.getTotalPaddingLeft(), mInputView.getTotalPaddingRight()); if(mLabelEnable) mLabelView.setPadding(mDivider.getPaddingLeft(), mLabelView.getPaddingTop(), mDivider.getPaddingRight(), mLabelView.getPaddingBottom()); if(mSupportMode != SUPPORT_MODE_NONE) mSupportView.setPadding(mDivider.getPaddingLeft(), mSupportView.getPaddingTop(), mDivider.getPaddingRight(), mSupportView.getPaddingBottom()); } } /** * Sets the Drawables (if any) to appear to the left of, above, to the * right of, and below the text. Use {@code null} if you do not want a * Drawable there. The Drawables must already have had * {@link android.graphics.drawable.Drawable#setBounds} called. * <p> * Calling this method will overwrite any Drawables previously set using * {@link #setCompoundDrawablesRelative} or related methods. * * @attr ref android.R.styleable#TextView_drawableLeft * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableRight * @attr ref android.R.styleable#TextView_drawableBottom */ public void setCompoundDrawables (Drawable left, Drawable top, Drawable right, Drawable bottom){ mInputView.setCompoundDrawables(left, top, right, bottom); if(mDividerCompoundPadding) { mDivider.setPadding(mInputView.getTotalPaddingLeft(), mInputView.getTotalPaddingRight()); if(mLabelEnable) mLabelView.setPadding(mDivider.getPaddingLeft(), mLabelView.getPaddingTop(), mDivider.getPaddingRight(), mLabelView.getPaddingBottom()); if(mSupportMode != SUPPORT_MODE_NONE) mSupportView.setPadding(mDivider.getPaddingLeft(), mSupportView.getPaddingTop(), mDivider.getPaddingRight(), mSupportView.getPaddingBottom()); } } /** * Sets the Drawables (if any) to appear to the start of, above, to the end * of, and below the text. Use {@code null} if you do not want a Drawable * there. The Drawables must already have had {@link android.graphics.drawable.Drawable#setBounds} * called. * <p> * Calling this method will overwrite any Drawables previously set using * {@link #setCompoundDrawables} or related methods. * * @attr ref android.R.styleable#TextView_drawableStart * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableEnd * @attr ref android.R.styleable#TextView_drawableBottom */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public void setCompoundDrawablesRelative (Drawable start, Drawable top, Drawable end, Drawable bottom){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) mInputView.setCompoundDrawablesRelative(start, top, end, bottom); else mInputView.setCompoundDrawables(start, top, end, bottom); } /** * Sets the Drawables (if any) to appear to the start of, above, to the end * of, and below the text. Use {@code null} if you do not want a Drawable * there. The Drawables' bounds will be set to their intrinsic bounds. * <p> * Calling this method will overwrite any Drawables previously set using * {@link #setCompoundDrawables} or related methods. * * @attr ref android.R.styleable#TextView_drawableStart * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableEnd * @attr ref android.R.styleable#TextView_drawableBottom */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public void setCompoundDrawablesRelativeWithIntrinsicBounds (Drawable start, Drawable top, Drawable end, Drawable bottom){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) mInputView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom); else mInputView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom); } /** * Sets the Drawables (if any) to appear to the start of, above, to the end * of, and below the text. Use 0 if you do not want a Drawable there. The * Drawables' bounds will be set to their intrinsic bounds. * <p> * Calling this method will overwrite any Drawables previously set using * {@link #setCompoundDrawables} or related methods. * * @param start Resource identifier of the start Drawable. * @param top Resource identifier of the top Drawable. * @param end Resource identifier of the end Drawable. * @param bottom Resource identifier of the bottom Drawable. * * @attr ref android.R.styleable#TextView_drawableStart * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableEnd * @attr ref android.R.styleable#TextView_drawableBottom */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public void setCompoundDrawablesRelativeWithIntrinsicBounds (int start, int top, int end, int bottom){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) mInputView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom); else mInputView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom); } /** * Sets the Drawables (if any) to appear to the left of, above, to the * right of, and below the text. Use {@code null} if you do not want a * Drawable there. The Drawables' bounds will be set to their intrinsic * bounds. * <p> * Calling this method will overwrite any Drawables previously set using * {@link #setCompoundDrawablesRelative} or related methods. * * @attr ref android.R.styleable#TextView_drawableLeft * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableRight * @attr ref android.R.styleable#TextView_drawableBottom */ public void setCompoundDrawablesWithIntrinsicBounds (Drawable left, Drawable top, Drawable right, Drawable bottom){ mInputView.setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom); } /** * Sets the Drawables (if any) to appear to the left of, above, to the * right of, and below the text. Use 0 if you do not want a Drawable there. * The Drawables' bounds will be set to their intrinsic bounds. * <p> * Calling this method will overwrite any Drawables previously set using * {@link #setCompoundDrawablesRelative} or related methods. * * @param left Resource identifier of the left Drawable. * @param top Resource identifier of the top Drawable. * @param right Resource identifier of the right Drawable. * @param bottom Resource identifier of the bottom Drawable. * * @attr ref android.R.styleable#TextView_drawableLeft * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableRight * @attr ref android.R.styleable#TextView_drawableBottom */ public void setCompoundDrawablesWithIntrinsicBounds (int left, int top, int right, int bottom){ mInputView.setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom); } /** * Set whether the cursor is visible. The default is true. Note that this property only * makes sense for editable TextView. * * @see #isCursorVisible() * * @attr ref android.R.styleable#TextView_cursorVisible */ public void setCursorVisible (boolean visible){ mInputView.setCursorVisible(visible); } /** * If provided, this ActionMode.Callback will be used to create the ActionMode when text * selection is initiated in this View. * * The standard implementation populates the menu with a subset of Select All, Cut, Copy and * Paste actions, depending on what this View supports. * * A custom implementation can add new entries in the default menu in its * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode, android.view.Menu)} method. The * default actions can also be removed from the menu using {@link android.view.Menu#removeItem(int)} and * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy} * or {@link android.R.id#paste} ids as parameters. * * Returning false from * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode, android.view.Menu)} will prevent * the action mode from being started. * * Action click events should be handled by the custom implementation of * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode, android.view.MenuItem)}. * * Note that text selection mode is not started when a TextView receives focus and the * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in * that case, to allow for quick replacement. */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public void setCustomSelectionActionModeCallback (ActionMode.Callback actionModeCallback){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) mInputView.setCustomSelectionActionModeCallback(actionModeCallback); } /** * Sets the Factory used to create new Editables. */ public final void setEditableFactory (Editable.Factory factory){ mInputView.setEditableFactory(factory); } /** * Set the TextView's elegant height metrics flag. This setting selects font * variants that have not been compacted to fit Latin-based vertical * metrics, and also increases top and bottom bounds to provide more space. * * @param elegant set the paint's elegant metrics flag. * * @attr ref android.R.styleable#TextView_elegantTextHeight */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public void setElegantTextHeight (boolean elegant){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) mInputView.setElegantTextHeight(elegant); } /** * Makes the TextView exactly this many ems wide * * @see #setMaxEms(int) * @see #setMinEms(int) * @see #getMinEms() * @see #getMaxEms() * * @attr ref android.R.styleable#TextView_ems */ public void setEms (int ems){ mInputView.setEms(ems); } /** * Apply to this text view the given extracted text, as previously * returned by {@link #extractText(android.view.inputmethod.ExtractedTextRequest, android.view.inputmethod.ExtractedText)}. */ public void setExtractedText (ExtractedText text){ mInputView.setExtractedText(text); } /** * Sets the list of input filters that will be used if the buffer is * Editable. Has no effect otherwise. * * @attr ref android.R.styleable#TextView_maxLength */ public void setFilters (InputFilter[] filters){ mInputView.setFilters(filters); } /** * Sets font feature settings. The format is the same as the CSS * font-feature-settings attribute: * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings * * @param fontFeatureSettings font feature settings represented as CSS compatible string * @see #getFontFeatureSettings() * @see android.graphics.Paint#getFontFeatureSettings * * @attr ref android.R.styleable#TextView_fontFeatureSettings */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public void setFontFeatureSettings (String fontFeatureSettings){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) mInputView.setFontFeatureSettings(fontFeatureSettings); } /** * Control whether this text view saves its entire text contents when * freezing to an icicle, in addition to dynamic state such as cursor * position. By default this is false, not saving the text. Set to true * if the text in the text view is not being saved somewhere else in * persistent storage (such as in a content provider) so that if the * view is later thawed the user will not lose their data. * * @param freezesText Controls whether a frozen icicle should include the * entire text data: true to include it, false to not. * * @attr ref android.R.styleable#TextView_freezesText */ public void setFreezesText (boolean freezesText){ mInputView.setFreezesText(freezesText); } /** * Sets the horizontal alignment of the text and the * vertical gravity that will be used when there is extra space * in the TextView beyond what is required for the text itself. * * @see android.view.Gravity * @attr ref android.R.styleable#TextView_gravity */ public void setGravity (int gravity){ mInputView.setGravity(gravity); } /** * Sets the color used to display the selection highlight. * * @attr ref android.R.styleable#TextView_textColorHighlight */ public void setHighlightColor (int color){ mInputView.setHighlightColor(color); } /** * Sets the text to be displayed when the text of the TextView is empty. * Null means to use the normal empty text. The hint does not currently * participate in determining the size of the view. * * @attr ref android.R.styleable#TextView_hint */ public final void setHint (CharSequence hint){ mInputView.setHint(hint); if(mLabelView != null) mLabelView.setText(hint); } /** * Sets the text to be displayed when the text of the TextView is empty, * from a resource. * * @attr ref android.R.styleable#TextView_hint */ public final void setHint (int resid){ mInputView.setHint(resid); if(mLabelView != null) mLabelView.setText(resid); } /** * Sets the color of the hint text. * * @see #getHintTextColors() * @see #setHintTextColor(int) * @see #setTextColor(android.content.res.ColorStateList) * @see #setLinkTextColor(android.content.res.ColorStateList) * * @attr ref android.R.styleable#TextView_textColorHint */ public final void setHintTextColor (ColorStateList colors){ mInputView.setHintTextColor(colors); } /** * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this * TextView. * * @see #setHintTextColor(android.content.res.ColorStateList) * @see #getHintTextColors() * @see #setTextColor(int) * * @attr ref android.R.styleable#TextView_textColorHint */ public final void setHintTextColor (int color){ mInputView.setHintTextColor(color); } /** * Sets whether the text should be allowed to be wider than the * View is. If false, it will be wrapped to the width of the View. * * @attr ref android.R.styleable#TextView_scrollHorizontally */ public void setHorizontallyScrolling (boolean whether){ mInputView.setHorizontallyScrolling(whether); } /** * Change the custom IME action associated with the text view, which * will be reported to an IME with {@link android.view.inputmethod.EditorInfo#actionLabel} * and {@link android.view.inputmethod.EditorInfo#actionId} when it has focus. * @see #getImeActionLabel * @see #getImeActionId * @see android.view.inputmethod.EditorInfo * @attr ref android.R.styleable#TextView_imeActionLabel * @attr ref android.R.styleable#TextView_imeActionId */ public void setImeActionLabel (CharSequence label, int actionId){ mInputView.setImeActionLabel(label, actionId); } /** * Change the editor type integer associated with the text view, which * will be reported to an IME with {@link android.view.inputmethod.EditorInfo#imeOptions} when it * has focus. * @see #getImeOptions * @see android.view.inputmethod.EditorInfo * @attr ref android.R.styleable#TextView_imeOptions */ public void setImeOptions (int imeOptions){ mInputView.setImeOptions(imeOptions); } /** * Set whether the TextView includes extra top and bottom padding to make * room for accents that go above the normal ascent and descent. * The default is true. * * @see #getIncludeFontPadding() * * @attr ref android.R.styleable#TextView_includeFontPadding */ public void setIncludeFontPadding (boolean includepad){ mInputView.setIncludeFontPadding(includepad); } /** * Set the extra input data of the text, which is the * {@link android.view.inputmethod.EditorInfo#extras TextBoxAttribute.extras} * Bundle that will be filled in when creating an input connection. The * given integer is the resource ID of an XML resource holding an * {@link android.R.styleable#InputExtras <input-extras>} XML tree. * * @see #getInputExtras(boolean) * @see android.view.inputmethod.EditorInfo#extras * @attr ref android.R.styleable#TextView_editorExtras */ public void setInputExtras (int xmlResId) throws XmlPullParserException, IOException{ mInputView.setInputExtras(xmlResId); } /** * Set the type of the content with a constant as defined for {@link android.view.inputmethod.EditorInfo#inputType}. This * will take care of changing the key listener, by calling {@link #setKeyListener(android.text.method.KeyListener)}, * to match the given content type. If the given content type is {@link android.view.inputmethod.EditorInfo#TYPE_NULL} * then a soft keyboard will not be displayed for this text view. * * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be * modified if you change the {@link android.view.inputmethod.EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input * type. * * @see #getInputType() * @see #setRawInputType(int) * @see android.text.InputType * @attr ref android.R.styleable#TextView_inputType */ public void setInputType (int type){ mInputView.setInputType(type); } /** * Sets the key listener to be used with this TextView. This can be null * to disallow user input. Note that this method has significant and * subtle interactions with soft keyboards and other input method: * see {@link android.text.method.KeyListener#getInputType() KeyListener.getContentType()} * for important details. Calling this method will replace the current * content type of the text view with the content type returned by the * key listener. * <p> * Be warned that if you want a TextView with a key listener or movement * method not to be focusable, or if you want a TextView without a * key listener or movement method to be focusable, you must call * {@link #setFocusable} again after calling this to get the focusability * back the way you want it. * * @attr ref android.R.styleable#TextView_numeric * @attr ref android.R.styleable#TextView_digits * @attr ref android.R.styleable#TextView_phoneNumber * @attr ref android.R.styleable#TextView_inputMethod * @attr ref android.R.styleable#TextView_capitalize * @attr ref android.R.styleable#TextView_autoText */ public void setKeyListener (KeyListener input){ mInputView.setKeyListener(input); } /** * Sets text letter-spacing. The value is in 'EM' units. Typical values * for slight expansion will be around 0.05. Negative values tighten text. * * @see #getLetterSpacing() * @see android.graphics.Paint#getLetterSpacing * * @attr ref android.R.styleable#TextView_letterSpacing */ public void setLetterSpacing (float letterSpacing){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) mInputView.setLetterSpacing(letterSpacing); } /** * Sets line spacing for this TextView. Each line will have its height * multiplied by <code>mult</code> and have <code>add</code> added to it. * * @attr ref android.R.styleable#TextView_lineSpacingExtra * @attr ref android.R.styleable#TextView_lineSpacingMultiplier */ public void setLineSpacing (float add, float mult){ mInputView.setLineSpacing(add, mult); } /** * Makes the TextView exactly this many lines tall. * * Note that setting this value overrides any other (minimum / maximum) number of lines or * height setting. A single line TextView will set this value to 1. * * @attr ref android.R.styleable#TextView_lines */ public void setLines (int lines){ mInputView.setLines(lines); } /** * Sets the color of links in the text. * * @see #setLinkTextColor(int) * @see #getLinkTextColors() * @see #setTextColor(android.content.res.ColorStateList) * @see #setHintTextColor(android.content.res.ColorStateList) * * @attr ref android.R.styleable#TextView_textColorLink */ public final void setLinkTextColor (ColorStateList colors){ mInputView.setLinkTextColor(colors); } /** * Sets the color of links in the text. * * @see #setLinkTextColor(int) * @see #getLinkTextColors() * @see #setTextColor(android.content.res.ColorStateList) * @see #setHintTextColor(android.content.res.ColorStateList) * * @attr ref android.R.styleable#TextView_textColorLink */ public final void setLinkTextColor (int color){ mInputView.setLinkTextColor(color); } /** * Sets whether the movement method will automatically be set to * {@link android.text.method.LinkMovementMethod} if {@link #setAutoLinkMask} has been * set to nonzero and links are detected in {@link #setText}. * The default is true. * * @attr ref android.R.styleable#TextView_linksClickable */ public final void setLinksClickable (boolean whether){ mInputView.setLinksClickable(whether); } /** * Sets how many times to repeat the marquee animation. Only applied if the * TextView has marquee enabled. Set to -1 to repeat indefinitely. * * @see #getMarqueeRepeatLimit() * * @attr ref android.R.styleable#TextView_marqueeRepeatLimit */ public void setMarqueeRepeatLimit (int marqueeLimit){ mInputView.setMarqueeRepeatLimit(marqueeLimit); } /** * Makes the TextView at most this many ems wide * * @attr ref android.R.styleable#TextView_maxEms */ public void setMaxEms (int maxems){ mInputView.setMaxEms(maxems); } /** * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the * {@link #setMaxLines(int)} method. * * Setting this value overrides any other (maximum) number of lines setting. * * @attr ref android.R.styleable#TextView_maxHeight */ public void setMaxHeight (int maxHeight){ mInputView.setMaxHeight(maxHeight); } /** * Makes the TextView at most this many lines tall. * * Setting this value overrides any other (maximum) height setting. * * @attr ref android.R.styleable#TextView_maxLines */ public void setMaxLines (int maxlines){ mInputView.setMaxLines(maxlines); } /** * Makes the TextView at most this many pixels wide * * @attr ref android.R.styleable#TextView_maxWidth */ public void setMaxWidth (int maxpixels){ mInputView.setMaxWidth(maxpixels); } /** * Makes the TextView at least this many ems wide * * @attr ref android.R.styleable#TextView_minEms */ public void setMinEms (int minems){ mInputView.setMinEms(minems); } /** * Makes the TextView at least this many pixels tall. * * Setting this value overrides any other (minimum) number of lines setting. * * @attr ref android.R.styleable#TextView_minHeight */ public void setMinHeight (int minHeight){ mInputView.setMinHeight(minHeight); } /** * Makes the TextView at least this many lines tall. * * Setting this value overrides any other (minimum) height setting. A single line TextView will * set this value to 1. * * @see #getMinLines() * * @attr ref android.R.styleable#TextView_minLines */ public void setMinLines (int minlines){ mInputView.setMinLines(minlines); } /** * Makes the TextView at least this many pixels wide * * @attr ref android.R.styleable#TextView_minWidth */ public void setMinWidth (int minpixels){ mInputView.setMinWidth(minpixels); } /** * Sets the movement method (arrow key handler) to be used for * this TextView. This can be null to disallow using the arrow keys * to move the cursor or scroll the view. * <p> * Be warned that if you want a TextView with a key listener or movement * method not to be focusable, or if you want a TextView without a * key listener or movement method to be focusable, you must call * {@link #setFocusable} again after calling this to get the focusability * back the way you want it. */ public final void setMovementMethod (MovementMethod movement){ mInputView.setMovementMethod(movement); } /** * Set a special listener to be called when an action is performed * on the text view. This will be called when the enter key is pressed, * or when an action supplied to the IME is selected by the user. Setting * this means that the normal hard key event will not insert a newline * into the text view, even if it is multi-line; holding down the ALT * modifier will, however, allow the user to insert a newline character. */ public void setOnEditorActionListener (android.widget.TextView.OnEditorActionListener l){ mInputView.setOnEditorActionListener(l); } /** * Register a callback to be invoked when a hardware key is pressed in this view. * Key presses in software input methods will generally not trigger the methods of * this listener. * @param l the key listener to attach to this view */ @Override public void setOnKeyListener(OnKeyListener l) { mInputView.setOnKeyListener(l); } /** * Register a callback to be invoked when focus of this view changed. * * @param l The callback that will run. */ @Override public void setOnFocusChangeListener(OnFocusChangeListener l) { mInputView.setOnFocusChangeListener(l); } /** * Directly change the content type integer of the text view, without * modifying any other state. * @see #setInputType(int) * @see android.text.InputType * @attr ref android.R.styleable#TextView_inputType */ public void setRawInputType (int type){ mInputView.setRawInputType(type); } public void setScroller (Scroller s){ mInputView.setScroller(s); } /** * Set the TextView so that when it takes focus, all the text is * selected. * * @attr ref android.R.styleable#TextView_selectAllOnFocus */ public void setSelectAllOnFocus (boolean selectAllOnFocus){ mInputView.setSelectAllOnFocus(selectAllOnFocus); } @Override public void setSelected (boolean selected){ mInputView.setSelected(selected); } /** * Gives the text a shadow of the specified blur radius and color, the specified * distance from its drawn position. * <p> * The text shadow produced does not interact with the properties on view * that are responsible for real time shadows, * {@link android.view.View#getElevation() elevation} and * {@link android.view.View#getTranslationZ() translationZ}. * * @see android.graphics.Paint#setShadowLayer(float, float, float, int) * * @attr ref android.R.styleable#TextView_shadowColor * @attr ref android.R.styleable#TextView_shadowDx * @attr ref android.R.styleable#TextView_shadowDy * @attr ref android.R.styleable#TextView_shadowRadius */ public void setShadowLayer (float radius, float dx, float dy, int color){ mInputView.setShadowLayer(radius, dx, dy, color); } /** * Sets whether the soft input method will be made visible when this * TextView gets focused. The default is true. */ public final void setShowSoftInputOnFocus (boolean show){ mInputView.setShowSoftInputOnFocus(show); } /** * Sets the properties of this field (lines, horizontally scrolling, * transformation method) to be for a single-line input. * * @attr ref android.R.styleable#TextView_singleLine */ public void setSingleLine (){ mInputView.setSingleLine(); } /** * Sets the Factory used to create new Spannables. */ public final void setSpannableFactory (Spannable.Factory factory){ mInputView.setSpannableFactory(factory); } public final void setText (int resid){ mInputView.setText(resid); } public final void setText (char[] text, int start, int len){ mInputView.setText(text, start, len); } public final void setText (int resid, android.widget.TextView.BufferType type){ mInputView.setText(resid, type); } public final void setText (CharSequence text){ mInputView.setText(text); } /** * Sets the text color, size, style, hint color, and highlight color * from the specified TextAppearance resource. */ public void setTextAppearance (Context context, int resid){ mInputView.setTextAppearance(context, resid); } /** * Sets the text color. * * @see #setTextColor(int) * @see #getTextColors() * @see #setHintTextColor(android.content.res.ColorStateList) * @see #setLinkTextColor(android.content.res.ColorStateList) * * @attr ref android.R.styleable#TextView_textColor */ public void setTextColor (ColorStateList colors){ mInputView.setTextColor(colors); } /** * Sets the text color for all the states (normal, selected, * focused) to be this color. * * @see #setTextColor(android.content.res.ColorStateList) * @see #getTextColors() * * @attr ref android.R.styleable#TextView_textColor */ public void setTextColor (int color){ mInputView.setTextColor(color); } /** * Sets whether the content of this view is selectable by the user. The default is * {@code false}, meaning that the content is not selectable. * <p> * When you use a TextView to display a useful piece of information to the user (such as a * contact's address), make it selectable, so that the user can select and copy its * content. You can also use set the XML attribute * {@link android.R.styleable#TextView_textIsSelectable} to "true". * <p> * When you call this method to set the value of {@code textIsSelectable}, it sets * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable}, * and {@code longClickable} to the same value. These flags correspond to the attributes * {@link android.R.styleable#View_focusable android:focusable}, * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode}, * {@link android.R.styleable#View_clickable android:clickable}, and * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these * flags to a state you had set previously, call one or more of the following methods: * {@link #setFocusable(boolean) setFocusable()}, * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()}, * {@link #setClickable(boolean) setClickable()} or * {@link #setLongClickable(boolean) setLongClickable()}. * * @param selectable Whether the content of this TextView should be selectable. */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public void setTextIsSelectable (boolean selectable){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) mInputView.setTextIsSelectable(selectable); } /** * Like {@link #setText(CharSequence)}, * except that the cursor position (if any) is retained in the new text. * * @param text The new text to place in the text view. * * @see #setText(CharSequence) */ public final void setTextKeepState (CharSequence text){ mInputView.setTextKeepState(text); } /** * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)}, * except that the cursor position (if any) is retained in the new text. * * @see #setText(CharSequence, android.widget.TextView.BufferType) */ public final void setTextKeepState (CharSequence text, android.widget.TextView.BufferType type){ mInputView.setTextKeepState(text, type); } /** * Set the default {@link java.util.Locale} of the text in this TextView to the given value. This value * is used to choose appropriate typefaces for ambiguous characters. Typically used for CJK * locales to disambiguate Hanzi/Kanji/Hanja characters. * * @param locale the {@link java.util.Locale} for drawing text, must not be null. * * @see android.graphics.Paint#setTextLocale */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public void setTextLocale (Locale locale){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) mInputView.setTextLocale(locale); } /** * Sets the extent by which text should be stretched horizontally. * * @attr ref android.R.styleable#TextView_textScaleX */ public void setTextScaleX (float size){ mInputView.setTextScaleX(size); } /** * Set the default text size to the given value, interpreted as "scaled * pixel" units. This size is adjusted based on the current density and * user font size preference. * * @param size The scaled pixel size. * * @attr ref android.R.styleable#TextView_textSize */ public void setTextSize (float size){ mInputView.setTextSize(size); } /** * Set the default text size to a given unit and value. See {@link * android.util.TypedValue} for the possible dimension units. * * @param unit The desired dimension unit. * @param size The desired size in the given units. * * @attr ref android.R.styleable#TextView_textSize */ public void setTextSize (int unit, float size){ mInputView.setTextSize(unit, size); } /** * Sets the transformation that is applied to the text that this * TextView is displaying. * * @attr ref android.R.styleable#TextView_password * @attr ref android.R.styleable#TextView_singleLine */ public final void setTransformationMethod (TransformationMethod method){ mInputView.setTransformationMethod(method); } /** * Sets the typeface and style in which the text should be displayed, * and turns on the fake bold and italic bits in the Paint if the * Typeface that you provided does not have all the bits in the * style that you specified. * * @attr ref android.R.styleable#TextView_typeface * @attr ref android.R.styleable#TextView_textStyle */ public void setTypeface (Typeface tf, int style){ mInputView.setTypeface(tf, style); } /** * Sets the typeface and style in which the text should be displayed. * Note that not all Typeface families actually have bold and italic * variants, so you may need to use * {@link #setTypeface(android.graphics.Typeface, int)} to get the appearance * that you actually want. * * @see #getTypeface() * * @attr ref android.R.styleable#TextView_fontFamily * @attr ref android.R.styleable#TextView_typeface * @attr ref android.R.styleable#TextView_textStyle */ public void setTypeface (Typeface tf){ mInputView.setTypeface(tf); } /** * It would be better to rely on the input type for everything. A password inputType should have * a password transformation. We should hence use isPasswordInputType instead of this method. * * We should: * - Call setInputType in setKeyListener instead of changing the input type directly (which * would install the correct transformation). * - Refuse the installation of a non-password transformation in setTransformation if the input * type is password. * * However, this is like this for legacy reasons and we cannot break existing apps. This method * is useful since it matches what the user can see (obfuscated text or not). * * @return true if the current transformation method is of the password type. */ private boolean hasPasswordTransformationMethod() { return getTransformationMethod() != null && getTransformationMethod() instanceof PasswordTransformationMethod; } public boolean canCut() { return !hasPasswordTransformationMethod() && getText().length() > 0 && hasSelection() && getKeyListener() != null; } public boolean canCopy() { return !hasPasswordTransformationMethod() && getText().length() > 0 && hasSelection(); } public boolean canPaste() { return (getKeyListener() != null && getSelectionStart() >= 0 && getSelectionEnd() >= 0 && ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).hasPrimaryClip()); } /* Inner class */ private class InputTextWatcher implements TextWatcher { @Override public void afterTextChanged(Editable s) { if(!mLabelEnable) return; int count = s.length(); if(count == 0){ if(mLabelView.getVisibility() == View.VISIBLE){ if(mLabelOutAnimId > 0){ Animation anim = AnimationUtils.loadAnimation(getContext(), mLabelOutAnimId); anim.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) {} @Override public void onAnimationRepeat(Animation animation) {} @Override public void onAnimationEnd(Animation animation) { mLabelView.setVisibility(View.INVISIBLE); } }); mLabelView.startAnimation(anim); } else mLabelView.setVisibility(View.INVISIBLE); } if(mSupportMode == SUPPORT_MODE_CHAR_COUNTER) updateCharCounter(count); } else{ if(mLabelView.getVisibility() == View.INVISIBLE){ if(mLabelInAnimId > 0){ Animation anim = AnimationUtils.loadAnimation(getContext(), mLabelInAnimId); anim.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { mLabelView.setVisibility(View.VISIBLE); } @Override public void onAnimationRepeat(Animation animation) {} @Override public void onAnimationEnd(Animation animation) {} }); mLabelView.startAnimation(anim); } else mLabelView.setVisibility(View.VISIBLE); } if(mSupportMode == SUPPORT_MODE_CHAR_COUNTER) updateCharCounter(count); } } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) {} } private class LabelView extends android.widget.TextView{ public LabelView(Context context) { super(context); } @Override protected int[] onCreateDrawableState(int extraSpace) { return mInputView.getDrawableState(); } } private class InternalEditText extends android.widget.EditText{ public InternalEditText(Context context) { super(context); } public InternalEditText(Context context, AttributeSet attrs) { super(context, attrs); } public InternalEditText(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public void refreshDrawableState() { super.refreshDrawableState(); if(mLabelView != null) mLabelView.refreshDrawableState(); if(mSupportView != null) mSupportView.refreshDrawableState(); } @Override public void onCommitCompletion(CompletionInfo text) { EditText.this.onCommitCompletion(text); } @Override public void onCommitCorrection(CorrectionInfo info) { EditText.this.onCommitCorrection(info); } @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { return EditText.this.onCreateInputConnection(outAttrs); } @Override public void onEditorAction(int actionCode) { EditText.this.onEditorAction(actionCode); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return EditText.this.onKeyDown(keyCode, event); } @Override public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { return EditText.this.onKeyMultiple(keyCode, repeatCount, event); } @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { return EditText.this.onKeyPreIme(keyCode, event); } @Override public boolean onKeyShortcut(int keyCode, KeyEvent event) { return EditText.this.onKeyShortcut(keyCode, event); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return EditText.this.onKeyUp(keyCode, event); } @Override protected void onSelectionChanged(int selStart, int selEnd) { EditText.this.onSelectionChanged(selStart, selEnd); } void superOnCommitCompletion(CompletionInfo text) { super.onCommitCompletion(text); } void superOnCommitCorrection(CorrectionInfo info) { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) super.onCommitCorrection(info); } InputConnection superOnCreateInputConnection(EditorInfo outAttrs) { return super.onCreateInputConnection(outAttrs); } void superOnEditorAction(int actionCode) { super.onEditorAction(actionCode); } boolean superOnKeyDown(int keyCode, KeyEvent event) { return super.onKeyDown(keyCode, event); } boolean superOnKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { return super.onKeyMultiple(keyCode, repeatCount, event); } boolean superOnKeyPreIme(int keyCode, KeyEvent event) { return super.onKeyPreIme(keyCode, event); } boolean superOnKeyShortcut(int keyCode, KeyEvent event) { return super.onKeyShortcut(keyCode, event); } boolean superOnKeyUp(int keyCode, KeyEvent event) { return super.onKeyUp(keyCode, event); } void superOnSelectionChanged(int selStart, int selEnd) { super.onSelectionChanged(selStart, selEnd); } } private class InternalAutoCompleteTextView extends android.widget.AutoCompleteTextView{ public InternalAutoCompleteTextView(Context context) { super(context); } public InternalAutoCompleteTextView(Context context, AttributeSet attrs) { super(context, attrs); } public InternalAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public void refreshDrawableState() { super.refreshDrawableState(); if(mLabelView != null) mLabelView.refreshDrawableState(); if(mSupportView != null) mSupportView.refreshDrawableState(); } @Override public void onCommitCompletion(CompletionInfo text) { EditText.this.onCommitCompletion(text); } @Override public void onCommitCorrection(CorrectionInfo info) { EditText.this.onCommitCorrection(info); } @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { return EditText.this.onCreateInputConnection(outAttrs); } @Override public void onEditorAction(int actionCode) { EditText.this.onEditorAction(actionCode); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return EditText.this.onKeyDown(keyCode, event); } @Override public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { return EditText.this.onKeyMultiple(keyCode, repeatCount, event); } @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { return EditText.this.onKeyPreIme(keyCode, event); } @Override public boolean onKeyShortcut(int keyCode, KeyEvent event) { return EditText.this.onKeyShortcut(keyCode, event); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return EditText.this.onKeyUp(keyCode, event); } @Override protected void onSelectionChanged(int selStart, int selEnd) { EditText.this.onSelectionChanged(selStart, selEnd); } @Override protected CharSequence convertSelectionToString(Object selectedItem) { return EditText.this.convertSelectionToString(selectedItem); } @Override protected void performFiltering(CharSequence text, int keyCode) { EditText.this.performFiltering(text, keyCode); } @Override protected void replaceText(CharSequence text) { EditText.this.replaceText(text); } @Override protected Filter getFilter() { return EditText.this.getFilter(); } @Override public void onFilterComplete(int count) { EditText.this.onFilterComplete(count); } void superOnCommitCompletion(CompletionInfo text) { super.onCommitCompletion(text); } void superOnCommitCorrection(CorrectionInfo info) { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) super.onCommitCorrection(info); } InputConnection superOnCreateInputConnection(EditorInfo outAttrs) { return super.onCreateInputConnection(outAttrs); } void superOnEditorAction(int actionCode) { super.onEditorAction(actionCode); } boolean superOnKeyDown(int keyCode, KeyEvent event) { return super.onKeyDown(keyCode, event); } boolean superOnKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { return super.onKeyMultiple(keyCode, repeatCount, event); } boolean superOnKeyPreIme(int keyCode, KeyEvent event) { return super.onKeyPreIme(keyCode, event); } boolean superOnKeyShortcut(int keyCode, KeyEvent event) { return super.onKeyShortcut(keyCode, event); } boolean superOnKeyUp(int keyCode, KeyEvent event) { return super.onKeyUp(keyCode, event); } void superOnFilterComplete(int count) { super.onFilterComplete(count); } CharSequence superConvertSelectionToString(Object selectedItem) { return super.convertSelectionToString(selectedItem); } void superPerformFiltering(CharSequence text, int keyCode) { super.performFiltering(text, keyCode); } void superReplaceText(CharSequence text) { super.replaceText(text); } Filter superGetFilter() { return super.getFilter(); } void superOnSelectionChanged(int selStart, int selEnd) { super.onSelectionChanged(selStart, selEnd); } } private class InternalMultiAutoCompleteTextView extends android.widget.MultiAutoCompleteTextView{ public InternalMultiAutoCompleteTextView(Context context) { super(context); } public InternalMultiAutoCompleteTextView(Context context, AttributeSet attrs) { super(context, attrs); } public InternalMultiAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public void refreshDrawableState() { super.refreshDrawableState(); if(mLabelView != null) mLabelView.refreshDrawableState(); if(mSupportView != null) mSupportView.refreshDrawableState(); } @Override public void onCommitCompletion(CompletionInfo text) { EditText.this.onCommitCompletion(text); } @Override public void onCommitCorrection(CorrectionInfo info) { EditText.this.onCommitCorrection(info); } @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { return EditText.this.onCreateInputConnection(outAttrs); } @Override public void onEditorAction(int actionCode) { EditText.this.onEditorAction(actionCode); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return EditText.this.onKeyDown(keyCode, event); } @Override public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { return EditText.this.onKeyMultiple(keyCode, repeatCount, event); } @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { return EditText.this.onKeyPreIme(keyCode, event); } @Override public boolean onKeyShortcut(int keyCode, KeyEvent event) { return EditText.this.onKeyShortcut(keyCode, event); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return EditText.this.onKeyUp(keyCode, event); } @Override protected void onSelectionChanged(int selStart, int selEnd) { EditText.this.onSelectionChanged(selStart, selEnd); } @Override public void onFilterComplete(int count) { EditText.this.onFilterComplete(count); } @Override protected CharSequence convertSelectionToString(Object selectedItem) { return EditText.this.convertSelectionToString(selectedItem); } @Override protected void performFiltering(CharSequence text, int keyCode) { EditText.this.performFiltering(text, keyCode); } @Override protected void replaceText(CharSequence text) { EditText.this.replaceText(text); } @Override protected Filter getFilter() { return EditText.this.getFilter(); } @Override protected void performFiltering(CharSequence text, int start, int end, int keyCode){ EditText.this.performFiltering(text, start, end, keyCode); } void superOnCommitCompletion(CompletionInfo text) { super.onCommitCompletion(text); } void superOnCommitCorrection(CorrectionInfo info) { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) super.onCommitCorrection(info); } InputConnection superOnCreateInputConnection(EditorInfo outAttrs) { return super.onCreateInputConnection(outAttrs); } void superOnEditorAction(int actionCode) { super.onEditorAction(actionCode); } boolean superOnKeyDown(int keyCode, KeyEvent event) { return super.onKeyDown(keyCode, event); } boolean superOnKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { return super.onKeyMultiple(keyCode, repeatCount, event); } boolean superOnKeyPreIme(int keyCode, KeyEvent event) { return super.onKeyPreIme(keyCode, event); } boolean superOnKeyShortcut(int keyCode, KeyEvent event) { return super.onKeyShortcut(keyCode, event); } boolean superOnKeyUp(int keyCode, KeyEvent event) { return super.onKeyUp(keyCode, event); } void superOnFilterComplete(int count) { super.onFilterComplete(count); } CharSequence superConvertSelectionToString(Object selectedItem) { return super.convertSelectionToString(selectedItem); } void superPerformFiltering(CharSequence text, int keyCode) { super.performFiltering(text, keyCode); } void superReplaceText(CharSequence text) { super.replaceText(text); } Filter superGetFilter() { return super.getFilter(); } void superPerformFiltering(CharSequence text, int start, int end, int keyCode){ super.performFiltering(text, start, end, keyCode); } void superOnSelectionChanged(int selStart, int selEnd) { super.onSelectionChanged(selStart, selEnd); } } }
ImageButton.java
package com.rey.material.widget; import android.content.Context; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.view.MotionEvent; import com.rey.material.drawable.RippleDrawable; public class ImageButton extends android.widget.ImageButton { private RippleManager mRippleManager = new RippleManager(); public ImageButton(Context context) { super(context); init(context, null, 0, 0); } public ImageButton(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public ImageButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public ImageButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, defStyleRes); } private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ applyStyle(context, attrs, defStyleAttr, defStyleRes); } public void applyStyle(int resId){ applyStyle(getContext(), null, 0, resId); } private void applyStyle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ mRippleManager.onCreate(this, context, attrs, defStyleAttr, defStyleRes); } @Override public void setBackgroundDrawable(Drawable drawable) { Drawable background = getBackground(); if(background instanceof RippleDrawable && !(drawable instanceof RippleDrawable)) ((RippleDrawable) background).setBackgroundDrawable(drawable); else super.setBackgroundDrawable(drawable); } @Override public void setOnClickListener(OnClickListener l) { if(l == mRippleManager) super.setOnClickListener(l); else{ mRippleManager.setOnClickListener(l); setOnClickListener(mRippleManager); } } @Override public boolean onTouchEvent(@NonNull MotionEvent event) { boolean result = super.onTouchEvent(event); return mRippleManager.onTouchEvent(event) || result; } }
ListPopupWindow.java
package com.rey.material.widget; import java.lang.reflect.Method; import java.util.Locale; import android.content.Context; import android.content.res.TypedArray; import android.database.DataSetObserver; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Handler; import android.os.SystemClock; import android.support.v4.text.TextUtilsCompat; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.ViewPropertyAnimatorCompat; import android.support.v4.widget.ListViewAutoScrollHelper; import android.support.v4.widget.PopupWindowCompat; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.View.MeasureSpec; import android.view.View.OnTouchListener; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.view.ViewTreeObserver; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.LinearLayout; import android.widget.ListAdapter; import com.example.textfieldformaterialdesign.R; /** * Static library support version of the framework's {@link android.widget.ListPopupWindow}. * Used to write apps that run on platforms prior to Android L. When running * on Android L or above, this implementation is still used; it does not try * to switch to the framework's implementation. See the framework SDK * documentation for a class overview. * * @see android.widget.ListPopupWindow */ public class ListPopupWindow { private static final String TAG = "ListPopupWindow"; private static final boolean DEBUG = false; /** * This value controls the length of time that the user * must leave a pointer down without scrolling to expand * the autocomplete dropdown list to cover the IME. */ private static final int EXPAND_LIST_TIMEOUT = 250; private static Method sClipToWindowEnabledMethod; static { try { sClipToWindowEnabledMethod = PopupWindow.class.getDeclaredMethod( "setClipToScreenEnabled", boolean.class); } catch (NoSuchMethodException e) { Log.i(TAG, "Could not find method setClipToScreenEnabled() on PopupWindow. Oh well."); } } private Context mContext; private PopupWindow mPopup; private ListAdapter mAdapter; private DropDownListView mDropDownList; private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT; private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT; private int mDropDownHorizontalOffset; private int mDropDownVerticalOffset; private boolean mDropDownVerticalOffsetSet; private int mItemAnimationId; private int mItemAnimationOffset; private int mDropDownGravity = Gravity.NO_GRAVITY; private boolean mDropDownAlwaysVisible = false; private boolean mForceIgnoreOutsideTouch = false; int mListItemExpandMaximum = Integer.MAX_VALUE; private View mPromptView; private int mPromptPosition = POSITION_PROMPT_ABOVE; private DataSetObserver mObserver; private View mDropDownAnchorView; private Drawable mDropDownListHighlight; private AdapterView.OnItemClickListener mItemClickListener; private AdapterView.OnItemSelectedListener mItemSelectedListener; private final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable(); private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor(); private final PopupScrollListener mScrollListener = new PopupScrollListener(); private final ListSelectorHider mHideSelector = new ListSelectorHider(); private Runnable mShowDropDownRunnable; private Handler mHandler = new Handler(); private Rect mTempRect = new Rect(); private boolean mModal; private int mLayoutDirection; /** * The provided prompt view should appear above list content. * * @see #setPromptPosition(int) * @see #getPromptPosition() * @see #setPromptView(android.view.View) */ public static final int POSITION_PROMPT_ABOVE = 0; /** * The provided prompt view should appear below list content. * * @see #setPromptPosition(int) * @see #getPromptPosition() * @see #setPromptView(android.view.View) */ public static final int POSITION_PROMPT_BELOW = 1; /** * Alias for {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}. * If used to specify a popup width, the popup will match the width of the anchor view. * If used to specify a popup height, the popup will fill available space. */ public static final int MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT; /** * Alias for {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}. * If used to specify a popup width, the popup will use the width of its content. */ public static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT; /** * Mode for {@link #setInputMethodMode(int)}: the requirements for the * input method should be based on the focusability of the popup. That is * if it is focusable than it needs to work with the input method, else * it doesn't. */ public static final int INPUT_METHOD_FROM_FOCUSABLE = PopupWindow.INPUT_METHOD_FROM_FOCUSABLE; /** * Mode for {@link #setInputMethodMode(int)}: this popup always needs to * work with an input method, regardless of whether it is focusable. This * means that it will always be displayed so that the user can also operate * the input method while it is shown. */ public static final int INPUT_METHOD_NEEDED = PopupWindow.INPUT_METHOD_NEEDED; /** * Mode for {@link #setInputMethodMode(int)}: this popup never needs to * work with an input method, regardless of whether it is focusable. This * means that it will always be displayed to use as much space on the * screen as needed, regardless of whether this covers the input method. */ public static final int INPUT_METHOD_NOT_NEEDED = PopupWindow.INPUT_METHOD_NOT_NEEDED; /** * Create a new, empty popup window capable of displaying items from a ListAdapter. * Backgrounds should be set using {@link #setBackgroundDrawable(android.graphics.drawable.Drawable)}. * * @param context Context used for contained views. */ public ListPopupWindow(Context context) { this(context, null, R.attr.listPopupWindowStyle, 0); } /** * Create a new, empty popup window capable of displaying items from a ListAdapter. * Backgrounds should be set using {@link #setBackgroundDrawable(android.graphics.drawable.Drawable)}. * * @param context Context used for contained views. * @param attrs Attributes from inflating parent views used to style the popup. */ public ListPopupWindow(Context context, AttributeSet attrs) { this(context, attrs, R.attr.listPopupWindowStyle, 0); } /** * Create a new, empty popup window capable of displaying items from a ListAdapter. * Backgrounds should be set using {@link #setBackgroundDrawable(android.graphics.drawable.Drawable)}. * * @param context Context used for contained views. * @param attrs Attributes from inflating parent views used to style the popup. * @param defStyleAttr Default style attribute to use for popup content. */ public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr){ this(context, attrs, defStyleAttr, 0); } /** * Create a new, empty popup window capable of displaying items from a ListAdapter. * Backgrounds should be set using {@link #setBackgroundDrawable(android.graphics.drawable.Drawable)}. * * @param context Context used for contained views. * @param attrs Attributes from inflating parent views used to style the popup. * @param defStyleAttr Default style attribute to use for popup content. * @param defStyleRes Default style to use for popup content. */ public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { mContext = context; final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ListPopupWindow, defStyleAttr, defStyleRes); mDropDownHorizontalOffset = a.getDimensionPixelOffset( R.styleable.ListPopupWindow_android_dropDownHorizontalOffset, 0); mDropDownVerticalOffset = a.getDimensionPixelOffset( R.styleable.ListPopupWindow_android_dropDownVerticalOffset, 0); if (mDropDownVerticalOffset != 0) { mDropDownVerticalOffsetSet = true; } a.recycle(); mPopup = new PopupWindow(context, attrs, defStyleAttr); mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); // Set the default layout direction to match the default locale one final Locale locale = mContext.getResources().getConfiguration().locale; mLayoutDirection = TextUtilsCompat.getLayoutDirectionFromLocale(locale); } public void setItemAnimation(int id){ mItemAnimationId = id; } public void setItemAnimationOffset(int offset){ mItemAnimationOffset = offset; } public void setBackgroundDrawable(Drawable background){ mPopup.setBackgroundDrawable(background); } public Drawable getBackground(){ return mPopup.getBackground(); } /** * Sets the adapter that provides the data and the views to represent the data * in this popup window. * * @param adapter The adapter to use to create this window's content. */ public void setAdapter(ListAdapter adapter) { if (mObserver == null) { mObserver = new PopupDataSetObserver(); } else if (mAdapter != null) { mAdapter.unregisterDataSetObserver(mObserver); } mAdapter = adapter; if (mAdapter != null) { adapter.registerDataSetObserver(mObserver); } if (mDropDownList != null) { mDropDownList.setAdapter(mAdapter); } } /** * Set where the optional prompt view should appear. The default is * {@link #POSITION_PROMPT_ABOVE}. * * @param position A position constant declaring where the prompt should be displayed. * * @see #POSITION_PROMPT_ABOVE * @see #POSITION_PROMPT_BELOW */ public void setPromptPosition(int position) { mPromptPosition = position; } /** * @return Where the optional prompt view should appear. * * @see #POSITION_PROMPT_ABOVE * @see #POSITION_PROMPT_BELOW */ public int getPromptPosition() { return mPromptPosition; } /** * Set whether this window should be modal when shown. * * <p>If a popup window is modal, it will receive all touch and key input. * If the user touches outside the popup window's content area the popup window * will be dismissed. * * @param modal {@code true} if the popup window should be modal, {@code false} otherwise. */ public void setModal(boolean modal) { mModal = modal; mPopup.setFocusable(modal); } /** * Returns whether the popup window will be modal when shown. * * @return {@code true} if the popup window will be modal, {@code false} otherwise. */ public boolean isModal() { return mModal; } /** * Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is * false, we allow outside touch to dismiss the dropdown. If this is set to true, then we * ignore outside touch even when the drop down is not set to always visible. * * @hide Used only by AutoCompleteTextView to handle some internal special cases. */ public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) { mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch; } /** * Sets whether the drop-down should remain visible under certain conditions. * * The drop-down will occupy the entire screen below {@link #getAnchorView} regardless * of the size or content of the list. {@link #getBackground()} will fill any space * that is not used by the list. * * @param dropDownAlwaysVisible Whether to keep the drop-down visible. * * @hide Only used by AutoCompleteTextView under special conditions. */ public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) { mDropDownAlwaysVisible = dropDownAlwaysVisible; } /** * @return Whether the drop-down is visible under special conditions. * * @hide Only used by AutoCompleteTextView under special conditions. */ public boolean isDropDownAlwaysVisible() { return mDropDownAlwaysVisible; } /** * Sets the operating mode for the soft input area. * * @param mode The desired mode, see * {@link android.view.WindowManager.LayoutParams#softInputMode} * for the full list * * @see android.view.WindowManager.LayoutParams#softInputMode * @see #getSoftInputMode() */ public void setSoftInputMode(int mode) { mPopup.setSoftInputMode(mode); } /** * Returns the current value in {@link #setSoftInputMode(int)}. * * @see #setSoftInputMode(int) * @see android.view.WindowManager.LayoutParams#softInputMode */ public int getSoftInputMode() { return mPopup.getSoftInputMode(); } /** * Sets a drawable to use as the list item selector. * * @param selector List selector drawable to use in the popup. */ public void setListSelector(Drawable selector) { mDropDownListHighlight = selector; } /** * Set an animation style to use when the popup window is shown or dismissed. * * @param animationStyle Animation style to use. */ public void setAnimationStyle(int animationStyle) { mPopup.setAnimationStyle(animationStyle); } /** * Returns the animation style that will be used when the popup window is shown or dismissed. * * @return Animation style that will be used. */ public int getAnimationStyle() { return mPopup.getAnimationStyle(); } /** * Returns the view that will be used to anchor this popup. * * @return The popup's anchor view */ public View getAnchorView() { return mDropDownAnchorView; } /** * Sets the popup's anchor view. This popup will always be positioned relative to the anchor * view when shown. * * @param anchor The view to use as an anchor. */ public void setAnchorView(View anchor) { mDropDownAnchorView = anchor; } /** * @return The horizontal offset of the popup from its anchor in pixels. */ public int getHorizontalOffset() { return mDropDownHorizontalOffset; } /** * Set the horizontal offset of this popup from its anchor view in pixels. * * @param offset The horizontal offset of the popup from its anchor. */ public void setHorizontalOffset(int offset) { mDropDownHorizontalOffset = offset; } /** * @return The vertical offset of the popup from its anchor in pixels. */ public int getVerticalOffset() { if (!mDropDownVerticalOffsetSet) { return 0; } return mDropDownVerticalOffset; } /** * Set the vertical offset of this popup from its anchor view in pixels. * * @param offset The vertical offset of the popup from its anchor. */ public void setVerticalOffset(int offset) { mDropDownVerticalOffset = offset; mDropDownVerticalOffsetSet = true; } /** * Set the gravity of the dropdown list. This is commonly used to * set gravity to START or END for alignment with the anchor. * * @param gravity Gravity value to use */ public void setDropDownGravity(int gravity) { mDropDownGravity = gravity; } /** * @return The width of the popup window in pixels. */ public int getWidth() { return mDropDownWidth; } /** * Sets the width of the popup window in pixels. Can also be {@link #MATCH_PARENT} * or {@link #WRAP_CONTENT}. * * @param width Width of the popup window. */ public void setWidth(int width) { mDropDownWidth = width; } /** * Sets the width of the popup window by the size of its content. The final width may be * larger to accommodate styled window dressing. * * @param width Desired width of content in pixels. */ public void setContentWidth(int width) { Drawable popupBackground = mPopup.getBackground(); if (popupBackground != null) { popupBackground.getPadding(mTempRect); mDropDownWidth = mTempRect.left + mTempRect.right + width; } else { setWidth(width); } } /** * @return The height of the popup window in pixels. */ public int getHeight() { return mDropDownHeight; } /** * Sets the height of the popup window in pixels. Can also be {@link #MATCH_PARENT}. * * @param height Height of the popup window. */ public void setHeight(int height) { mDropDownHeight = height; } /** * Sets a listener to receive events when a list item is clicked. * * @param clickListener Listener to register * * @see com.rey.material.widget.ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener) */ public void setOnItemClickListener(AdapterView.OnItemClickListener clickListener) { mItemClickListener = clickListener; } /** * Sets a listener to receive events when a list item is selected. * * @param selectedListener Listener to register. * * @see com.rey.material.widget.ListView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener) */ public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener selectedListener) { mItemSelectedListener = selectedListener; } /** * Set a view to act as a user prompt for this popup window. Where the prompt view will appear * is controlled by {@link #setPromptPosition(int)}. * * @param prompt View to use as an informational prompt. */ public void setPromptView(View prompt) { boolean showing = isShowing(); if (showing) { removePromptView(); } mPromptView = prompt; if (showing) { show(); } } /** * Post a {@link #show()} call to the UI thread. */ public void postShow() { mHandler.post(mShowDropDownRunnable); } /** * Show the popup list. If the list is already showing, this method * will recalculate the popup's size and position. */ public void show() { int height = buildDropDown(); int widthSpec = 0; int heightSpec = 0; boolean noInputMethod = isInputMethodNotNeeded(); if (mPopup.isShowing()) { if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) { // The call to PopupWindow's update method below can accept -1 for any // value you do not want to update. widthSpec = -1; } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) { widthSpec = getAnchorView().getWidth(); } else { widthSpec = mDropDownWidth; } if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { // The call to PopupWindow's update method below can accept -1 for any // value you do not want to update. heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT; if (noInputMethod) { mPopup.setWindowLayoutMode( mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ? ViewGroup.LayoutParams.MATCH_PARENT : 0, 0); } else { mPopup.setWindowLayoutMode( mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ? ViewGroup.LayoutParams.MATCH_PARENT : 0, ViewGroup.LayoutParams.MATCH_PARENT); } } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) { heightSpec = height; } else { heightSpec = mDropDownHeight; } mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible); mPopup.update(getAnchorView(), mDropDownHorizontalOffset, mDropDownVerticalOffset, widthSpec, heightSpec); } else { if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) { widthSpec = ViewGroup.LayoutParams.MATCH_PARENT; } else { if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) { mPopup.setWidth(getAnchorView().getWidth()); } else { mPopup.setWidth(mDropDownWidth); } } if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { heightSpec = ViewGroup.LayoutParams.MATCH_PARENT; } else { if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) { mPopup.setHeight(height); } else { mPopup.setHeight(mDropDownHeight); } } mPopup.setWindowLayoutMode(widthSpec, heightSpec); setPopupClipToScreenEnabled(true); // use outside touchable to dismiss drop down when touching outside of it, so // only set this if the dropdown is not always visible mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible); mPopup.setTouchInterceptor(mTouchInterceptor); PopupWindowCompat.showAsDropDown(mPopup, getAnchorView(), mDropDownHorizontalOffset, mDropDownVerticalOffset, mDropDownGravity); mDropDownList.setSelection(ListView.INVALID_POSITION); if (!mModal || mDropDownList.isInTouchMode()) { clearListSelection(); } if (!mModal) { mHandler.post(mHideSelector); } // show item animation if(mItemAnimationId != 0) mPopup.getContentView().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { mPopup.getContentView().getViewTreeObserver().removeOnPreDrawListener(this); for(int i = 0, count = mDropDownList.getChildCount(); i < count; i ++){ View v = mDropDownList.getChildAt(i); Animation anim = AnimationUtils.loadAnimation(mContext, mItemAnimationId); anim.setStartOffset(mItemAnimationOffset * i); v.startAnimation(anim); } return false; } }); } } /** * Dismiss the popup window. */ public void dismiss() { mPopup.dismiss(); removePromptView(); mPopup.setContentView(null); mDropDownList = null; mHandler.removeCallbacks(mResizePopupRunnable); } /** * Set a listener to receive a callback when the popup is dismissed. * * @param listener Listener that will be notified when the popup is dismissed. */ public void setOnDismissListener(PopupWindow.OnDismissListener listener) { mPopup.setOnDismissListener(listener); } private void removePromptView() { if (mPromptView != null) { final ViewParent parent = mPromptView.getParent(); if (parent instanceof ViewGroup) { final ViewGroup group = (ViewGroup) parent; group.removeView(mPromptView); } } } /** * Control how the popup operates with an input method: one of * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED}, * or {@link #INPUT_METHOD_NOT_NEEDED}. * * <p>If the popup is showing, calling this method will take effect only * the next time the popup is shown or through a manual call to the {@link #show()} * method.</p> * * @see #getInputMethodMode() * @see #show() */ public void setInputMethodMode(int mode) { mPopup.setInputMethodMode(mode); } /** * Return the current value in {@link #setInputMethodMode(int)}. * * @see #setInputMethodMode(int) */ public int getInputMethodMode() { return mPopup.getInputMethodMode(); } /** * Set the selected position of the list. * Only valid when {@link #isShowing()} == {@code true}. * * @param position List position to set as selected. */ public void setSelection(int position) { DropDownListView list = mDropDownList; if (isShowing() && list != null) { list.mListSelectionHidden = false; list.setSelection(position); if (Build.VERSION.SDK_INT >= 11) { if (list.getChoiceMode() != ListView.CHOICE_MODE_NONE) { list.setItemChecked(position, true); } } } } /** * Clear any current list selection. * Only valid when {@link #isShowing()} == {@code true}. */ public void clearListSelection() { final DropDownListView list = mDropDownList; if (list != null) { // WARNING: Please read the comment where mListSelectionHidden is declared list.mListSelectionHidden = true; //list.hideSelector(); list.requestLayout(); } } /** * @return {@code true} if the popup is currently showing, {@code false} otherwise. */ public boolean isShowing() { return mPopup.isShowing(); } /** * @return {@code true} if this popup is configured to assume the user does not need * to interact with the IME while it is showing, {@code false} otherwise. */ public boolean isInputMethodNotNeeded() { return mPopup.getInputMethodMode() == INPUT_METHOD_NOT_NEEDED; } /** * Perform an item click operation on the specified list adapter position. * * @param position Adapter position for performing the click * @return true if the click action could be performed, false if not. * (e.g. if the popup was not showing, this method would return false.) */ public boolean performItemClick(int position) { if (isShowing()) { if (mItemClickListener != null) { final DropDownListView list = mDropDownList; final View child = list.getChildAt(position - list.getFirstVisiblePosition()); final ListAdapter adapter = list.getAdapter(); mItemClickListener.onItemClick(list, child, position, adapter.getItemId(position)); } return true; } return false; } /** * @return The currently selected item or null if the popup is not showing. */ public Object getSelectedItem() { if (!isShowing()) { return null; } return mDropDownList.getSelectedItem(); } /** * @return The position of the currently selected item or {@link ListView#INVALID_POSITION} * if {@link #isShowing()} == {@code false}. * * @see ListView#getSelectedItemPosition() */ public int getSelectedItemPosition() { if (!isShowing()) { return ListView.INVALID_POSITION; } return mDropDownList.getSelectedItemPosition(); } /** * @return The ID of the currently selected item or {@link ListView#INVALID_ROW_ID} * if {@link #isShowing()} == {@code false}. * * @see ListView#getSelectedItemId() */ public long getSelectedItemId() { if (!isShowing()) { return ListView.INVALID_ROW_ID; } return mDropDownList.getSelectedItemId(); } /** * @return The View for the currently selected item or null if * {@link #isShowing()} == {@code false}. * * @see ListView#getSelectedView() */ public View getSelectedView() { if (!isShowing()) { return null; } return mDropDownList.getSelectedView(); } /** * @return The {@link ListView} displayed within the popup window. * Only valid when {@link #isShowing()} == {@code true}. */ public ListView getListView() { return mDropDownList; } public PopupWindow getPopup(){ return mPopup; } /** * The maximum number of list items that can be visible and still have * the list expand when touched. * * @param max Max number of items that can be visible and still allow the list to expand. */ void setListItemExpandMax(int max) { mListItemExpandMaximum = max; } /** * Filter key down events. By forwarding key down events to this function, * views using non-modal ListPopupWindow can have it handle key selection of items. * * @param keyCode keyCode param passed to the host view's onKeyDown * @param event event param passed to the host view's onKeyDown * @return true if the event was handled, false if it was ignored. * * @see #setModal(boolean) */ public boolean onKeyDown(int keyCode, KeyEvent event) { // when the drop down is shown, we drive it directly if (isShowing()) { // the key events are forwarded to the list in the drop down view // note that ListView handles space but we don't want that to happen // also if selection is not currently in the drop down, then don't // let center or enter presses go there since that would cause it // to select one of its items if (keyCode != KeyEvent.KEYCODE_SPACE && (mDropDownList.getSelectedItemPosition() >= 0 || !isConfirmKey(keyCode))) { int curIndex = mDropDownList.getSelectedItemPosition(); boolean consumed; final boolean below = !mPopup.isAboveAnchor(); final ListAdapter adapter = mAdapter; boolean allEnabled; int firstItem = Integer.MAX_VALUE; int lastItem = Integer.MIN_VALUE; if (adapter != null) { allEnabled = adapter.areAllItemsEnabled(); firstItem = allEnabled ? 0 : mDropDownList.lookForSelectablePosition(0, true); lastItem = allEnabled ? adapter.getCount() - 1 : mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false); } if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem) || (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem)) { // When the selection is at the top, we block the key // event to prevent focus from moving. clearListSelection(); mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); show(); return true; } else { // WARNING: Please read the comment where mListSelectionHidden // is declared mDropDownList.mListSelectionHidden = false; } consumed = mDropDownList.onKeyDown(keyCode, event); if (DEBUG) Log.v(TAG, "Key down: code=" + keyCode + " list consumed=" + consumed); if (consumed) { // If it handled the key event, then the user is // navigating in the list, so we should put it in front. mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); // Here's a little trick we need to do to make sure that // the list view is actually showing its focus indicator, // by ensuring it has focus and getting its window out // of touch mode. mDropDownList.requestFocusFromTouch(); show(); switch (keyCode) { // avoid passing the focus from the text view to the // next component case KeyEvent.KEYCODE_ENTER: case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_DPAD_DOWN: case KeyEvent.KEYCODE_DPAD_UP: return true; } } else { if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { // when the selection is at the bottom, we block the // event to avoid going to the next focusable widget if (curIndex == lastItem) { return true; } } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex == firstItem) { return true; } } } } return false; } /** * Filter key down events. By forwarding key up events to this function, * views using non-modal ListPopupWindow can have it handle key selection of items. * * @param keyCode keyCode param passed to the host view's onKeyUp * @param event event param passed to the host view's onKeyUp * @return true if the event was handled, false if it was ignored. * * @see #setModal(boolean) */ public boolean onKeyUp(int keyCode, KeyEvent event) { if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) { boolean consumed = mDropDownList.onKeyUp(keyCode, event); if (consumed && isConfirmKey(keyCode)) { // if the list accepts the key events and the key event was a click, the text view // gets the selected item from the drop down as its content dismiss(); } return consumed; } return false; } /** * Filter pre-IME key events. By forwarding {@link android.view.View#onKeyPreIme(int, android.view.KeyEvent)} * events to this function, views using ListPopupWindow can have it dismiss the popup * when the back key is pressed. * * @param keyCode keyCode param passed to the host view's onKeyPreIme * @param event event param passed to the host view's onKeyPreIme * @return true if the event was handled, false if it was ignored. * * @see #setModal(boolean) */ public boolean onKeyPreIme(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && isShowing()) { // special case for the back key, we do not even try to send it // to the drop down list but instead, consume it immediately final View anchorView = mDropDownAnchorView; if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState(); if (state != null) { state.startTracking(event, this); } return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState(); if (state != null) { state.handleUpEvent(event); } if (event.isTracking() && !event.isCanceled()) { dismiss(); return true; } } } return false; } /** * Returns an {@link android.view.View.OnTouchListener} that can be added to the source view * to implement drag-to-open behavior. Generally, the source view should be * the same view that was passed to {@link #setAnchorView}. * <p> * When the listener is set on a view, touching that view and dragging * outside of its bounds will open the popup window. Lifting will select the * currently touched list item. * <p> * Example usage: * <pre> * ListPopupWindow myPopup = new ListPopupWindow(context); * myPopup.setAnchor(myAnchor); * OnTouchListener dragListener = myPopup.createDragToOpenListener(myAnchor); * myAnchor.setOnTouchListener(dragListener); * </pre> * * @param src the view on which the resulting listener will be set * @return a touch listener that controls drag-to-open behavior */ public OnTouchListener createDragToOpenListener(View src) { return new ForwardingListener(src) { @Override public ListPopupWindow getPopup() { return ListPopupWindow.this; } }; } /** * <p>Builds the popup window's content and returns the height the popup * should have. Returns -1 when the content already exists.</p> * * @return the content's height or -1 if content already exists */ private int buildDropDown() { int otherHeights = 0; if (mDropDownList == null) { ViewGroup dropDownView; Context context = mContext; /** * This Runnable exists for the sole purpose of checking if the view layout has got * completed and if so call showDropDown to display the drop down. This is used to show * the drop down as soon as possible after user opens up the search dialog, without * waiting for the normal UI pipeline to do it's job which is slower than this method. */ mShowDropDownRunnable = new Runnable() { public void run() { // View layout should be all done before displaying the drop down. View view = getAnchorView(); if (view != null && view.getWindowToken() != null) { show(); } } }; mDropDownList = new DropDownListView(context, !mModal); if (mDropDownListHighlight != null) { mDropDownList.setSelector(mDropDownListHighlight); } mDropDownList.setAdapter(mAdapter); mDropDownList.setOnItemClickListener(mItemClickListener); mDropDownList.setFocusable(true); mDropDownList.setFocusableInTouchMode(true); mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { if (position != -1) { DropDownListView dropDownList = mDropDownList; if (dropDownList != null) { dropDownList.mListSelectionHidden = false; } } } public void onNothingSelected(AdapterView<?> parent) { } }); mDropDownList.setOnScrollListener(mScrollListener); if (mItemSelectedListener != null) { mDropDownList.setOnItemSelectedListener(mItemSelectedListener); } dropDownView = mDropDownList; View hintView = mPromptView; if (hintView != null) { // if a hint has been specified, we accomodate more space for it and // add a text view in the drop down menu, at the bottom of the list LinearLayout hintContainer = new LinearLayout(context); hintContainer.setOrientation(LinearLayout.VERTICAL); LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f ); switch (mPromptPosition) { case POSITION_PROMPT_BELOW: hintContainer.addView(dropDownView, hintParams); hintContainer.addView(hintView); break; case POSITION_PROMPT_ABOVE: hintContainer.addView(hintView); hintContainer.addView(dropDownView, hintParams); break; default: Log.e(TAG, "Invalid hint position " + mPromptPosition); break; } // measure the hint's height to find how much more vertical space // we need to add to the drop down's height int widthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.AT_MOST); int heightSpec = MeasureSpec.UNSPECIFIED; hintView.measure(widthSpec, heightSpec); hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams(); otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin + hintParams.bottomMargin; dropDownView = hintContainer; } mPopup.setContentView(dropDownView); } else { final View view = mPromptView; if (view != null) { LinearLayout.LayoutParams hintParams = (LinearLayout.LayoutParams) view.getLayoutParams(); otherHeights = view.getMeasuredHeight() + hintParams.topMargin + hintParams.bottomMargin; } } // getMaxAvailableHeight() subtracts the padding, so we put it back // to get the available height for the whole window int padding = 0; Drawable background = mPopup.getBackground(); if (background != null) { background.getPadding(mTempRect); padding = mTempRect.top + mTempRect.bottom; // If we don't have an explicit vertical offset, determine one from the window // background so that content will line up. if (!mDropDownVerticalOffsetSet) { mDropDownVerticalOffset = -mTempRect.top; } } else { mTempRect.setEmpty(); } // Max height available on the screen for a popup. boolean ignoreBottomDecorations = mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED; final int maxHeight = mPopup.getMaxAvailableHeight( getAnchorView(), mDropDownVerticalOffset /*, ignoreBottomDecorations*/); if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { return maxHeight + padding; } final int childWidthSpec; switch (mDropDownWidth) { case ViewGroup.LayoutParams.WRAP_CONTENT: childWidthSpec = MeasureSpec.makeMeasureSpec( mContext.getResources().getDisplayMetrics().widthPixels - (mTempRect.left + mTempRect.right), MeasureSpec.AT_MOST); break; case ViewGroup.LayoutParams.MATCH_PARENT: childWidthSpec = MeasureSpec.makeMeasureSpec( mContext.getResources().getDisplayMetrics().widthPixels - (mTempRect.left + mTempRect.right), MeasureSpec.EXACTLY); break; default: childWidthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.EXACTLY); break; } final int listContent = mDropDownList.measureHeightOfChildrenCompat(childWidthSpec, 0, DropDownListView.NO_POSITION, maxHeight - otherHeights, -1); // add padding only if the list has items in it, that way we don't show // the popup if it is not needed if (listContent > 0) otherHeights += padding; return listContent + otherHeights; } /** * Abstract class that forwards touch events to a {@link ListPopupWindow}. * * @hide */ public static abstract class ForwardingListener implements OnTouchListener { /** Scaled touch slop, used for detecting movement outside bounds. */ private final float mScaledTouchSlop; /** Timeout before disallowing intercept on the source's parent. */ private final int mTapTimeout; /** Timeout before accepting a long-press to start forwarding. */ private final int mLongPressTimeout; /** Source view from which events are forwarded. */ private final View mSrc; /** Runnable used to prevent conflicts with scrolling parents. */ private Runnable mDisallowIntercept; /** Runnable used to trigger forwarding on long-press. */ private Runnable mTriggerLongPress; /** Whether this listener is currently forwarding touch events. */ private boolean mForwarding; /** * Whether forwarding was initiated by a long-press. If so, we won't * force the window to dismiss when the touch stream ends. */ private boolean mWasLongPress; /** The id of the first pointer down in the current event stream. */ private int mActivePointerId; /** * Temporary Matrix instance */ private final int[] mTmpLocation = new int[2]; public ForwardingListener(View src) { mSrc = src; mScaledTouchSlop = ViewConfiguration.get(src.getContext()).getScaledTouchSlop(); mTapTimeout = ViewConfiguration.getTapTimeout(); // Use a medium-press timeout. Halfway between tap and long-press. mLongPressTimeout = (mTapTimeout + ViewConfiguration.getLongPressTimeout()) / 2; } /** * Returns the popup to which this listener is forwarding events. * <p> * Override this to return the correct popup. If the popup is displayed * asynchronously, you may also need to override * {@link #onForwardingStopped} to prevent premature cancelation of * forwarding. * * @return the popup to which this listener is forwarding events */ public abstract ListPopupWindow getPopup(); @Override public boolean onTouch(View v, MotionEvent event) { final boolean wasForwarding = mForwarding; final boolean forwarding; if (wasForwarding) { if (mWasLongPress) { // If we started forwarding as a result of a long-press, // just silently stop forwarding events so that the window // stays open. forwarding = onTouchForwarded(event); } else { forwarding = onTouchForwarded(event) || !onForwardingStopped(); } } else { forwarding = onTouchObserved(event) && onForwardingStarted(); if (forwarding) { // Make sure we cancel any ongoing source event stream. final long now = SystemClock.uptimeMillis(); final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); mSrc.onTouchEvent(e); e.recycle(); } } mForwarding = forwarding; return forwarding || wasForwarding; } /** * Called when forwarding would like to start. <p> By default, this will show the popup * returned by {@link #getPopup()}. It may be overridden to perform another action, like * clicking the source view or preparing the popup before showing it. * * @return true to start forwarding, false otherwise */ protected boolean onForwardingStarted() { final ListPopupWindow popup = getPopup(); if (popup != null && !popup.isShowing()) { popup.show(); } return true; } /** * Called when forwarding would like to stop. <p> By default, this will dismiss the popup * returned by {@link #getPopup()}. It may be overridden to perform some other action. * * @return true to stop forwarding, false otherwise */ protected boolean onForwardingStopped() { final ListPopupWindow popup = getPopup(); if (popup != null && popup.isShowing()) { popup.dismiss(); } return true; } /** * Observes motion events and determines when to start forwarding. * * @param srcEvent motion event in source view coordinates * @return true to start forwarding motion events, false otherwise */ private boolean onTouchObserved(MotionEvent srcEvent) { final View src = mSrc; if (!src.isEnabled()) { return false; } final int actionMasked = MotionEventCompat.getActionMasked(srcEvent); switch (actionMasked) { case MotionEvent.ACTION_DOWN: mActivePointerId = srcEvent.getPointerId(0); mWasLongPress = false; if (mDisallowIntercept == null) { mDisallowIntercept = new DisallowIntercept(); } src.postDelayed(mDisallowIntercept, mTapTimeout); if (mTriggerLongPress == null) { mTriggerLongPress = new TriggerLongPress(); } src.postDelayed(mTriggerLongPress, mLongPressTimeout); break; case MotionEvent.ACTION_MOVE: final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId); if (activePointerIndex >= 0) { final float x = srcEvent.getX(activePointerIndex); final float y = srcEvent.getY(activePointerIndex); if (!pointInView(src, x, y, mScaledTouchSlop)) { clearCallbacks(); // Don't let the parent intercept our events. src.getParent().requestDisallowInterceptTouchEvent(true); return true; } } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: clearCallbacks(); break; } return false; } private void clearCallbacks() { if (mTriggerLongPress != null) { mSrc.removeCallbacks(mTriggerLongPress); } if (mDisallowIntercept != null) { mSrc.removeCallbacks(mDisallowIntercept); } } private void onLongPress() { clearCallbacks(); final View src = mSrc; if (!src.isEnabled()) { return; } if (!onForwardingStarted()) { return; } // Don't let the parent intercept our events. mSrc.getParent().requestDisallowInterceptTouchEvent(true); // Make sure we cancel any ongoing source event stream. final long now = SystemClock.uptimeMillis(); final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0); mSrc.onTouchEvent(e); e.recycle(); mForwarding = true; mWasLongPress = true; } /** * Handled forwarded motion events and determines when to stop forwarding. * * @param srcEvent motion event in source view coordinates * @return true to continue forwarding motion events, false to cancel */ private boolean onTouchForwarded(MotionEvent srcEvent) { final View src = mSrc; final ListPopupWindow popup = getPopup(); if (popup == null || !popup.isShowing()) { return false; } final DropDownListView dst = popup.mDropDownList; if (dst == null || !dst.isShown()) { return false; } // Convert event to destination-local coordinates. final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent); toGlobalMotionEvent(src, dstEvent); toLocalMotionEvent(dst, dstEvent); // Forward converted event to destination view, then recycle it. final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId); dstEvent.recycle(); // Always cancel forwarding when the touch stream ends. final int action = MotionEventCompat.getActionMasked(srcEvent); final boolean keepForwarding = action != MotionEvent.ACTION_UP && action != MotionEvent.ACTION_CANCEL; return handled && keepForwarding; } private static boolean pointInView(View view, float localX, float localY, float slop) { return localX >= -slop && localY >= -slop && localX < ((view.getRight() - view.getLeft()) + slop) && localY < ((view.getBottom() - view.getTop()) + slop); } /** * Emulates View.toLocalMotionEvent(). This implementation does not handle transformations * (scaleX, scaleY, etc). */ private boolean toLocalMotionEvent(View view, MotionEvent event) { final int[] loc = mTmpLocation; view.getLocationOnScreen(loc); event.offsetLocation(-loc[0], -loc[1]); return true; } /** * Emulates View.toGlobalMotionEvent(). This implementation does not handle transformations * (scaleX, scaleY, etc). */ private boolean toGlobalMotionEvent(View view, MotionEvent event) { final int[] loc = mTmpLocation; view.getLocationOnScreen(loc); event.offsetLocation(loc[0], loc[1]); return true; } private class DisallowIntercept implements Runnable { @Override public void run() { final ViewParent parent = mSrc.getParent(); parent.requestDisallowInterceptTouchEvent(true); } } private class TriggerLongPress implements Runnable { @Override public void run() { onLongPress(); } } } /** * <p>Wrapper class for a ListView. This wrapper can hijack the focus to * make sure the list uses the appropriate drawables and states when * displayed on screen within a drop down. The focus is never actually * passed to the drop down in this mode; the list only looks focused.</p> */ private static class DropDownListView extends ListView { /* * WARNING: This is a workaround for a touch mode issue. * * Touch mode is propagated lazily to windows. This causes problems in * the following scenario: * - Type something in the AutoCompleteTextView and get some results * - Move down with the d-pad to select an item in the list * - Move up with the d-pad until the selection disappears * - Type more text in the AutoCompleteTextView *using the soft keyboard* * and get new results; you are now in touch mode * - The selection comes back on the first item in the list, even though * the list is supposed to be in touch mode * * Using the soft keyboard triggers the touch mode change but that change * is propagated to our window only after the first list layout, therefore * after the list attempts to resurrect the selection. * * The trick to work around this issue is to pretend the list is in touch * mode when we know that the selection should not appear, that is when * we know the user moved the selection away from the list. * * This boolean is set to true whenever we explicitly hide the list's * selection and reset to false whenever we know the user moved the * selection back to the list. * * When this boolean is true, isInTouchMode() returns true, otherwise it * returns super.isInTouchMode(). */ private boolean mListSelectionHidden; /** * True if this wrapper should fake focus. */ private boolean mHijackFocus; /** Whether to force drawing of the pressed state selector. */ private boolean mDrawsInPressedState; /** Current drag-to-open click animation, if any. */ private ViewPropertyAnimatorCompat mClickAnimation; /** Helper for drag-to-open auto scrolling. */ private ListViewAutoScrollHelper mScrollHelper; /** * <p>Creates a new list view wrapper.</p> * * @param context this view's context */ public DropDownListView(Context context, boolean hijackFocus) { super(context, null, R.attr.dropDownListViewStyle); mHijackFocus = hijackFocus; setCacheColorHint(0); // Transparent, since the background drawable could be anything. } /** * Handles forwarded events. * * @param activePointerId id of the pointer that activated forwarding * @return whether the event was handled */ public boolean onForwardedEvent(MotionEvent event, int activePointerId) { boolean handledEvent = true; boolean clearPressedItem = false; final int actionMasked = MotionEventCompat.getActionMasked(event); switch (actionMasked) { case MotionEvent.ACTION_CANCEL: handledEvent = false; break; case MotionEvent.ACTION_UP: handledEvent = false; // $FALL-THROUGH$ case MotionEvent.ACTION_MOVE: final int activeIndex = event.findPointerIndex(activePointerId); if (activeIndex < 0) { handledEvent = false; break; } final int x = (int) event.getX(activeIndex); final int y = (int) event.getY(activeIndex); final int position = pointToPosition(x, y); if (position == INVALID_POSITION) { clearPressedItem = true; break; } final View child = getChildAt(position - getFirstVisiblePosition()); setPressedItem(child, position, x, y); handledEvent = true; if (actionMasked == MotionEvent.ACTION_UP) { clickPressedItem(child, position); } break; } // Failure to handle the event cancels forwarding. if (!handledEvent || clearPressedItem) { clearPressedItem(); } // Manage automatic scrolling. if (handledEvent) { if (mScrollHelper == null) { mScrollHelper = new ListViewAutoScrollHelper(this); } mScrollHelper.setEnabled(true); mScrollHelper.onTouch(this, event); } else if (mScrollHelper != null) { mScrollHelper.setEnabled(false); } return handledEvent; } /** * Starts an alpha animation on the selector. When the animation ends, * the list performs a click on the item. */ private void clickPressedItem(final View child, final int position) { final long id = getItemIdAtPosition(position); performItemClick(child, position, id); } private void clearPressedItem() { mDrawsInPressedState = false; setPressed(false); // This will call through to updateSelectorState() drawableStateChanged(); if (mClickAnimation != null) { mClickAnimation.cancel(); mClickAnimation = null; } } private void setPressedItem(View child, int position, float x, float y) { mDrawsInPressedState = true; // Ordering is essential. First update the pressed state and layout // the children. This will ensure the selector actually gets drawn. setPressed(true); layoutChildren(); // Ensure that keyboard focus starts from the last touched position. setSelection(position); positionSelectorLikeTouchCompat(position, child, x, y); // This needs some explanation. We need to disable the selector for this next call // due to the way that ListViewCompat works. Otherwise both ListView and ListViewCompat // will draw the selector and bad things happen. setSelectorEnabled(false); // Refresh the drawable state to reflect the new pressed state, // which will also update the selector state. refreshDrawableState(); } @Override protected boolean touchModeDrawsInPressedStateCompat() { return mDrawsInPressedState || super.touchModeDrawsInPressedStateCompat(); } @Override public boolean isInTouchMode() { // WARNING: Please read the comment where mListSelectionHidden is declared return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode(); } /** * <p>Returns the focus state in the drop down.</p> * * @return true always if hijacking focus */ @Override public boolean hasWindowFocus() { return mHijackFocus || super.hasWindowFocus(); } /** * <p>Returns the focus state in the drop down.</p> * * @return true always if hijacking focus */ @Override public boolean isFocused() { return mHijackFocus || super.isFocused(); } /** * <p>Returns the focus state in the drop down.</p> * * @return true always if hijacking focus */ @Override public boolean hasFocus() { return mHijackFocus || super.hasFocus(); } } private class PopupDataSetObserver extends DataSetObserver { @Override public void onChanged() { if (isShowing()) { // Resize the popup to fit new content show(); } } @Override public void onInvalidated() { dismiss(); } } private class ListSelectorHider implements Runnable { public void run() { clearListSelection(); } } private class ResizePopupRunnable implements Runnable { public void run() { if (mDropDownList != null && mDropDownList.getCount() > mDropDownList.getChildCount() && mDropDownList.getChildCount() <= mListItemExpandMaximum) { mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); show(); } } } private class PopupTouchInterceptor implements OnTouchListener { public boolean onTouch(View v, MotionEvent event) { final int action = event.getAction(); final int x = (int) event.getX(); final int y = (int) event.getY(); if (action == MotionEvent.ACTION_DOWN && mPopup != null && mPopup.isShowing() && (x >= 0 && x < mPopup.getWidth() && y >= 0 && y < mPopup.getHeight())) { mHandler.postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT); } else if (action == MotionEvent.ACTION_UP) { mHandler.removeCallbacks(mResizePopupRunnable); } return false; } } private class PopupScrollListener implements ListView.OnScrollListener { public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == SCROLL_STATE_TOUCH_SCROLL && !isInputMethodNotNeeded() && mPopup.getContentView() != null) { mHandler.removeCallbacks(mResizePopupRunnable); mResizePopupRunnable.run(); } } } private static boolean isConfirmKey(int keyCode) { return keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_DPAD_CENTER; } private void setPopupClipToScreenEnabled(boolean clip) { if (sClipToWindowEnabledMethod != null) { try { sClipToWindowEnabledMethod.invoke(mPopup, clip); } catch (Exception e) { Log.i(TAG, "Could not call setClipToScreenEnabled() on PopupWindow. Oh well."); } } } }
ListView.java
package com.rey.material.widget; import android.content.Context; import android.support.v7.internal.widget.ListViewCompat; import android.util.AttributeSet; import android.view.View; public class ListView extends ListViewCompat { private RecyclerListener mRecyclerListener; public ListView(Context context) { super(context); init(context, null, 0, 0); } public ListView(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public ListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public ListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, defStyleRes); } private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ super.setRecyclerListener(new RecyclerListener() { @Override public void onMovedToScrapHeap(View view) { RippleManager.cancelRipple(view); if(mRecyclerListener != null) mRecyclerListener.onMovedToScrapHeap(view); } }); } @Override public void setRecyclerListener(RecyclerListener listener) { mRecyclerListener = listener; } }
PopupWindow.java
package com.rey.material.widget; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.os.Build; import android.util.AttributeSet; import android.view.View; import com.example.textfieldformaterialdesign.R; public class PopupWindow extends android.widget.PopupWindow { private final boolean mOverlapAnchor; public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PopupWindow, defStyleAttr, 0); mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false); a.recycle(); } @Override public void showAsDropDown(View anchor, int xoff, int yoff) { if (Build.VERSION.SDK_INT < 21 && mOverlapAnchor) { // If we're pre-L, emulate overlapAnchor by modifying the yOff yoff -= anchor.getHeight(); } super.showAsDropDown(anchor, xoff, yoff); } @TargetApi(Build.VERSION_CODES.KITKAT) @Override public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) { if (Build.VERSION.SDK_INT < 21 && mOverlapAnchor) { // If we're pre-L, emulate overlapAnchor by modifying the yOff yoff -= anchor.getHeight(); } super.showAsDropDown(anchor, xoff, yoff, gravity); } @Override public void update(View anchor, int xoff, int yoff, int width, int height) { if (Build.VERSION.SDK_INT < 21 && mOverlapAnchor) { // If we're pre-L, emulate overlapAnchor by modifying the yOff yoff -= anchor.getHeight(); } super.update(anchor, xoff, yoff, width, height); } }
RippleManager.java
package com.rey.material.widget; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import com.example.textfieldformaterialdesign.R; import com.rey.material.drawable.RippleDrawable; import com.rey.material.drawable.ToolbarRippleDrawable; public final class RippleManager implements View.OnClickListener, Runnable{ private View.OnClickListener mClickListener; private View mView; public RippleManager(){} @SuppressWarnings("deprecation") @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public void onCreate(View v, Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ if(v.isInEditMode()) return; mView = v; TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RippleView, defStyleAttr, defStyleRes); int rippleStyle = a.getResourceId(R.styleable.RippleView_rd_style, 0); RippleDrawable drawable = null; if(rippleStyle != 0) drawable = new RippleDrawable.Builder(context, rippleStyle).backgroundDrawable(mView.getBackground()).build(); else{ boolean rippleEnable = a.getBoolean(R.styleable.RippleView_rd_enable, false); if(rippleEnable) drawable = new RippleDrawable.Builder(context, attrs, defStyleAttr, defStyleRes).backgroundDrawable(mView.getBackground()).build(); } a.recycle(); if(drawable != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) mView.setBackground(drawable); else mView.setBackgroundDrawable(drawable); } } public boolean isDelayClick(){ Drawable background = mView.getBackground(); if(background instanceof RippleDrawable) return ((RippleDrawable)background).isDelayClick(); else if(background instanceof ToolbarRippleDrawable) return ((ToolbarRippleDrawable)background).isDelayClick(); return false; } public void setDelayClick(boolean delay){ Drawable background = mView.getBackground(); if(background instanceof RippleDrawable) ((RippleDrawable)background).setDelayClick(delay); else if(background instanceof ToolbarRippleDrawable) ((ToolbarRippleDrawable)background).setDelayClick(delay); } public void setOnClickListener(View.OnClickListener l) { mClickListener = l; } public boolean onTouchEvent(MotionEvent event){ Drawable background = mView.getBackground(); return background instanceof RippleDrawable && ((RippleDrawable) background).onTouch(mView, event); } @Override public void onClick(View v) { Drawable background = mView.getBackground(); long delay = 0; if(background instanceof RippleDrawable) delay = ((RippleDrawable)background).getClickDelayTime(); else if(background instanceof ToolbarRippleDrawable) delay = ((ToolbarRippleDrawable)background).getClickDelayTime(); if(delay > 0 && mView.getHandler() != null) mView.getHandler().postDelayed(this, delay); else run(); } @Override public void run() { if(mClickListener != null) mClickListener.onClick(mView); } public static void cancelRipple(View v){ Drawable background = v.getBackground(); if(background instanceof RippleDrawable) ((RippleDrawable)background).cancel(); else if(background instanceof ToolbarRippleDrawable) ((ToolbarRippleDrawable)background).cancel(); if(v instanceof ViewGroup){ ViewGroup vg = (ViewGroup)v; for(int i = 0, count = vg.getChildCount(); i < count; i++) RippleManager.cancelRipple(vg.getChildAt(i)); } } }
TextView.java
package com.rey.material.widget; import android.content.Context; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import com.rey.material.drawable.RippleDrawable; public class TextView extends android.widget.TextView { private RippleManager mRippleManager = new RippleManager(); public interface OnSelectionChangedListener{ public void onSelectionChanged(View v, int selStart, int selEnd); } private OnSelectionChangedListener mOnSelectionChangedListener; public TextView(Context context) { super(context); init(context, null, 0, 0); } public TextView(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public TextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public TextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, defStyleRes); } private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ applyStyle(context, attrs, defStyleAttr, defStyleRes); } public void applyStyle(int resId){ applyStyle(getContext(), null, 0, resId); } private void applyStyle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ mRippleManager.onCreate(this, context, attrs, defStyleAttr, defStyleRes); } @Override public void setBackgroundDrawable(Drawable drawable) { Drawable background = getBackground(); if(background instanceof RippleDrawable && !(drawable instanceof RippleDrawable)) ((RippleDrawable) background).setBackgroundDrawable(drawable); else super.setBackgroundDrawable(drawable); } @Override public void setOnClickListener(OnClickListener l) { if(l == mRippleManager) super.setOnClickListener(l); else{ mRippleManager.setOnClickListener(l); setOnClickListener(mRippleManager); } } @Override public boolean onTouchEvent(@NonNull MotionEvent event) { boolean result = super.onTouchEvent(event); return mRippleManager.onTouchEvent(event) || result; } public void setOnSelectionChangedListener(OnSelectionChangedListener listener){ mOnSelectionChangedListener = listener; } @Override protected void onSelectionChanged(int selStart, int selEnd) { super.onSelectionChanged(selStart, selEnd); if(mOnSelectionChangedListener != null) mOnSelectionChangedListener.onSelectionChanged(this, selStart, selEnd); } }
—> Run Your Code.
Leave a Reply