Bag o' QC UX improvements
UX and I spent a couple days trying out different tweaks to QC.
This is the result.
Main Changes:
-landscape
-better blending, and interpolation of blended values
-different scrim animation length
-updated colors & dimensions
-scaling of title TextView during scroll
-EdgeEffect color is now dynamic
-Drop shadow size
Bug: 15725269
Change-Id: Ib992b41692704d3d932527cef715693ed7a7f4cc
diff --git a/res/layout-land/quickcontact_activity.xml b/res/layout-land/quickcontact_activity.xml
index 552f568..65fcd7d 100644
--- a/res/layout-land/quickcontact_activity.xml
+++ b/res/layout-land/quickcontact_activity.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
+<!-- Copyright (C) 2014 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.
@@ -13,45 +13,29 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout
+<com.android.contacts.widget.MultiShrinkScroller
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:ex="http://schemas.android.com/apk/res-auto"
- android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:padding="32dip"
- android:orientation="horizontal">
- <view
- class="com.android.contacts.common.widget.ProportionalLayout"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- ex:ratio="1.0"
- ex:direction="heightToWidth">
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <ImageView
- android:id="@+id/photo"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="centerCrop"
- android:contentDescription="@string/description_contact_photo" />
- <!-- Need to set a non null background on Toolbar in order for MenuItem
- ripples to be drawn on this view, instead of another-->
- <Toolbar
- android:layout_width="match_parent"
- android:layout_height="?android:attr/actionBarSize"
- android:background="#00000000"
- android:id="@+id/toolbar"/>
- </FrameLayout>
- </view>
- <com.android.contacts.quickcontact.ExpandingEntryCardView
- style="@style/ExpandingEntryCardStyle"
- android:id="@+id/communication_card"
- android:layout_marginTop="@dimen/communication_card_marginTop"
- android:visibility="gone" />
- <com.android.contacts.quickcontact.ExpandingEntryCardView
- style="@style/ExpandingEntryCardStyle"
- android:id="@+id/recent_card"
- android:visibility="gone" />
-</LinearLayout>
\ No newline at end of file
+ android:orientation="vertical"
+ android:id="@+id/multiscroller"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:descendantFocusability="afterDescendants" >
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/quickcontact_starting_empty_height"
+ android:id="@+id/transparent_view" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/quickcontact_header" />
+
+ <include layout="@layout/quickcontact_content" />
+
+ </LinearLayout>
+
+</com.android.contacts.widget.MultiShrinkScroller>
\ No newline at end of file
diff --git a/res/layout/expanding_entry_card_item.xml b/res/layout/expanding_entry_card_item.xml
index 890f2da..c038d1b 100644
--- a/res/layout/expanding_entry_card_item.xml
+++ b/res/layout/expanding_entry_card_item.xml
@@ -40,8 +40,7 @@
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/icon"
android:singleLine="true"
- android:textColor="@android:color/black"
- android:textStyle="bold" />
+ android:textColor="@android:color/black" />
<TextView
android:id="@+id/sub_header"
diff --git a/res/layout/quickcontact_activity.xml b/res/layout/quickcontact_activity.xml
index 13b8d9b..7b81ea2 100644
--- a/res/layout/quickcontact_activity.xml
+++ b/res/layout/quickcontact_activity.xml
@@ -29,54 +29,8 @@
android:layout_height="@dimen/quickcontact_starting_empty_height"
android:id="@+id/transparent_view" />
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@color/card_margin_color"
- android:id="@+id/toolbar_parent">
+ <include layout="@layout/quickcontact_header" />
- <ImageView
- android:id="@+id/photo"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="centerCrop"
- android:contentDescription="@string/description_contact_photo" />
-
- <!-- Need to set a non null background on Toolbar in order for MenuItem
- ripples to be drawn on this view, instead of another-->
- <Toolbar
- android:layout_width="match_parent"
- android:layout_height="?android:attr/actionBarSize"
- android:background="#00000000"
- android:id="@+id/toolbar"/>
-
- </FrameLayout>
-
- <com.android.contacts.widget.TouchlessScrollView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:fillViewport="true"
- android:id="@+id/content_scroller"
- android:background="@color/card_margin_color">
-
- <!-- All the cards should be inserted into this LinearLayout -->
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:id="@+id/card_container">
- <com.android.contacts.quickcontact.ExpandingEntryCardView
- style="@style/ExpandingEntryCardStyle"
- android:id="@+id/communication_card"
- android:layout_marginTop="@dimen/communication_card_marginTop"
- android:visibility="gone" />
-
- <com.android.contacts.quickcontact.ExpandingEntryCardView
- style="@style/ExpandingEntryCardStyle"
- android:id="@+id/recent_card"
- android:visibility="gone" />
- </LinearLayout>
-
- </com.android.contacts.widget.TouchlessScrollView>
+ <include layout="@layout/quickcontact_content" />
</com.android.contacts.widget.MultiShrinkScroller>
\ No newline at end of file
diff --git a/res/layout/quickcontact_content.xml b/res/layout/quickcontact_content.xml
new file mode 100644
index 0000000..b5b2a83
--- /dev/null
+++ b/res/layout/quickcontact_content.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<com.android.contacts.widget.TouchlessScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true"
+ android:id="@+id/content_scroller"
+ android:background="@color/card_margin_color">
+
+ <!-- All the cards should be inserted into this LinearLayout -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:id="@+id/card_container">
+ <com.android.contacts.quickcontact.ExpandingEntryCardView
+ style="@style/ExpandingEntryCardStyle"
+ android:id="@+id/communication_card"
+ android:layout_marginTop="@dimen/communication_card_marginTop"
+ android:visibility="gone" />
+
+ <com.android.contacts.quickcontact.ExpandingEntryCardView
+ style="@style/ExpandingEntryCardStyle"
+ android:id="@+id/recent_card"
+ android:visibility="gone" />
+ </LinearLayout>
+
+</com.android.contacts.widget.TouchlessScrollView>
\ No newline at end of file
diff --git a/res/layout/quickcontact_header.xml b/res/layout/quickcontact_header.xml
new file mode 100644
index 0000000..0d06917
--- /dev/null
+++ b/res/layout/quickcontact_header.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/card_margin_color"
+ android:id="@+id/toolbar_parent">
+
+ <com.android.contacts.widget.QuickContactImageView
+ android:id="@+id/photo"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerCrop"
+ android:contentDescription="@string/description_contact_photo" />
+
+ <!-- Need to set a non null background on Toolbar in order for MenuItem
+ ripples to be drawn on this view, instead of another-->
+ <Toolbar
+ android:layout_width="match_parent"
+ android:layout_height="?android:attr/actionBarSize"
+ android:background="#00000000"
+ android:id="@+id/toolbar"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/actionbar_text_color"
+ android:maxLines="@integer/quickcontact_title_lines"
+ android:ellipsize="end"
+ android:layout_gravity="bottom"
+ android:textSize="@dimen/quickcontact_maximum_title_size"
+ android:id="@+id/large_title"/>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/quickcontact_title_placeholder.xml b/res/layout/quickcontact_title_placeholder.xml
new file mode 100644
index 0000000..31d83ff
--- /dev/null
+++ b/res/layout/quickcontact_title_placeholder.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 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.
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent" >
+
+ <!-- Marks the location and size of the Activity title -->
+ <TextView
+ android:id="@+id/placeholder_textview"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@android:style/TextAppearance.Material.Widget.ActionBar.Title" />
+
+</FrameLayout>
+
+
diff --git a/res/values-land/bools.xml b/res/values-land/bools.xml
new file mode 100644
index 0000000..bd0650f
--- /dev/null
+++ b/res/values-land/bools.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<resources>
+
+ <bool name="quickcontact_two_panel">true</bool>
+
+</resources>
diff --git a/res/values-land/integers.xml b/res/values-land/integers.xml
index 010a5b7..08e1fe3 100644
--- a/res/values-land/integers.xml
+++ b/res/values-land/integers.xml
@@ -18,4 +18,7 @@
<integer name="contact_tile_column_count_in_favorites">5</integer>
<integer name="contact_tile_column_count">4</integer>
+
+ <!-- Number of lines the QuickContact title can have -->
+ <integer name="quickcontact_title_lines">2</integer>
</resources>
diff --git a/res/values-land/vals.xml b/res/values-land/vals.xml
new file mode 100644
index 0000000..ebcae31
--- /dev/null
+++ b/res/values-land/vals.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<resources>
+ <!-- The ratio of width:height for the contact's photo -->
+ <item name="quickcontact_photo_ratio" type="vals" format="float">0.7</item>
+</resources>
diff --git a/res/values/bools.xml b/res/values/bools.xml
new file mode 100644
index 0000000..663845a
--- /dev/null
+++ b/res/values/bools.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<resources>
+
+ <bool name="quickcontact_two_panel">false</bool>
+
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 962fe97..89f39b8 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -39,16 +39,16 @@
<color name="contacts_accent_color">#00acc1</color>
<!-- Color of the separator between entries in an ExpandingEntryCardView -->
- <color name="expanding_entry_card_item_separator_color">#eeeeee</color>
+ <color name="expanding_entry_card_item_separator_color">#e4e4e4</color>
<!-- Color of the text on an ExpandingEntryCard button -->
<color name="expanding_entry_card_button_text_color">@android:color/black</color>
<!-- Background color for an ExpandingEntryCard -->
- <color name="expanding_entry_card_background_color">#f7f8f9</color>
+ <color name="expanding_entry_card_background_color">#ffffff</color>
<!-- Color of the margin for cards -->
- <color name="card_margin_color">#ffbbbbbb</color>
+ <color name="card_margin_color">#f4f4f4</color>
<color name="call_arrow_green">#2aad6f</color>
<color name="call_arrow_red">#ff2e58</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 99bc345..9d8ba70 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -18,8 +18,8 @@
<!-- Initial height of transparent space above QuickContacts -->
<dimen name="quickcontact_starting_empty_height">150dp</dimen>
- <!-- Initial height of QuickContact's header/avatar-photo -->
- <dimen name="quickcontact_starting_header_height">200dp</dimen>
+ <!-- Initial size of QuickContact's title size -->
+ <dimen name="quickcontact_maximum_title_size">36dp</dimen>
<!-- Top padding of the entire contact editor -->
<dimen name="editor_padding_top">0dip</dimen>
@@ -147,9 +147,9 @@
<dimen name="expanding_entry_card_marginBottom">8dp</dimen>
<!-- Elevation of an ExpandingEntryCard, for the sake of shadow casting -->
- <dimen name="expanding_entry_card_elevation">2dp</dimen>
+ <dimen name="expanding_entry_card_elevation">1dp</dimen>
<!-- Elevation of the QuickContact's Toolbar, for the sake of shadow casting -->
- <dimen name="quick_contact_toolbar_elevation">6dp</dimen>
+ <dimen name="quick_contact_toolbar_elevation">4.5dp</dimen>
<!-- Top margin for the communication card, used to add space from header. -->
<dimen name="communication_card_marginTop">8dp</dimen>
diff --git a/res/values/integers.xml b/res/values/integers.xml
index a6b43d7..ff34d11 100644
--- a/res/values/integers.xml
+++ b/res/values/integers.xml
@@ -20,4 +20,7 @@
<!-- Determines the number of columns in a ContactTileRow -->
<integer name="contact_tile_column_count">2</integer>
+
+ <!-- Number of lines the QuickContact title can have -->
+ <integer name="quickcontact_title_lines">1</integer>
</resources>
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index 303dae5..1453ff4 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -123,11 +123,13 @@
private static final String TAG = "QuickContact";
+ private static final String KEY_THEME_COLOR = "theme_color";
+
private static final int ANIMATION_SLIDE_OPEN_DURATION = 250;
- private static final int ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION = 75;
+ private static final int ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION = 150;
private static final int REQUEST_CODE_CONTACT_EDITOR_ACTIVITY = 1;
private static final float SYSTEM_BAR_BRIGHTNESS_FACTOR = 0.7f;
- private static final int SHIM_COLOR = Color.argb(0x7F, 0, 0, 0);
+ private static final int SCRIM_COLOR = Color.argb(0xB2, 0, 0, 0);
/** This is the Intent action to install a shortcut in the launcher. */
private static final String ACTION_INSTALL_SHORTCUT =
@@ -149,7 +151,7 @@
private MultiShrinkScroller mScroller;
private SelectAccountDialogFragmentListener mSelectAccountFragmentListener;
private AsyncTask<Void, Void, Void> mEntriesAndActionsTask;
- private ColorDrawable mWindowShim;
+ private ColorDrawable mWindowScrim;
private boolean mIsWaitingForOtherPieceOfExitAnimation;
private boolean mIsExitAnimationInProgress;
@@ -158,6 +160,7 @@
private Contact mContactData;
private ContactLoader mContactLoader;
+ private PorterDuffColorFilter mColorFilter;
private final ImageViewDrawableSetter mPhotoSetter = new ImageViewDrawableSetter();
@@ -292,7 +295,7 @@
public void onStartScrollOffBottom() {
// Remove the window shim now that we are starting an Activity exit animation.
final int duration = getResources().getInteger(android.R.integer.config_shortAnimTime);
- final ObjectAnimator animator = ObjectAnimator.ofInt(mWindowShim, "alpha", 0xFF, 0);
+ final ObjectAnimator animator = ObjectAnimator.ofInt(mWindowScrim, "alpha", 0xFF, 0);
animator.addListener(mExitWindowShimAnimationListener);
animator.setDuration(duration).start();
mIsWaitingForOtherPieceOfExitAnimation = true;
@@ -354,15 +357,18 @@
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setActionBar(toolbar);
- setHeaderNameText(R.string.missing_name);
+ getActionBar().setTitle(null);
+ // Put a TextView with a known resource id into the ActionBar. This allows us to easily
+ // find the correct TextView location & size later.
+ toolbar.addView(getLayoutInflater().inflate(R.layout.quickcontact_title_placeholder, null));
mHasAlreadyBeenOpened = savedInstanceState != null;
- mWindowShim = new ColorDrawable(SHIM_COLOR);
- getWindow().setBackgroundDrawable(mWindowShim);
+ mWindowScrim = new ColorDrawable(SCRIM_COLOR);
+ getWindow().setBackgroundDrawable(mWindowScrim);
if (!mHasAlreadyBeenOpened) {
final int duration = getResources().getInteger(android.R.integer.config_shortAnimTime);
- ObjectAnimator.ofInt(mWindowShim, "alpha", 0, 0xFF).setDuration(duration).start();
+ ObjectAnimator.ofInt(mWindowScrim, "alpha", 0, 0xFF).setDuration(duration).start();
}
if (mScroller != null) {
@@ -377,6 +383,8 @@
}
}
+ setHeaderNameText(R.string.missing_name);
+
mSelectAccountFragmentListener= (SelectAccountDialogFragmentListener) getFragmentManager()
.findFragmentByTag(FRAGMENT_TAG_SELECT_ACCOUNT);
if (mSelectAccountFragmentListener == null) {
@@ -387,6 +395,21 @@
}
mSelectAccountFragmentListener.setQuickContactActivity(this);
+ if (savedInstanceState != null) {
+ final int color = savedInstanceState.getInt(KEY_THEME_COLOR, 0);
+ if (color != 0) {
+ // Wait for pre draw. Setting the header tint before the MultiShrinkScroller has
+ // been measured will cause incorrect tinting calculations.
+ SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ true,
+ new Runnable() {
+ @Override
+ public void run() {
+ setThemeColor(color);
+ }
+ });
+ }
+ }
+
Trace.endSection();
}
@@ -406,6 +429,14 @@
processIntent(intent);
}
+ @Override
+ public void onSaveInstanceState(Bundle savedInstanceState) {
+ super.onSaveInstanceState(savedInstanceState);
+ if (mColorFilter != null) {
+ savedInstanceState.putInt(KEY_THEME_COLOR, mColorFilter.getColor());
+ }
+ }
+
private void processIntent(Intent intent) {
Uri lookupUri = intent.getData();
@@ -450,13 +481,17 @@
/** Assign this string to the view if it is not empty. */
private void setHeaderNameText(int resId) {
- getActionBar().setTitle(getText(resId));
+ if (mScroller != null) {
+ mScroller.setTitle(String.valueOf(getText(resId)));
+ }
}
/** Assign this string to the view if it is not empty. */
private void setHeaderNameText(CharSequence value) {
if (!TextUtils.isEmpty(value)) {
- getActionBar().setTitle(value);
+ if (mScroller != null) {
+ mScroller.setTitle(value.toString());
+ }
}
}
@@ -700,24 +735,7 @@
return colorFromBitmap(bitmap);
}
if (imageViewDrawable instanceof LetterTileDrawable) {
- // LetterTileDrawable doesn't normally draw unless it is visible. Therefore,
- // we need to directly ask it for its color via getColor(). We could directly
- // return this color. However, in the future Palette#generate() may incorporate
- // saturation boosting. So I want to use Palette#generate() for the sake of
- // consistency.
- final LetterTileDrawable tileDrawable = (LetterTileDrawable) imageViewDrawable;
- final int PALETTE_BITMAP_SIZE = 1;
- final Bitmap bitmap = Bitmap.createBitmap(PALETTE_BITMAP_SIZE,
- PALETTE_BITMAP_SIZE, Bitmap.Config.ARGB_8888);
- // If Palette can not extract a primary color, our UX person says we are better
- // off using the LetterTileDrawable's non vibrant color than falling back
- // to the app's default color.
- final int color = colorFromBitmap(bitmap);
- if (color == 0) {
- return tileDrawable.getColor();
- } else {
- return color;
- }
+ return ((LetterTileDrawable) imageViewDrawable).getColor();
}
return 0;
}
@@ -725,34 +743,38 @@
@Override
protected void onPostExecute(Integer color) {
super.onPostExecute(color);
- // Check that the Photo has not changed. If it has changed, the new tint color
- // needs to be extracted
- if (imageViewDrawable == mPhotoView.getDrawable()) {
- // If the color is invalid, use the predefined default
- if (color == 0) {
- color = getResources().getColor(R.color.actionbar_background_color);
- }
- // TODO: animate from the previous tint.
- mScroller.setHeaderTintColor(color);
-
- // Create a darker version of the actionbar color. HSV is device dependent
- // and not perceptually-linear. Therefore, we can't say mStatusBarColor is
- // 70% as bright as the action bar color. We can only say: it is a bit darker.
- final float hsvComponents[] = new float[3];
- Color.colorToHSV(color, hsvComponents);
- hsvComponents[2] *= SYSTEM_BAR_BRIGHTNESS_FACTOR;
- mStatusBarColor = Color.HSVToColor(hsvComponents);
-
- updateStatusBarColor();
- final ColorFilter colorFilter =
- new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP);
- mCommunicationCard.setColorAndFilter(color, colorFilter);
- mRecentCard.setColorAndFilter(color, colorFilter);
+ // Make sure the color is valid. Also check that the Photo has not changed. If it
+ // has changed, the new tint color needs to be extracted
+ if (color != 0 && imageViewDrawable == mPhotoView.getDrawable()) {
+ setThemeColor(color);
}
}
}.execute();
}
+ private void setThemeColor(int color) {
+ // If the color is invalid, use the predefined default
+ if (color == 0) {
+ color = getResources().getColor(R.color.actionbar_background_color);
+ }
+ // TODO: animate from the previous tint.
+ mScroller.setHeaderTintColor(color);
+
+ // Create a darker version of the actionbar color. HSV is device dependent
+ // and not perceptually-linear. Therefore, we can't say mStatusBarColor is
+ // 70% as bright as the action bar color. We can only say: it is a bit darker.
+ final float hsvComponents[] = new float[3];
+ Color.colorToHSV(color, hsvComponents);
+ hsvComponents[2] *= SYSTEM_BAR_BRIGHTNESS_FACTOR;
+ mStatusBarColor = Color.HSVToColor(hsvComponents);
+
+ updateStatusBarColor();
+ mColorFilter =
+ new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP);
+ mCommunicationCard.setColorAndFilter(color, mColorFilter);
+ mRecentCard.setColorAndFilter(color, mColorFilter);
+ }
+
private void updateStatusBarColor() {
if (mScroller == null) {
return;
diff --git a/src/com/android/contacts/widget/MultiShrinkScroller.java b/src/com/android/contacts/widget/MultiShrinkScroller.java
index 96a3d0b..3666420 100644
--- a/src/com/android/contacts/widget/MultiShrinkScroller.java
+++ b/src/com/android/contacts/widget/MultiShrinkScroller.java
@@ -11,10 +11,14 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
import android.util.AttributeSet;
+import android.util.TypedValue;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.MotionEvent;
@@ -22,12 +26,14 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewConfiguration;
+import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.widget.EdgeEffect;
-import android.widget.ImageView;
+import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.Scroller;
import android.widget.ScrollView;
+import android.widget.TextView;
/**
* A custom {@link ViewGroup} that operates similarly to a {@link ScrollView}, except with multiple
@@ -54,6 +60,11 @@
*/
private static final int EXIT_FLING_ANIMATION_DURATION_MS = 300;
+ /**
+ * In portrait mode, the height:width ratio of the photo's starting height.
+ */
+ private static final float INTERMEDIATE_HEADER_HEIGHT_RATIO = 0.5f;
+
private float[] mLastEventPosition = { 0, 0 };
private VelocityTracker mVelocityTracker;
private boolean mIsBeingDragged = false;
@@ -62,24 +73,49 @@
private ScrollView mScrollView;
private View mScrollViewChild;
private View mToolbar;
- private ImageView mPhotoView;
+ private QuickContactImageView mPhotoView;
private View mPhotoViewContainer;
private View mTransparentView;
private MultiShrinkScrollerListener mListener;
+ private TextView mLargeTextView;
+ /** Contains desired location/size of the title, once the header is fully compressed */
+ private TextView mInvisiblePlaceholderTextView;
private int mHeaderTintColor;
private int mMaximumHeaderHeight;
+ private int mMinimumHeaderHeight;
+ private int mIntermediateHeaderHeight;
+ private int mMaximumHeaderTextSize;
private final Scroller mScroller;
private final EdgeEffect mEdgeGlowBottom;
private final int mTouchSlop;
private final int mMaximumVelocity;
private final int mMinimumVelocity;
- private final int mIntermediateHeaderHeight;
- private final int mMinimumHeaderHeight;
private final int mTransparentStartHeight;
private final float mToolbarElevation;
- private final PorterDuffColorFilter mColorFilter
- = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
+ private final boolean mIsTwoPanel;
+ final Rect mLargeTextViewRect = new Rect();
+ final Rect mInvisiblePlaceholderTextViewRect = new Rect();
+
+ // Objects used to perform color filtering on the header. These are stored as fields for
+ // the sole purpose of avoiding "new" operations inside animation loops.
+ private final ColorMatrix mWhitenessColorMatrix = new ColorMatrix();
+ private final ColorMatrixColorFilter mColorFilter = new ColorMatrixColorFilter(
+ mWhitenessColorMatrix);
+ private final ColorMatrix mColorMatrix = new ColorMatrix();
+ private final float[] mAlphaMatrixValues = {
+ 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0
+ };
+ private final ColorMatrix mMultiplyBlendMatrix = new ColorMatrix();
+ private final float[] mMultiplyBlendMatrixValues = {
+ 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0
+ };
public interface MultiShrinkScrollerListener {
void onScrolledOffBottom();
@@ -148,12 +184,11 @@
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
- mIntermediateHeaderHeight = (int) getResources().getDimension(
- R.dimen.quickcontact_starting_header_height);
mTransparentStartHeight = (int) getResources().getDimension(
R.dimen.quickcontact_starting_empty_height);
mToolbarElevation = mContext.getResources().getDimension(
R.dimen.quick_contact_toolbar_elevation);
+ mIsTwoPanel = mContext.getResources().getBoolean(R.bool.quickcontact_two_panel);
final TypedArray attributeArray = context.obtainStyledAttributes(
new int[]{android.R.attr.actionBarSize});
@@ -170,10 +205,11 @@
mToolbar = findViewById(R.id.toolbar_parent);
mPhotoViewContainer = findViewById(R.id.toolbar_parent);
mTransparentView = findViewById(R.id.transparent_view);
+ mLargeTextView = (TextView) findViewById(R.id.large_title);
+ mInvisiblePlaceholderTextView = (TextView) findViewById(R.id.placeholder_textview);
mListener = listener;
- mPhotoView = (ImageView) findViewById(R.id.photo);
- setHeaderHeight(mIntermediateHeaderHeight);
+ mPhotoView = (QuickContactImageView) findViewById(R.id.photo);
mPhotoView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
@@ -181,15 +217,49 @@
}
});
- SchedulingUtils.doOnPreDraw(this, /* drawNextFrame = */ true, new Runnable() {
+ final WindowManager windowManager = (WindowManager) getContext().getSystemService(
+ Context.WINDOW_SERVICE);
+ final Point windowSize = new Point();
+ windowManager.getDefaultDisplay().getSize(windowSize);
+ if (!mIsTwoPanel) {
+ // We never want the height of the photo view to exceed its width.
+ mMaximumHeaderHeight = windowSize.x;
+ mIntermediateHeaderHeight = (int) (mMaximumHeaderHeight
+ * INTERMEDIATE_HEADER_HEIGHT_RATIO);
+ }
+ setHeaderHeight(mIntermediateHeaderHeight);
+
+ SchedulingUtils.doOnPreDraw(this, /* drawNextFrame = */ false, new Runnable() {
@Override
public void run() {
- // We never want the height of the photo view to exceed its width.
- mMaximumHeaderHeight = mToolbar.getWidth();
+ mMaximumHeaderTextSize = mLargeTextView.getHeight();
+ // Unlike Window width, we can't know the usable window height until predraw
+ // has occured. Therefore, setting these constraints must be done inside
+ // onPreDraw for the two panel layout. Fortunately, the two panel layout
+ // doesn't need these values anywhere else inside the activity's creation.
+ if (mIsTwoPanel) {
+ mMaximumHeaderHeight = getHeight();
+ mMinimumHeaderHeight = mMaximumHeaderHeight;
+ mIntermediateHeaderHeight = mMaximumHeaderHeight;
+ final TypedValue photoRatio = new TypedValue();
+ getResources().getValue(R.vals.quickcontact_photo_ratio, photoRatio,
+ /* resolveRefs = */ true);
+ final LayoutParams layoutParams
+ = (LayoutParams) mPhotoViewContainer.getLayoutParams();
+ layoutParams.height = mMaximumHeaderHeight;
+ layoutParams.width = (int) (mMaximumHeaderHeight * photoRatio.getFloat());
+ mPhotoViewContainer.setLayoutParams(layoutParams);
+ }
+
+ updateHeaderTextSize();
}
});
}
+ public void setTitle(String title) {
+ mLargeTextView.setText(title);
+ }
+
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// The only time we want to intercept touch events is when we are being dragged.
@@ -257,14 +327,13 @@
mReceivedDown = false;
if (mIsBeingDragged) {
- final int heightScrollViewChild = mScrollViewChild.getHeight();
- final int pulledToY = mScrollView.getScrollY() + (int) delta;
- if (pulledToY > heightScrollViewChild - mScrollView.getHeight()
- && mToolbar.getHeight() == mMinimumHeaderHeight) {
+ final int distanceFromMaxScrolling = getMaximumScrollUpwards() - getScroll();
+ if (delta > distanceFromMaxScrolling) {
// The ScrollView is being pulled upwards while there is no more
// content offscreen, and the view port is already fully expanded.
mEdgeGlowBottom.onPull(delta / getHeight(), 1 - event.getX() / getWidth());
}
+
if (!mEdgeGlowBottom.isFinished()) {
postInvalidateOnAnimation();
}
@@ -285,6 +354,9 @@
public void setHeaderTintColor(int color) {
mHeaderTintColor = color;
updatePhotoTintAndDropShadow();
+ // We want to use the same amount of alpha on the new tint color as the previous tint color.
+ final int edgeEffectAlpha = Color.alpha(mEdgeGlowBottom.getColor());
+ mEdgeGlowBottom.setColor((color & 0xffffff) | Color.argb(edgeEffectAlpha, 0, 0, 0));
}
/**
@@ -399,6 +471,7 @@
scrollDown(delta);
}
updatePhotoTintAndDropShadow();
+ updateHeaderTextSize();
final boolean isFullscreen = getScrollNeededToBeFullScreen() <= 0;
if (mListener != null) {
if (wasFullscreen && !isFullscreen) {
@@ -419,6 +492,7 @@
toolbarLayoutParams.height = height;
mToolbar.setLayoutParams(toolbarLayoutParams);
updatePhotoTintAndDropShadow();
+ updateHeaderTextSize();
}
@NeededForReflection
@@ -510,7 +584,12 @@
height + getMaximumScrollUpwards() - getScroll());
canvas.rotate(180, width, 0);
- mEdgeGlowBottom.setSize(width, height);
+ if (mIsTwoPanel) {
+ // Only show the EdgeEffect on the bottom of the ScrollView.
+ mEdgeGlowBottom.setSize(mScrollView.getWidth(), height);
+ } else {
+ mEdgeGlowBottom.setSize(width, height);
+ }
if (mEdgeGlowBottom.draw(canvas)) {
postInvalidateOnAnimation();
}
@@ -535,11 +614,18 @@
}
private int getMaximumScrollUpwards() {
- return mTransparentStartHeight
- // How much the Header view can compress
- + mIntermediateHeaderHeight - mMinimumHeaderHeight
- // How much the ScrollView can scroll. 0, if child is smaller than ScrollView.
- + Math.max(0, mScrollViewChild.getHeight() - getHeight() + mMinimumHeaderHeight);
+ if (!mIsTwoPanel) {
+ return mTransparentStartHeight
+ // How much the Header view can compress
+ + mIntermediateHeaderHeight - mMinimumHeaderHeight
+ // How much the ScrollView can scroll. 0, if child is smaller than ScrollView.
+ + Math.max(0, mScrollViewChild.getHeight() - getHeight()
+ + mMinimumHeaderHeight);
+ } else {
+ return mTransparentStartHeight
+ // How much the ScrollView can scroll. 0, if child is smaller than ScrollView.
+ + Math.max(0, mScrollViewChild.getHeight() - getHeight());
+ }
}
private int getTransparentViewHeight() {
@@ -602,27 +688,167 @@
}
}
+ /**
+ * Set the header size and padding, based on the current scroll position.
+ */
+ private void updateHeaderTextSize() {
+ if (mIsTwoPanel) {
+ // The text size stays constant on two panel layouts.
+ return;
+ }
+
+ // The pivot point for scaling should be middle of the starting side.
+ if (isLayoutRtl()) {
+ mLargeTextView.setPivotX(mLargeTextView.getWidth());
+ } else {
+ mLargeTextView.setPivotX(0);
+ }
+ mLargeTextView.setPivotY(mLargeTextView.getHeight() / 2);
+
+ final int START_TEXT_SCALING_THRESHOLD_COEFFICIENT = 2;
+ final int threshold = START_TEXT_SCALING_THRESHOLD_COEFFICIENT * mMinimumHeaderHeight;
+ final int toolbarHeight = mToolbar.getLayoutParams().height;
+ if (toolbarHeight >= threshold) {
+ // Keep the text at maximum size since the header is smaller than threshold.
+ mLargeTextView.setScaleX(1);
+ mLargeTextView.setScaleY(1);
+ configureLargeTitlePadding();
+ return;
+ }
+ final float ratio = (toolbarHeight - mMinimumHeaderHeight)
+ / (float)(threshold - mMinimumHeaderHeight);
+ final float minimumSize = mInvisiblePlaceholderTextView.getHeight();
+ final float scale = (minimumSize + (mMaximumHeaderTextSize - minimumSize) * ratio)
+ / mMaximumHeaderTextSize;
+
+ mLargeTextView.setScaleX(scale);
+ mLargeTextView.setScaleY(scale);
+ configureLargeTitlePadding();
+ }
+
+ /**
+ * Configure the padding around mLargeTextView so that it will look appropriate once it
+ * finishes moving into its target location/size.
+ */
+ private void configureLargeTitlePadding() {
+ mToolbar.getBoundsOnScreen(mLargeTextViewRect);
+ mInvisiblePlaceholderTextView.getBoundsOnScreen(mInvisiblePlaceholderTextViewRect);
+ final int neededPaddingStart;
+ if (isLayoutRtl()) {
+ neededPaddingStart = mInvisiblePlaceholderTextViewRect.right - mLargeTextViewRect.right;
+ } else {
+ neededPaddingStart = mInvisiblePlaceholderTextViewRect.left - mLargeTextViewRect.left;
+ }
+
+ // Distance between top of toolbar to the center of the target rectangle.
+ final int desiredTopToCenter = (
+ mInvisiblePlaceholderTextViewRect.top + mInvisiblePlaceholderTextViewRect.bottom)
+ / 2 - mLargeTextViewRect.top;
+ // Additional padding needed on the mLargeTextView so that it has the same amount of
+ // padding as the target rectangle.
+ final int additionalBottomPaddingNeeded = desiredTopToCenter - mLargeTextView.getHeight()
+ / 2;
+
+ final FrameLayout.LayoutParams layoutParams
+ = (FrameLayout.LayoutParams) mLargeTextView.getLayoutParams();
+ layoutParams.bottomMargin = additionalBottomPaddingNeeded;
+ layoutParams.setMarginStart(neededPaddingStart);
+ mLargeTextView.setLayoutParams(layoutParams);
+ }
+
private void updatePhotoTintAndDropShadow() {
// We need to use toolbarLayoutParams to determine the height, since the layout
// params can be updated before the height change is reflected inside the View#getHeight().
final int toolbarHeight = mToolbar.getLayoutParams().height;
+
+ if (toolbarHeight <= mMinimumHeaderHeight && !mIsTwoPanel) {
+ mPhotoViewContainer.setElevation(mToolbarElevation);
+ } else {
+ mPhotoViewContainer.setElevation(0);
+ }
+
// Reuse an existing mColorFilter (to avoid GC pauses) to change the photo's tint.
mPhotoView.clearColorFilter();
- if (toolbarHeight >= mMaximumHeaderHeight) {
- mPhotoViewContainer.setElevation(0);
- return;
+
+ final float ratio;
+ final float intermediateRatio;
+ if (!mIsTwoPanel) {
+ // Ratio of current size to maximum size of the header.
+ ratio = (toolbarHeight - mMinimumHeaderHeight)
+ / (float) (mMaximumHeaderHeight - mMinimumHeaderHeight) ;
+ // The value that "ratio" will have when the header is at its
+ // starting/intermediate size.
+ intermediateRatio = (mIntermediateHeaderHeight - mMinimumHeaderHeight)
+ / (float) (mMaximumHeaderHeight - mMinimumHeaderHeight);
+ } else {
+ // Set ratio and intermediateRatio to the same arbitrary value, so that
+ // the math below considers us to be in the intermediate position. The specific
+ // values are not very important.
+ ratio = 0.5f;
+ intermediateRatio = 0.5f;
}
- if (toolbarHeight <= mMinimumHeaderHeight) {
- mColorFilter.setColor(mHeaderTintColor);
- mPhotoView.setColorFilter(mColorFilter);
- mPhotoViewContainer.setElevation(mToolbarElevation);
+
+ final float linearBeforeMiddle = Math.max(1 - (1 - ratio) / intermediateRatio, 0);
+
+ // Want a function with a derivative of 0 at x=0. I don't want it to grow too
+ // slowly before x=0.5. x^1.1 satisfies both requirements.
+ final float EXPONENT_ALMOST_ONE = 1.1f;
+ final float semiLinearBeforeMiddle = (float) Math.pow(linearBeforeMiddle,
+ EXPONENT_ALMOST_ONE);
+ mColorMatrix.reset();
+ mColorMatrix.setSaturation(semiLinearBeforeMiddle);
+ mColorMatrix.postConcat(alphaMatrix(ratio, Color.WHITE));
+
+ final float colorAlpha;
+ if (mPhotoView.isBasedOffLetterTile()) {
+ // Since the letter tile only has white and grey, tint it more slowly. Otherwise
+ // it will be completely invisible before we reach the intermediate point.
+ final float SLOWING_FACTOR = 1.6f;
+ float linearBeforeMiddleish = Math.max(1 - (1 - ratio) / intermediateRatio
+ / SLOWING_FACTOR, 0);
+ colorAlpha = 1 - (float) Math.pow(linearBeforeMiddleish, EXPONENT_ALMOST_ONE);
+ mColorMatrix.postConcat(alphaMatrix(colorAlpha, mHeaderTintColor));
+ } else {
+ colorAlpha = 1 - semiLinearBeforeMiddle;
+ mColorMatrix.postConcat(multiplyBlendMatrix(mHeaderTintColor, colorAlpha));
}
- mPhotoViewContainer.setElevation(0);
- final int alphaBits = 0xff - 0xff * (toolbarHeight - mMinimumHeaderHeight)
- / (mMaximumHeaderHeight - mMinimumHeaderHeight);
- final int color = alphaBits << 24 | (mHeaderTintColor & 0xffffff);
- mColorFilter.setColor(color);
+
+ mColorFilter.setColorMatrix(mColorMatrix);
mPhotoView.setColorFilter(mColorFilter);
+ // Tell the photo view what tint we are trying to achieve. Depending on the type of
+ // drawable used, the photo view may or may not use this tint.
+ mPhotoView.setTint(((int) (0xFF * colorAlpha)) << 24 | (mHeaderTintColor & 0xffffff));
+ }
+
+ /**
+ * Simulates alpha blending an image with {@param color}.
+ */
+ private ColorMatrix alphaMatrix(float alpha, int color) {
+ mAlphaMatrixValues[0] = Color.red(color) * alpha / 255;
+ mAlphaMatrixValues[6] = Color.green(color) * alpha / 255;
+ mAlphaMatrixValues[12] = Color.blue(color) * alpha / 255;
+ mAlphaMatrixValues[4] = 255 * (1 - alpha);
+ mAlphaMatrixValues[9] = 255 * (1 - alpha);
+ mAlphaMatrixValues[14] = 255 * (1 - alpha);
+ mWhitenessColorMatrix.set(mAlphaMatrixValues);
+ return mWhitenessColorMatrix;
+ }
+
+ /**
+ * Simulates multiply blending an image with a single {@param color}.
+ *
+ * Multiply blending is [Sa * Da, Sc * Dc]. See {@link android.graphics.PorterDuff}.
+ */
+ private ColorMatrix multiplyBlendMatrix(int color, float alpha) {
+ mMultiplyBlendMatrixValues[0] = multiplyBlend(Color.red(color), alpha);
+ mMultiplyBlendMatrixValues[6] = multiplyBlend(Color.green(color), alpha);
+ mMultiplyBlendMatrixValues[12] = multiplyBlend(Color.blue(color), alpha);
+ mMultiplyBlendMatrix.set(mMultiplyBlendMatrixValues);
+ return mMultiplyBlendMatrix;
+ }
+
+ private float multiplyBlend(int color, float alpha) {
+ return color * alpha / 255.0f + (1 - alpha);
}
private void updateLastEventPosition(MotionEvent event) {
diff --git a/src/com/android/contacts/widget/QuickContactImageView.java b/src/com/android/contacts/widget/QuickContactImageView.java
new file mode 100644
index 0000000..9dbf85e
--- /dev/null
+++ b/src/com/android/contacts/widget/QuickContactImageView.java
@@ -0,0 +1,85 @@
+package com.android.contacts.widget;
+
+
+import com.android.contacts.common.lettertiles.LetterTileDrawable;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Xfermode;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+/**
+ * An {@link ImageView} designed to display QuickContact's contact photo. In addition to
+ * supporting {@link ImageView#setColorFilter} this also performs a second color blending with
+ * the tint set in {@link #setTint}. This requires a second draw pass.
+ */
+public class QuickContactImageView extends ImageView {
+
+ private Xfermode mMode = new PorterDuffXfermode(Mode.MULTIPLY);
+ private int mTintColor;
+ private BitmapDrawable mBitmapDrawable;
+ private Drawable mOriginalDrawable;
+
+ public QuickContactImageView(Context context) {
+ this(context, null);
+ }
+
+ public QuickContactImageView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public QuickContactImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public QuickContactImageView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public void setTint(int color) {
+ mTintColor = color;
+ postInvalidate();
+ }
+
+ public boolean isBasedOffLetterTile() {
+ return mOriginalDrawable instanceof LetterTileDrawable;
+ }
+
+ @Override
+ public void setImageDrawable(Drawable drawable) {
+ // There is no way to avoid all this casting. Blending modes aren't equally
+ // supported for all drawable types.
+ if (drawable == null || drawable instanceof BitmapDrawable) {
+ mBitmapDrawable = (BitmapDrawable) drawable;
+ } else if (drawable instanceof LetterTileDrawable) {
+ // TODO: set a desired hardcoded BitmapDrawable here
+ mBitmapDrawable = null;
+ } else {
+ throw new IllegalArgumentException("Does not support this type of drawable");
+
+ }
+ mOriginalDrawable = drawable;
+ super.setImageDrawable(mBitmapDrawable);
+ }
+
+ @Override
+ public Drawable getDrawable() {
+ return mOriginalDrawable;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (isBasedOffLetterTile()) {
+ // The LetterTileDrawable's bitmaps have a lot of pixels with alpha=0. These
+ // look stupid unless we fill in the background and use a different blending mode.
+ canvas.drawColor(((LetterTileDrawable) mOriginalDrawable).getColor());
+ }
+ super.onDraw(canvas);
+ }
+}