diff options
author | Yorke Lee <yorkelee@google.com> | 2013-05-07 11:59:36 -0700 |
---|---|---|
committer | Yorke Lee <yorkelee@google.com> | 2013-05-14 13:55:21 -0700 |
commit | 3a18654bf87df4f17880551b6cfa45c871917960 (patch) | |
tree | 55a360b02c7e799def416e9b672c52a4028b97e6 | |
parent | dbb6c6fc024bbacf3b495a44bdddccf8502793ae (diff) |
Animation for smart dialing suggestions
Suggestions now appear with a fade in and slide up animation.
Suggestions vanish with a fade out and slide down animation.
If a suggestion is moved into the middle, it slides to the left/right
as appropriate.
Change the layout containing suggestions to a LinearLayout, in order to
better support animations.
Renamed SmartDialAdapter to SmartDialController, and also refactored
it to handle entries for a LinearLayout instead of a GridView, as well
as adding animation support and view management.
Use null object pattern in SmartDialEntry to better handle null entries.
Start displaying suggestions on the first digit entered.
Bug 8840240
Change-Id: If4e16006c0b36d2244434e0b2d8f3d3b997b0ad2
-rw-r--r-- | res/layout/dialpad_fragment.xml | 30 | ||||
-rw-r--r-- | res/layout/dialpad_smartdial_item.xml | 12 | ||||
-rw-r--r-- | res/values/colors.xml | 7 | ||||
-rw-r--r-- | src/com/android/dialer/dialpad/DialpadFragment.java | 33 | ||||
-rw-r--r-- | src/com/android/dialer/dialpad/SmartDialAdapter.java | 166 | ||||
-rw-r--r-- | src/com/android/dialer/dialpad/SmartDialCache.java | 2 | ||||
-rw-r--r-- | src/com/android/dialer/dialpad/SmartDialController.java | 389 | ||||
-rw-r--r-- | src/com/android/dialer/dialpad/SmartDialEntry.java | 3 | ||||
-rw-r--r-- | src/com/android/dialer/dialpad/SmartDialLoaderTask.java | 2 | ||||
-rw-r--r-- | src/com/android/dialer/dialpad/SmartDialMatchPosition.java | 4 | ||||
-rw-r--r-- | src/com/android/dialer/dialpad/SmartDialTextView.java | 47 |
11 files changed, 457 insertions, 238 deletions
diff --git a/res/layout/dialpad_fragment.xml b/res/layout/dialpad_fragment.xml index 13d91bd27..f3bd2a213 100644 --- a/res/layout/dialpad_fragment.xml +++ b/res/layout/dialpad_fragment.xml @@ -57,17 +57,29 @@ android:src="@drawable/ic_dial_action_delete" /> </LinearLayout> - <!-- Smard dial suggestion section --> - <GridView - android:id="@+id/dialpad_smartdial_list" + <!-- Smart dial suggestion section. + sp is used here for this layout instead of dp in order for it to resize as + appropriate when the font size increases. This is a one-time exception that is + ok in this case because there is space for the suggestion strip to expand. --> + <RelativeLayout + android:id="@+id/dialpad_smartdial_container" android:layout_width="match_parent" android:layout_height="50sp" - android:columnWidth="0dp" - android:numColumns="3" - android:stretchMode="columnWidth" - android:gravity="center" - android:layout_marginTop="@dimen/dialpad_vertical_margin" - android:background="@drawable/dialpad_background"/> + android:layout_marginTop="@dimen/dialpad_vertical_margin"> + <View + android:id="@+id/dialpad_smartdial_list_background" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@drawable/dialpad_background"> + </View> + <LinearLayout + android:id="@+id/dialpad_smartdial_list" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:gravity="center"> + </LinearLayout> + </RelativeLayout> <!-- Keypad section --> <include layout="@layout/dialpad" /> diff --git a/res/layout/dialpad_smartdial_item.xml b/res/layout/dialpad_smartdial_item.xml index 54f2a084e..32d801e80 100644 --- a/res/layout/dialpad_smartdial_item.xml +++ b/res/layout/dialpad_smartdial_item.xml @@ -16,15 +16,19 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="46sp"> + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="match_parent" + android:layout_marginTop="2dp" + android:layout_marginBottom="2dp" + android:background="?android:attr/selectableItemBackground"> <com.android.dialer.dialpad.SmartDialTextView android:id="@+id/contact_name" android:layout_width="match_parent" android:layout_height="28sp" android:padding="@dimen/smartdial_suggestions_padding" - android:textColor="@color/smartdial_primary_text_color" + android:textColor="@color/smartdial_name_primary_text_color" android:textSize="16sp" android:singleLine="true" android:ellipsize="none" @@ -34,7 +38,7 @@ android:id="@+id/contact_number" android:layout_width="match_parent" android:layout_height="16sp" - android:textColor="@color/dialtacts_secondary_text_color" + android:textColor="@color/smartdial_number_primary_text_color" android:textSize="13sp" android:gravity="center" /> diff --git a/res/values/colors.xml b/res/values/colors.xml index 288f58b64..1aa217fe8 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -18,9 +18,10 @@ <!-- Secondary text color in the Phone app --> <color name="dialtacts_secondary_text_color">#888888</color> - <color name="smartdial_confidence_drawable_color">#39caff</color> - <color name="smartdial_primary_text_color">#39caff</color> - <color name="smartdial_highlighted_text_color">#ffffff</color> + <color name="smartdial_name_primary_text_color">#0099cc</color> + <color name="smartdial_name_highlighted_text_color">#39c9ff</color> + <color name="smartdial_number_primary_text_color">#bbbbbb</color> + <color name="smartdial_number_highlighted_text_color">#ffffff</color> <!-- Color of the text describing an unconsumed missed call. --> <color name="call_log_missed_call_highlight_color">#FF0000</color> diff --git a/src/com/android/dialer/dialpad/DialpadFragment.java b/src/com/android/dialer/dialpad/DialpadFragment.java index f571d892e..3d75e3983 100644 --- a/src/com/android/dialer/dialpad/DialpadFragment.java +++ b/src/com/android/dialer/dialpad/DialpadFragment.java @@ -67,8 +67,10 @@ import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.EditText; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ListView; import android.widget.PopupMenu; +import android.widget.RelativeLayout; import android.widget.TextView; import com.android.contacts.common.CallUtil; @@ -147,13 +149,12 @@ public class DialpadFragment extends Fragment private DialpadChooserAdapter mDialpadChooserAdapter; /** Will be set only if the view has the smart dialing section. */ - private AbsListView mSmartDialList; + private RelativeLayout mSmartDialContainer; /** - * Adapter for {@link #mSmartDialList}. * Will be set only if the view has the smart dialing section. */ - private SmartDialAdapter mSmartDialAdapter; + private SmartDialController mSmartDialAdapter; private SmartDialCache mSmartDialCache; /** @@ -368,12 +369,12 @@ public class DialpadFragment extends Fragment mDialpadChooser.setOnItemClickListener(this); // Smart dial - mSmartDialList = (AbsListView) fragmentView.findViewById(R.id.dialpad_smartdial_list); - if (mSmartDialList != null) { - mSmartDialAdapter = new SmartDialAdapter(getActivity()); - mSmartDialList.setAdapter(mSmartDialAdapter); - mSmartDialList.setOnItemClickListener(new OnSmartDialItemClick()); - mSmartDialList.setOnItemLongClickListener(new OnSmartDialLongClick()); + mSmartDialContainer = (RelativeLayout) fragmentView.findViewById( + R.id.dialpad_smartdial_container); + + if (mSmartDialContainer != null) { + mSmartDialAdapter = new SmartDialController(getActivity(), mSmartDialContainer, + new OnSmartDialShortClick(), new OnSmartDialLongClick()); } return fragmentView; } @@ -1697,7 +1698,7 @@ public class DialpadFragment extends Fragment } mLastDigitsForSmartDial = digits; - if (digits.length() < 2) { + if (digits.length() < 1) { mSmartDialAdapter.clear(); } else { final SmartDialLoaderTask task = new SmartDialLoaderTask(this, digits, mSmartDialCache); @@ -1716,7 +1717,7 @@ public class DialpadFragment extends Fragment private void initializeSmartDialingState() { // Handle smart dialing related state if (mSmartDialEnabled) { - mSmartDialList.setVisibility(View.VISIBLE); + mSmartDialContainer.setVisibility(View.VISIBLE); mSmartDialCache = SmartDialCache.getInstance(getActivity(), mContactsPrefs.getDisplayOrder()); // Don't force recache if this is the first time onResume is being called, since @@ -1730,14 +1731,14 @@ public class DialpadFragment extends Fragment mSmartDialCache.cacheIfNeeded(true); } } else { - mSmartDialList.setVisibility(View.GONE); + mSmartDialContainer.setVisibility(View.GONE); mSmartDialCache = null; } } - private class OnSmartDialLongClick implements AdapterView.OnItemLongClickListener { + private class OnSmartDialLongClick implements View.OnLongClickListener { @Override - public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { + public boolean onLongClick(View view) { final SmartDialEntry entry = (SmartDialEntry) view.getTag(); if (entry == null) return false; // just in case. mClearDigitsOnStop = true; @@ -1749,9 +1750,9 @@ public class DialpadFragment extends Fragment } } - private class OnSmartDialItemClick implements AdapterView.OnItemClickListener { + private class OnSmartDialShortClick implements View.OnClickListener { @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + public void onClick(View view) { final SmartDialEntry entry = (SmartDialEntry) view.getTag(); if (entry == null) return; // just in case. // Dial the displayed phone number immediately diff --git a/src/com/android/dialer/dialpad/SmartDialAdapter.java b/src/com/android/dialer/dialpad/SmartDialAdapter.java deleted file mode 100644 index 0a246e3e5..000000000 --- a/src/com/android/dialer/dialpad/SmartDialAdapter.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.dialer.dialpad; - -import android.content.Context; -import android.content.res.Resources; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.TextUtils; -import android.text.style.ForegroundColorSpan; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.LinearLayout; - -import com.android.dialer.R; -import com.google.common.collect.Lists; - -import java.util.List; - -public class SmartDialAdapter extends BaseAdapter { - public static final String LOG_TAG = "SmartDial"; - private final LayoutInflater mInflater; - - private List<SmartDialEntry> mEntries; - - private final int mHighlightedTextColor; - - public SmartDialAdapter(Context context) { - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - final Resources res = context.getResources(); - mHighlightedTextColor = res.getColor(R.color.smartdial_highlighted_text_color); - clear(); - } - - /** Remove all entries. */ - public void clear() { - mEntries = Lists.newArrayList(); - notifyDataSetChanged(); - } - - /** Set entries. */ - public void setEntries(List<SmartDialEntry> entries) { - if (entries == null) throw new IllegalArgumentException(); - mEntries = entries; - - if (mEntries.size() <= 1) { - // add a null entry to push the single entry into the middle - mEntries.add(0, null); - } else if (mEntries.size() >= 2){ - // swap the 1st and 2nd entries so that the highest confidence match goes into the - // middle - final SmartDialEntry temp = mEntries.get(0); - mEntries.set(0, mEntries.get(1)); - mEntries.set(1, temp); - } - - notifyDataSetChanged(); - } - - @Override - public boolean isEnabled(int position) { - return !(mEntries.get(position) == null); - } - - @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public int getCount() { - return mEntries.size(); - } - - @Override - public Object getItem(int position) { - return mEntries.get(position); - } - - @Override - public long getItemId(int position) { - return position; // Just use the position as the ID, so it's not stable. - } - - @Override - public boolean hasStableIds() { - return false; // Not stable because we just use the position as the ID. - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final LinearLayout view; - if (convertView == null) { - view = (LinearLayout) mInflater.inflate( - R.layout.dialpad_smartdial_item, parent, false); - } else { - view = (LinearLayout) convertView; - } - - final SmartDialTextView nameView = (SmartDialTextView) view.findViewById(R.id.contact_name); - - final SmartDialTextView numberView = (SmartDialTextView) view.findViewById( - R.id.contact_number); - - final SmartDialEntry item = mEntries.get(position); - - if (item == null) { - // Clear the text in case the view was reused. - nameView.setText(""); - numberView.setText(""); - // Empty view. We use this to force a single entry to be in the middle - return view; - } - - // Highlight the display name with the provided match positions - if (!TextUtils.isEmpty(item.displayName)) { - final SpannableString displayName = new SpannableString(item.displayName); - for (final SmartDialMatchPosition p : item.matchPositions) { - if (p.start < p.end) { - if (p.end > displayName.length()) { - p.end = displayName.length(); - } - // Create a new ForegroundColorSpan for each section of the name to highlight, - // otherwise multiple highlights won't work. - displayName.setSpan(new ForegroundColorSpan(mHighlightedTextColor), p.start, - p.end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - nameView.setText(displayName); - } - - // Highlight the phone number with the provided match positions - if (!TextUtils.isEmpty(item.phoneNumber)) { - final SmartDialMatchPosition p = item.phoneNumberMatchPosition; - final SpannableString phoneNumber = new SpannableString(item.phoneNumber); - if (p != null && p.start < p.end) { - if (p.end > phoneNumber.length()) { - p.end = phoneNumber.length(); - } - phoneNumber.setSpan(new ForegroundColorSpan(mHighlightedTextColor), p.start, p.end, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - numberView.setText(phoneNumber); - } - view.setTag(item); - - return view; - } -} diff --git a/src/com/android/dialer/dialpad/SmartDialCache.java b/src/com/android/dialer/dialpad/SmartDialCache.java index 51e900ada..3294bfbb6 100644 --- a/src/com/android/dialer/dialpad/SmartDialCache.java +++ b/src/com/android/dialer/dialpad/SmartDialCache.java @@ -16,7 +16,7 @@ package com.android.dialer.dialpad; -import static com.android.dialer.dialpad.SmartDialAdapter.LOG_TAG; +import static com.android.dialer.dialpad.SmartDialController.LOG_TAG; import android.content.Context; import android.content.SharedPreferences; diff --git a/src/com/android/dialer/dialpad/SmartDialController.java b/src/com/android/dialer/dialpad/SmartDialController.java new file mode 100644 index 000000000..5ce993be1 --- /dev/null +++ b/src/com/android/dialer/dialpad/SmartDialController.java @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dialer.dialpad; + +import android.content.Context; +import android.content.res.Resources; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.style.ForegroundColorSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.View.OnLongClickListener; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; +import android.view.animation.OvershootInterpolator; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.dialer.R; + +import com.google.common.collect.Lists; + +import java.util.List; + +/** +* This class controls the display and animation logic behind the smart dialing suggestion strip. +* +* It allows a list of SmartDialEntries to be assigned to the suggestion strip via +* {@link #setEntries}, and also animates the removal of old suggestions. +* +* To avoid creating new views every time new entries are assigned, references to 2 * +* {@link #NUM_SUGGESTIONS} views are kept in {@link #mViews} and {@link #mViewOverlays}. +* +* {@code mViews} contains the active views that are currently being displayed to the user, +* while {@code mViewOverlays} contains the views that are used as view overlays. The view +* overlays are used to provide the illusion of the former suggestions fading out. These two +* lists of views are rotated each time a new set of entries is assigned to achieve the appropriate +* cross fade animations using the new {@link View#getOverlay()} API. +*/ +public class SmartDialController { + public static final String LOG_TAG = "SmartDial"; + + /** + * Handtuned interpolator used to achieve the bounce effect when suggestions slide up. It + * uses a combination of a decelerate interpolator and overshoot interpolator to first + * decelerate, and then overshoot its top bounds and bounce back to its final position. + */ + private class DecelerateAndOvershootInterpolator implements Interpolator { + private DecelerateInterpolator a; + private OvershootInterpolator b; + + public DecelerateAndOvershootInterpolator() { + a = new DecelerateInterpolator(1.5f); + b = new OvershootInterpolator(1.3f); + } + + @Override + public float getInterpolation(float input) { + if (input > 0.6) { + return b.getInterpolation(input); + } else { + return a.getInterpolation(input); + } + } + + } + + private DecelerateAndOvershootInterpolator mDecelerateAndOvershootInterpolator = + new DecelerateAndOvershootInterpolator(); + private AccelerateDecelerateInterpolator mAccelerateDecelerateInterpolator = + new AccelerateDecelerateInterpolator(); + + private List<SmartDialEntry> mEntries; + private List<SmartDialEntry> mOldEntries; + + private final int mNameHighlightedTextColor; + private final int mNumberHighlightedTextColor; + + private final LinearLayout mList; + private final View mBackground; + + private final List<LinearLayout> mViewOverlays = Lists.newArrayList(); + private final List<LinearLayout> mViews = Lists.newArrayList(); + + private static final int NUM_SUGGESTIONS = 3; + + private static final long ANIM_DURATION = 200; + + private static final float BACKGROUND_FADE_AMOUNT = 0.25f; + + Resources mResources; + + public SmartDialController(Context context, ViewGroup parent, + OnClickListener shortClickListener, OnLongClickListener longClickListener) { + final Resources res = context.getResources(); + mResources = res; + + mNameHighlightedTextColor = res.getColor(R.color.smartdial_name_highlighted_text_color); + mNumberHighlightedTextColor = res.getColor( + R.color.smartdial_number_highlighted_text_color); + + mList = (LinearLayout) parent.findViewById(R.id.dialpad_smartdial_list); + mBackground = parent.findViewById(R.id.dialpad_smartdial_list_background); + + mEntries = Lists.newArrayList(); + for (int i = 0; i < NUM_SUGGESTIONS; i++) { + mEntries.add(SmartDialEntry.NULL); + } + + mOldEntries = mEntries; + + final LayoutInflater inflater = (LayoutInflater) context.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + + for (int i = 0; i < NUM_SUGGESTIONS * 2; i++) { + final LinearLayout view = (LinearLayout) inflater.inflate( + R.layout.dialpad_smartdial_item, mList, false); + view.setOnClickListener(shortClickListener); + view.setOnLongClickListener(longClickListener); + if (i < NUM_SUGGESTIONS) { + mViews.add(view); + } else { + mViewOverlays.add(view); + } + // Add all the views to mList so that they can get measured properly for animation + // purposes. Once setEntries is called they will be removed and added as appropriate. + view.setEnabled(false); + mList.addView(view); + } + } + + /** Remove all entries. */ + public void clear() { + mOldEntries = mEntries; + mEntries = Lists.newArrayList(); + for (int i = 0; i < NUM_SUGGESTIONS; i++) { + mEntries.add(SmartDialEntry.NULL); + } + updateViews(); + } + + /** Set entries. At the end of this method {@link #mEntries} should contain exactly + * {@link #NUM_SUGGESTIONS} entries.*/ + public void setEntries(List<SmartDialEntry> entries) { + if (entries == null) throw new IllegalArgumentException(); + mOldEntries = mEntries; + mEntries = entries; + + final int size = mEntries.size(); + if (size <= 1) { + if (size == 0) { + mEntries.add(SmartDialEntry.NULL); + } + // add a null entry to push the single entry into the middle + mEntries.add(0, SmartDialEntry.NULL); + } else if (size >= 2) { + // swap the 1st and 2nd entries so that the highest confidence match goes into the + // middle + swap(0, 1); + } + + while (mEntries.size() < NUM_SUGGESTIONS) { + mEntries.add(SmartDialEntry.NULL); + } + + updateViews(); + } + + /** + * This method is called every time a new set of SmartDialEntries is to be assigned to the + * suggestions view. The current set of active views are to be used as view overlays and + * faded out, while the former view overlays are assigned the current entries, added to + * {@link #mList} and faded into view. + */ + private void updateViews() { + // Remove all views from the root in preparation to swap the two sets of views + mList.removeAllViews(); + try { + mList.getOverlay().clear(); + } catch (NullPointerException e) { + // Catch possible NPE b/8895794 + } + + // Used to track whether or not to animate the overlay. In the case where the suggestion + // at position i will slide from the left or right, or if the suggestion at position i + // has not changed, the overlay at i should be hidden immediately. Overlay animations are + // set in a separate loop from the active views to avoid unnecessarily reanimating the same + // overlay multiple times. + boolean[] dontAnimateOverlay = new boolean[NUM_SUGGESTIONS]; + boolean noSuggestions = true; + + // At this point in time {@link #mViews} contains the former active views with old + // suggestions that will be swapped out to serve as view overlays, while + // {@link #mViewOverlays} contains the former overlays that will now serve as active + // views. + for (int i = 0; i < NUM_SUGGESTIONS; i++) { + // Retrieve the former overlay to be used as the new active view + final LinearLayout active = mViewOverlays.get(i); + final SmartDialEntry item = mEntries.get(i); + + noSuggestions &= (item == SmartDialEntry.NULL); + + assignEntryToView(active, mEntries.get(i)); + final SmartDialEntry oldItem = mOldEntries.get(i); + // The former active view will now be used as an overlay for the cross-fade effect + final LinearLayout overlay = mViews.get(i); + show(active); + if (!containsSameContact(oldItem, item)) { + // Determine what kind of animation to use for the new view + if (i == 1) { // Middle suggestion + if (containsSameContact(item, mOldEntries.get(0))) { + // Suggestion went from the left to the middle, slide it left to right + animateSlideFromLeft(active); + dontAnimateOverlay[0] = true; + } else if (containsSameContact(item, mOldEntries.get(2))) { + // Suggestion sent from the right to the middle, slide it right to left + animateSlideFromRight(active); + dontAnimateOverlay[2] = true; + } else { + animateFadeInAndSlideUp(active); + } + } else { // Left/Right suggestion + if (i == 2 && containsSameContact(item, mOldEntries.get(1))) { + // Suggestion went from middle to the right, slide it left to right + animateSlideFromLeft(active); + dontAnimateOverlay[1] = true; + } else if (i == 0 && containsSameContact(item, mOldEntries.get(1))) { + // Suggestion went from middle to the left, slide it right to left + animateSlideFromRight(active); + dontAnimateOverlay[1] = true; + } else { + animateFadeInAndSlideUp(active); + } + } + } else { + // Since the same item is in the same spot, don't do any animations and just + // show the new view. + dontAnimateOverlay[i] = true; + } + mList.getOverlay().add(overlay); + mList.addView(active); + // Keep track of active views and view overlays + mViews.set(i, active); + mViewOverlays.set(i, overlay); + } + + // Separate loop for overlay animations. At this point in time {@link #mViewOverlays} + // contains the actual overlays. + for (int i = 0; i < NUM_SUGGESTIONS; i++) { + final LinearLayout overlay = mViewOverlays.get(i); + if (!dontAnimateOverlay[i]) { + animateFadeOutAndSlideDown(overlay); + } else { + hide(overlay); + } + } + + // Fade out the background to 25% opacity if there are suggestions. If there are no + // suggestions, display the background as usual. + mBackground.animate().withLayer().alpha(noSuggestions ? 1.0f : BACKGROUND_FADE_AMOUNT); + } + + private void show(View view) { + view.animate().cancel(); + view.setAlpha(1); + view.setTranslationX(0); + view.setTranslationY(0); + } + + private void hide(View view) { + view.animate().cancel(); + view.setAlpha(0); + } + + private void animateFadeInAndSlideUp(View view) { + view.animate().cancel(); + view.setAlpha(0.2f); + view.setTranslationY(view.getHeight()); + view.animate().withLayer().alpha(1).translationY(0).setDuration(ANIM_DURATION). + setInterpolator(mDecelerateAndOvershootInterpolator); + } + + private void animateFadeOutAndSlideDown(View view) { + view.animate().cancel(); + view.setAlpha(1); + view.setTranslationY(0); + view.animate().withLayer().alpha(0).translationY(view.getHeight()).setDuration( + ANIM_DURATION).setInterpolator(mAccelerateDecelerateInterpolator); + } + + private void animateSlideFromLeft(View view) { + view.animate().cancel(); + view.setAlpha(1); + view.setTranslationX(-1 * view.getWidth()); + view.animate().withLayer().translationX(0).setDuration(ANIM_DURATION).setInterpolator( + mAccelerateDecelerateInterpolator); + } + + private void animateSlideFromRight(View view) { + view.animate().cancel(); + view.setAlpha(1); + view.setTranslationX(view.getWidth()); + view.animate().withLayer().translationX(0).setDuration(ANIM_DURATION).setInterpolator( + mAccelerateDecelerateInterpolator); + } + + // Swaps the items in pos1 and pos2 of mEntries + private void swap(int pos1, int pos2) { + if (pos1 == pos2) { + return; + } + final SmartDialEntry temp = mEntries.get(pos1); + mEntries.set(pos1, mEntries.get(pos2)); + mEntries.set(pos2, temp); + } + + // Returns whether two SmartDialEntries contain the same contact + private boolean containsSameContact(SmartDialEntry x, SmartDialEntry y) { + return x.contactUri.equals(y.contactUri); + } + + // Sets the information within a SmartDialEntry to the provided view + private void assignEntryToView(LinearLayout view, SmartDialEntry item) { + final TextView nameView = (TextView) view.findViewById(R.id.contact_name); + + final TextView numberView = (TextView) view.findViewById( + R.id.contact_number); + + if (item == SmartDialEntry.NULL) { + // Clear the text in case the view was reused. + nameView.setText(""); + numberView.setText(""); + view.setEnabled(false); + return; + } + + // Highlight the display name with the provided match positions + if (!TextUtils.isEmpty(item.displayName)) { + final SpannableString displayName = new SpannableString(item.displayName); + for (final SmartDialMatchPosition p : item.matchPositions) { + if (p.start < p.end) { + if (p.end > displayName.length()) { + p.end = displayName.length(); + } + // Create a new ForegroundColorSpan for each section of the name to highlight, + // otherwise multiple highlights won't work. + displayName.setSpan(new ForegroundColorSpan(mNameHighlightedTextColor), p.start, + p.end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + nameView.setText(displayName); + } + + // Highlight the phone number with the provided match positions + if (!TextUtils.isEmpty(item.phoneNumber)) { + final SmartDialMatchPosition p = item.phoneNumberMatchPosition; + final SpannableString phoneNumber = new SpannableString(item.phoneNumber); + if (p != null && p.start < p.end) { + if (p.end > phoneNumber.length()) { + p.end = phoneNumber.length(); + } + phoneNumber.setSpan(new ForegroundColorSpan(mNumberHighlightedTextColor), p.start, + p.end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + numberView.setText(phoneNumber); + } + view.setEnabled(true); + view.setTag(item); + } +} diff --git a/src/com/android/dialer/dialpad/SmartDialEntry.java b/src/com/android/dialer/dialpad/SmartDialEntry.java index d658f3dca..9ff491293 100644 --- a/src/com/android/dialer/dialpad/SmartDialEntry.java +++ b/src/com/android/dialer/dialpad/SmartDialEntry.java @@ -29,6 +29,9 @@ public class SmartDialEntry { public final ArrayList<SmartDialMatchPosition> matchPositions; public final SmartDialMatchPosition phoneNumberMatchPosition; + public static final SmartDialEntry NULL = new SmartDialEntry("", Uri.EMPTY, "", + new ArrayList<SmartDialMatchPosition>(), null); + public SmartDialEntry(CharSequence displayName, Uri contactUri, CharSequence phoneNumber, ArrayList<SmartDialMatchPosition> matchPositions, SmartDialMatchPosition phoneNumberMatchPosition) { diff --git a/src/com/android/dialer/dialpad/SmartDialLoaderTask.java b/src/com/android/dialer/dialpad/SmartDialLoaderTask.java index 08c526503..216697d20 100644 --- a/src/com/android/dialer/dialpad/SmartDialLoaderTask.java +++ b/src/com/android/dialer/dialpad/SmartDialLoaderTask.java @@ -16,7 +16,7 @@ package com.android.dialer.dialpad; -import static com.android.dialer.dialpad.SmartDialAdapter.LOG_TAG; +import static com.android.dialer.dialpad.SmartDialController.LOG_TAG; import android.os.AsyncTask; import android.provider.ContactsContract; diff --git a/src/com/android/dialer/dialpad/SmartDialMatchPosition.java b/src/com/android/dialer/dialpad/SmartDialMatchPosition.java index 3d248ccb2..434874646 100644 --- a/src/com/android/dialer/dialpad/SmartDialMatchPosition.java +++ b/src/com/android/dialer/dialpad/SmartDialMatchPosition.java @@ -16,7 +16,7 @@ package com.android.dialer.dialpad; -import static com.android.dialer.dialpad.SmartDialAdapter.LOG_TAG; +import static com.android.dialer.dialpad.SmartDialController.LOG_TAG; import android.util.Log; @@ -25,7 +25,7 @@ import java.util.ArrayList; /** * Stores information about a range of characters matched in a display name The integers * start and end indicate that the range start to end (exclusive) correspond to some characters - * in the query. Used by {@link SmartDialAdapter} to highlight certain parts of the contact's + * in the query. Used by {@link SmartDialController} to highlight certain parts of the contact's * display name to indicate that those ranges matched the user's query. */ class SmartDialMatchPosition { diff --git a/src/com/android/dialer/dialpad/SmartDialTextView.java b/src/com/android/dialer/dialpad/SmartDialTextView.java index d1d570658..398f99ba5 100644 --- a/src/com/android/dialer/dialpad/SmartDialTextView.java +++ b/src/com/android/dialer/dialpad/SmartDialTextView.java @@ -17,14 +17,8 @@ package com.android.dialer.dialpad; import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.Rect; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; +import android.text.TextUtils; import android.util.AttributeSet; import android.widget.TextView; @@ -34,7 +28,6 @@ public class SmartDialTextView extends TextView { private final float mPadding; private final float mExtraPadding; - private static final String HIGH_CONFIDENCE_HINT = "\u2026"; public SmartDialTextView(Context context) { this(context, null); @@ -46,33 +39,6 @@ public class SmartDialTextView extends TextView { mExtraPadding = getResources().getDimension(R.dimen.smartdial_suggestions_extra_padding); } - /** - * Returns a drawable that resembles a sideways overflow icon. Used to indicate the presence - * of a high confidence match. - * - * @param res Resources that we will use to create our BitmapDrawable with - * @param textSize Size of drawable to create - * @param color Color of drawable to create - * @return The drawable drawn according to the given parameters - */ - public static Drawable getHighConfidenceHintDrawable(final Resources res, final float textSize, - final int color) { - final Paint paint = new Paint(); - paint.setAntiAlias(true); - paint.setTextAlign(Align.CENTER); - paint.setTextSize(textSize); - paint.setColor(color); - final Rect bounds = new Rect(); - paint.getTextBounds(HIGH_CONFIDENCE_HINT, 0, HIGH_CONFIDENCE_HINT.length(), bounds); - final int width = bounds.width(); - final int height = bounds.height(); - final Bitmap buffer = Bitmap.createBitmap( - width, (height * 3 / 2), Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(buffer); - canvas.drawText(HIGH_CONFIDENCE_HINT, width / 2, height, paint); - return new BitmapDrawable(res, buffer); - } - @Override protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { super.onTextChanged(text, start, lengthBefore, lengthAfter); @@ -94,8 +60,17 @@ public class SmartDialTextView extends TextView { float width = w - 2 * mPadding - 2 * mExtraPadding; float ratio = width / paint.measureText(getText().toString()); + TextUtils.TruncateAt ellipsizeAt = null; if (ratio < 1.0f) { - setTextScaleX(ratio); + if (ratio < 0.8f) { + // If the text is too big to fit even after scaling to 80%, just ellipsize it + // instead. + ellipsizeAt = TextUtils.TruncateAt.END; + setTextScaleX(0.8f); + } else { + setTextScaleX(ratio); + } } + setEllipsize(ellipsizeAt); } } |