summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLinus Lee <llee@cyngn.com>2015-03-25 00:15:10 (GMT)
committerGerrit Code Review <gerrit@cyanogenmod.org>2015-04-02 00:56:26 (GMT)
commite002b691b3233629598c77ef50f1ef91eb1877f0 (patch)
tree11c849d031fa02162471b3aa1df40e6e9a9df77b
parenta9f83600fe59ea8bf65ae6dca8dc5239c529de7b (diff)
downloadandroid_packages_apps_Trebuchet-e002b691b3233629598c77ef50f1ef91eb1877f0.zip
android_packages_apps_Trebuchet-e002b691b3233629598c77ef50f1ef91eb1877f0.tar.gz
android_packages_apps_Trebuchet-e002b691b3233629598c77ef50f1ef91eb1877f0.tar.bz2
App drawer: add animations and letters to make things pretty
Change-Id: Iaf6718893e09df715b4524b864be9a7c22addc59
-rw-r--r--res/drawable-hdpi/letter_indicator.pngbin0 -> 21583 bytes
-rw-r--r--res/drawable-mdpi/letter_indicator.pngbin0 -> 4095 bytes
-rw-r--r--res/drawable-nodpi/letter_indicator.9.pngbin3614 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/letter_indicator.pngbin0 -> 25268 bytes
-rw-r--r--res/drawable-xxhdpi/letter_indicator.pngbin0 -> 34597 bytes
-rw-r--r--res/drawable/empty_seek_bar.xml19
-rw-r--r--res/layout/app_drawer_container.xml12
-rw-r--r--res/layout/app_drawer_item.xml58
-rw-r--r--res/layout/scrub_layout.xml46
-rw-r--r--res/values/colors.xml3
-rw-r--r--res/values/dimens.xml2
-rw-r--r--src/com/android/launcher3/AppDrawerListAdapter.java215
-rw-r--r--src/com/android/launcher3/AppDrawerScrubber.java239
-rw-r--r--src/com/android/launcher3/AppDrawerScrubberSections.java244
-rw-r--r--src/com/android/launcher3/AutoExpandTextView.java246
-rw-r--r--src/com/android/launcher3/Launcher.java47
16 files changed, 1010 insertions, 121 deletions
diff --git a/res/drawable-hdpi/letter_indicator.png b/res/drawable-hdpi/letter_indicator.png
new file mode 100644
index 0000000..4770d81
--- /dev/null
+++ b/res/drawable-hdpi/letter_indicator.png
Binary files differ
diff --git a/res/drawable-mdpi/letter_indicator.png b/res/drawable-mdpi/letter_indicator.png
new file mode 100644
index 0000000..2ecfe7c
--- /dev/null
+++ b/res/drawable-mdpi/letter_indicator.png
Binary files differ
diff --git a/res/drawable-nodpi/letter_indicator.9.png b/res/drawable-nodpi/letter_indicator.9.png
deleted file mode 100644
index af3578e..0000000
--- a/res/drawable-nodpi/letter_indicator.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/letter_indicator.png b/res/drawable-xhdpi/letter_indicator.png
new file mode 100644
index 0000000..6f21860
--- /dev/null
+++ b/res/drawable-xhdpi/letter_indicator.png
Binary files differ
diff --git a/res/drawable-xxhdpi/letter_indicator.png b/res/drawable-xxhdpi/letter_indicator.png
new file mode 100644
index 0000000..acbacb0
--- /dev/null
+++ b/res/drawable-xxhdpi/letter_indicator.png
Binary files differ
diff --git a/res/drawable/empty_seek_bar.xml b/res/drawable/empty_seek_bar.xml
deleted file mode 100644
index e3cf61d..0000000
--- a/res/drawable/empty_seek_bar.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 The CyanogenMod 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.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="#00000000"/>
-</shape> \ No newline at end of file
diff --git a/res/layout/app_drawer_container.xml b/res/layout/app_drawer_container.xml
index f5db08c..6024ddd 100644
--- a/res/layout/app_drawer_container.xml
+++ b/res/layout/app_drawer_container.xml
@@ -71,12 +71,14 @@
<TextView
android:id="@+id/scrubberIndicator"
android:background="@drawable/letter_indicator"
- android:layout_width="80dp"
- android:textSize="30sp"
- android:gravity="center"
- android:textColor="@android:color/white"
+ android:layout_width="100dp"
+ android:layout_marginLeft="@dimen/app_drawer_scrubber_padding"
+ android:paddingTop="18dp"
+ android:textSize="24sp"
+ android:gravity="center_horizontal|top"
+ android:textColor="@android:color/black"
android:clickable="false"
- android:layout_marginBottom="-20dp"
+ android:layout_marginBottom="-48dp"
android:visibility="invisible"
android:layout_height="100dp" />
</RelativeLayout>
diff --git a/res/layout/app_drawer_item.xml b/res/layout/app_drawer_item.xml
index 08043eb..43e2562 100644
--- a/res/layout/app_drawer_item.xml
+++ b/res/layout/app_drawer_item.xml
@@ -14,34 +14,44 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:autofit="http://schemas.android.com/apk/res-auto"
- android:orientation="vertical"
android:splitMotionEvents="false"
android:layout_width="match_parent"
android:layout_height="wrap_content">
+ <!-- Layout in back to front render order -->
<LinearLayout
- android:layout_marginTop="10dp"
+ android:id="@+id/drawer_item_flow"
+ android:layout_alignParentRight="true"
+ android:paddingTop="10dp"
+ android:layout_toRightOf="@+id/drawer_item_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal" >
- <com.android.launcher3.AutoFitTextView
- android:id="@+id/drawer_item_title"
- android:layout_width="30dp"
- android:layout_height="wrap_content"
- android:layout_marginLeft="10dp"
- android:includeFontPadding="false"
- android:gravity="start|top"
- android:singleLine="true"
- autofit:minTextSize="8sp"
- android:textSize="24sp"
- android:layout_gravity="center"
- android:fontFamily="sans-serif-light"
- android:textColor="@android:color/white"/>
- <LinearLayout
- android:id="@+id/drawer_item_flow"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal" />
- </LinearLayout>
-</LinearLayout>
+ android:orientation="horizontal" />
+
+ <View
+ android:id="@+id/fading_background"
+ android:alpha="0"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_alignTop="@id/drawer_item_flow"
+ android:layout_alignBottom="@id/drawer_item_flow"
+ android:background="@color/app_drawer_drag_background" />
+
+ <com.android.launcher3.AutoFitTextView
+ android:id="@+id/drawer_item_title"
+ android:layout_width="27dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginLeft="10dp"
+ android:layout_centerVertical="true"
+ android:includeFontPadding="false"
+ android:gravity="center"
+ android:singleLine="true"
+ autofit:minTextSize="8sp"
+ android:textSize="24sp"
+ android:layout_gravity="center"
+ android:fontFamily="sans-serif-light"
+ android:textColor="@android:color/white" />
+
+</RelativeLayout>
diff --git a/res/layout/scrub_layout.xml b/res/layout/scrub_layout.xml
index 90a9d09..11ee381 100644
--- a/res/layout/scrub_layout.xml
+++ b/res/layout/scrub_layout.xml
@@ -14,31 +14,31 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/app_drawer_scrubber_container"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@drawable/seek_back"
- android:layout_alignParentBottom="true">
+ android:layout_height="@dimen/scrubber_height"
+ android:paddingLeft="@dimen/app_drawer_scrubber_padding"
+ android:paddingRight="@dimen/app_drawer_scrubber_padding"
+ android:layout_alignParentBottom="true"
+ android:background="@drawable/seek_back">
- <LinearLayout
- android:clickable="true"
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_marginLeft="20dp"
- android:layout_marginRight="20dp"
- android:layout_height="@dimen/scrubber_height">
-
- <SeekBar
- android:id="@+id/scrubber"
- android:paddingLeft="0dp"
- android:paddingRight="0dp"
- android:thumb="@android:color/transparent"
- android:progressDrawable="@drawable/empty_seek_bar"
- android:layout_width="match_parent"
- android:layout_gravity="center"
- android:layout_height="match_parent" />
+ <com.android.launcher3.AutoExpandTextView
+ android:id="@+id/scrubberText"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:textColor="@android:color/white"
+ android:maxLines="1"
+ android:lines="1"
+ android:textSize="8sp" />
- </LinearLayout>
+ <SeekBar
+ android:id="@+id/scrubber"
+ android:thumb="@android:color/transparent"
+ android:progressDrawable="@android:color/transparent"
+ android:layout_width="match_parent"
+ android:layout_gravity="center"
+ android:layout_height="match_parent" />
-</RelativeLayout>
+</FrameLayout>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 3cf7d39..082489d 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -51,6 +51,9 @@
<color name="dynamic_grid_preview_foreground">#FF000000</color>
<color name="app_drawer_background">#76000000</color>
+ <color name="app_drawer_drag_background">#bf14191e</color>
+ <color name="app_scrubber_highlight_color">@android:color/white</color>
+ <color name="app_scrubber_gray_color">@android:color/darker_gray</color>
<color name="scrubber_background">#CC14191E</color>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 81e40bf..5394ea7 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -124,4 +124,6 @@
<!-- Vertical app drawer padding -->
<dimen name="vertical_app_drawer_icon_padding">5dp</dimen>
+
+ <dimen name="app_drawer_scrubber_padding">20dp</dimen>
</resources>
diff --git a/src/com/android/launcher3/AppDrawerListAdapter.java b/src/com/android/launcher3/AppDrawerListAdapter.java
index a169373..ea32435 100644
--- a/src/com/android/launcher3/AppDrawerListAdapter.java
+++ b/src/com/android/launcher3/AppDrawerListAdapter.java
@@ -16,25 +16,29 @@
package com.android.launcher3;
+import android.animation.ValueAnimator;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.support.v7.widget.RecyclerView;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
import android.widget.LinearLayout;
import android.widget.SectionIndexer;
import com.android.launcher3.locale.LocaleSetManager;
import com.android.launcher3.locale.LocaleUtils;
import com.android.launcher3.settings.SettingsProvider;
-import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
@@ -61,6 +65,8 @@ public class AppDrawerListAdapter extends RecyclerView.Adapter<AppDrawerListAdap
private boolean mHideIconLabels;
+ private ItemAnimatorSet mItemAnimatorSet;
+
public enum DrawerType {
Drawer(0),
Pager(1);
@@ -87,10 +93,173 @@ public class AppDrawerListAdapter extends RecyclerView.Adapter<AppDrawerListAdap
public static class ViewHolder extends RecyclerView.ViewHolder {
public AutoFitTextView mTextView;
public ViewGroup mLayout;
+ public View mFadingBackground;
public ViewHolder(View itemView) {
super(itemView);
mTextView = (AutoFitTextView) itemView.findViewById(R.id.drawer_item_title);
mLayout = (ViewGroup) itemView.findViewById(R.id.drawer_item_flow);
+ mFadingBackground = itemView.findViewById(R.id.fading_background);
+ }
+ }
+
+ /**
+ * This class handles animating the different items when the user scrolls through the drawer
+ * quickly
+ */
+ private class ItemAnimatorSet {
+ private static final long ANIMATION_DURATION = 200;
+ private static final float MAX_SCALE = 2f;
+ private static final float MIN_SCALE = 1f;
+ private static final float FAST_SCROLL = 0.3f;
+
+ private final float YDPI;
+ private final HashSet<ViewHolder> mViewHolderSet;
+ private final Interpolator mInterpolator;
+ private final View.OnLayoutChangeListener mLayoutChangeListener;
+
+ private boolean mDragging;
+ private boolean mExpanding;
+ private boolean mPendingShrink;
+ private long mStartTime;
+ private int mScrollState;
+ private float mFastScrollSpeed;
+ private float mLastScrollSpeed;
+
+ public ItemAnimatorSet(Context ctx) {
+ mDragging = false;
+ mExpanding = false;
+ mPendingShrink = false;
+ mScrollState = RecyclerView.SCROLL_STATE_IDLE;
+ mViewHolderSet = new HashSet<>();
+ mInterpolator = new DecelerateInterpolator();
+ YDPI = ctx.getResources().getDisplayMetrics().ydpi;
+ mLayoutChangeListener = new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ // set the pivot of the text view
+ v.setPivotX(0);
+ v.setPivotY(v.getMeasuredHeight() / 2);
+ }
+ };
+ }
+
+ public void add(ViewHolder holder) {
+ mViewHolderSet.add(holder);
+ holder.mTextView.addOnLayoutChangeListener(mLayoutChangeListener);
+
+ createAnimationHook(holder);
+ }
+
+ public void remove(ViewHolder holder) {
+ mViewHolderSet.remove(holder);
+ holder.mTextView.removeOnLayoutChangeListener(mLayoutChangeListener);
+ }
+
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ if (newState != mScrollState) {
+ mScrollState = newState;
+ mFastScrollSpeed = 0;
+ checkAnimationState();
+ }
+ }
+
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ if (mScrollState == RecyclerView.SCROLL_STATE_SETTLING) {
+ mLastScrollSpeed = Math.abs(dy / YDPI);
+ // get the max of the current scroll speed and the previous fastest scroll speed
+ mFastScrollSpeed = Math.max(mFastScrollSpeed, mLastScrollSpeed);
+ checkAnimationState();
+ }
+ }
+
+ public void setDragging(boolean dragging) {
+ mDragging = dragging;
+ checkAnimationState();
+ }
+
+ private void checkAnimationState() {
+ // if the user is dragging or if we're settling at a fast speed, then show animation
+ showAnimation(mDragging ||
+ (mScrollState == RecyclerView.SCROLL_STATE_SETTLING &&
+ mFastScrollSpeed >= FAST_SCROLL));
+ }
+
+ private void showAnimation(boolean expanding) {
+ if (mExpanding != expanding) {
+ // if near the top or bottom and flick to that side of the list, the scroll speed
+ // will hit 0 and the animation will cut straight to shrinking. This code
+ // is here to allow the expand animation to complete in that specific scenario
+ // before shrinking
+ // if the user isn't dragging, the scroll state is idle, the last scroll is fast and
+ // the expand animation is still playing, then mark pending shrink as true
+ if (!mDragging
+ && mScrollState == RecyclerView.SCROLL_STATE_IDLE
+ && mLastScrollSpeed > FAST_SCROLL
+ && System.currentTimeMillis() - mStartTime < ANIMATION_DURATION) {
+ mPendingShrink = true;
+ return;
+ }
+
+ mExpanding = expanding;
+ mPendingShrink = false;
+ mStartTime = System.currentTimeMillis();
+
+ for (ViewHolder holder : mViewHolderSet) {
+ createAnimationHook(holder);
+ }
+ }
+ }
+
+ public void createAnimationHook(ViewHolder holder) {
+ holder.mTextView.animate().cancel();
+ holder.mTextView.animate()
+ .setUpdateListener(new ItemAnimator(holder, mItemAnimatorSet))
+ .setDuration(ANIMATION_DURATION)
+ .start();
+ }
+
+ public void animate(ViewHolder holder, ValueAnimator animation) {
+ long diffTime = System.currentTimeMillis() - mStartTime;
+
+ float percentage = Math.min(diffTime / (float) ANIMATION_DURATION, 1f);
+ percentage = mInterpolator.getInterpolation(percentage);
+
+ if (!mExpanding) {
+ percentage = 1 - percentage;
+ }
+
+ final float targetScale = (MAX_SCALE - MIN_SCALE) * percentage + MIN_SCALE;
+ holder.mTextView.setScaleX(targetScale);
+ holder.mTextView.setScaleY(targetScale);
+
+ holder.mFadingBackground.setAlpha(percentage);
+
+ if (diffTime >= ANIMATION_DURATION) {
+ animation.cancel();
+
+ if (mPendingShrink) {
+ mPendingShrink = false;
+ mLastScrollSpeed = 0;
+ checkAnimationState();
+ }
+
+ }
+ }
+ }
+
+ private static class ItemAnimator implements ValueAnimator.AnimatorUpdateListener {
+ private ViewHolder mViewHolder;
+ private ItemAnimatorSet mAnimatorSet;
+
+ public ItemAnimator(final ViewHolder holder, final ItemAnimatorSet animatorSet) {
+ mViewHolder = holder;
+ mAnimatorSet = animatorSet;
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mAnimatorSet.animate(mViewHolder, animation);
}
}
@@ -101,11 +270,25 @@ public class AppDrawerListAdapter extends RecyclerView.Adapter<AppDrawerListAdap
mLocaleSetManager = new LocaleSetManager(mLauncher);
mLocaleSetManager.updateLocaleSet(mLocaleSetManager.getSystemLocaleSet());
+ mItemAnimatorSet = new ItemAnimatorSet(launcher);
initParams();
updateProtectedAppsList(mLauncher);
}
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ mItemAnimatorSet.onScrollStateChanged(recyclerView, newState);
+ }
+
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ mItemAnimatorSet.onScrolled(recyclerView, dx, dy);
+ }
+
+
+ public void setDragging(boolean dragging) {
+ mItemAnimatorSet.setDragging(dragging);
+ }
+
private void initParams() {
mDeviceProfile = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
@@ -347,7 +530,17 @@ public class AppDrawerListAdapter extends RecyclerView.Adapter<AppDrawerListAdap
View v = LayoutInflater.from(parent.getContext()).
inflate(R.layout.app_drawer_item, parent, false);
ViewHolder holder = new ViewHolder(v);
- holder.mTextView.setPadding(0, 0, 0, mDeviceProfile.iconTextSizePx + 10);
+ ViewGroup.LayoutParams params = holder.mTextView.getLayoutParams();
+
+ // set the margin parameter to account for the text size of the icons so that the text view
+ // is based on the icon size only
+ if (params instanceof ViewGroup.MarginLayoutParams) {
+ ViewGroup.MarginLayoutParams marginParams = (ViewGroup.MarginLayoutParams) params;
+ marginParams.setMargins(marginParams.leftMargin, marginParams.topMargin,
+ marginParams.rightMargin, mDeviceProfile.iconTextSizePx);
+ holder.mTextView.setLayoutParams(marginParams);
+ }
+
for (int i = 0; i < mDeviceProfile.numColumnsBase; i++) {
AppDrawerIconView icon = (AppDrawerIconView) mLayoutInflater.inflate(
R.layout.drawer_icon, holder.mLayout, false);
@@ -359,6 +552,20 @@ public class AppDrawerListAdapter extends RecyclerView.Adapter<AppDrawerListAdap
}
@Override
+ public void onViewAttachedToWindow(ViewHolder holder) {
+ super.onViewAttachedToWindow(holder);
+
+ mItemAnimatorSet.add(holder);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(ViewHolder holder) {
+ super.onViewDetachedFromWindow(holder);
+
+ mItemAnimatorSet.remove(holder);
+ }
+
+ @Override
public int getItemCount() {
return mHeaderList.size();
}
@@ -380,6 +587,10 @@ public class AppDrawerListAdapter extends RecyclerView.Adapter<AppDrawerListAdap
holder.mTextView.setText(String.valueOf(indexedInfo.mStartString));
}
}
+
+ holder.mTextView.setPivotX(0);
+ holder.mTextView.setPivotY(holder.mTextView.getHeight() / 2);
+
final int size = indexedInfo.mInfo.size();
for (int i = 0; i < holder.mLayout.getChildCount(); i++) {
AppDrawerIconView icon = (AppDrawerIconView) holder.mLayout.getChildAt(i);
diff --git a/src/com/android/launcher3/AppDrawerScrubber.java b/src/com/android/launcher3/AppDrawerScrubber.java
index 0ace60d..1670934 100644
--- a/src/com/android/launcher3/AppDrawerScrubber.java
+++ b/src/com/android/launcher3/AppDrawerScrubber.java
@@ -19,26 +19,30 @@ package com.android.launcher3;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
-public class AppDrawerScrubber extends LinearLayout {
-
- private final int SCRUBBER_INDICATOR_DISPLAY_DURATION = 200;
- private final float SCRUBBER_INDICATOR_DISPLAY_TRANSLATIONY = 20f;
+import java.util.ArrayList;
+public class AppDrawerScrubber extends LinearLayout {
private AppDrawerListAdapter mAdapter;
private RecyclerView mListView;
private TextView mScrubberIndicator;
private SeekBar mSeekBar;
- private String[] mSections;
+ private AutoExpandTextView mScrubberText;
+ private SectionContainer mSectionContainer;
private LinearLayoutManager mLayoutManager;
+ private ScrubberAnimationState mScrubberAnimationState;
public AppDrawerScrubber(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -50,9 +54,85 @@ public class AppDrawerScrubber extends LinearLayout {
init(context);
}
+ /**
+ * Simple container class that tries to abstract out the knowledge of complex sections vs
+ * simple string sections
+ */
+ private static class SectionContainer {
+ private ArrayList<AppDrawerScrubberSections> mSections;
+ private String[] mHeaders;
+
+ public SectionContainer(String[] headers) {
+ mSections = AppDrawerScrubberSections.createSections(headers);
+ mHeaders = headers;
+ }
+
+ public int size() {
+ return showLetters() ? mSections.size() : mHeaders.length;
+ }
+
+ public String getHeader(int idx) {
+ return showLetters() ? mSections.get(idx).getText() : mHeaders[idx];
+ }
+
+ /**
+ * Because the list section headers is not necessarily the same size as the scrubber
+ * letters, we need to map from the larger list to the smaller list.
+ * In the case that curIdx is not highlighted, it will use the directional index to
+ * determine the adapter index
+ * @return the mHeaders index (aka the underlying adapter index).
+ */
+ public int getAdapterIndex(int prevIdx, int curIdx) {
+ if (!showLetters()) {
+ return curIdx;
+ }
+
+ // because we have some unhighlighted letters, we need to first get the directional
+ // index before getting the adapter index
+ return mSections.get(getDirectionalIndex(prevIdx, curIdx)).getAdapterIndex();
+ }
+
+ /**
+ * Given the direction the user is scrolling in, return the closest index which is a
+ * highlighted index
+ */
+ public int getDirectionalIndex(int prevIdx, int curIdx) {
+ if (!showLetters() || mSections.get(curIdx).getHighlight()) {
+ return curIdx;
+ }
+
+ if (prevIdx < curIdx) {
+ return mSections.get(curIdx).getNextIndex();
+ } else {
+ return mSections.get(curIdx).getPreviousIndex();
+ }
+ }
+
+ /**
+ * @return true if the scrubber is showing characters as opposed to a line
+ */
+ public boolean showLetters() {
+ return mSections != null;
+ }
+
+ /**
+ * Initializes the scrubber text with the proper characters
+ */
+ public void initializeScrubberText(AutoExpandTextView scrubberText) {
+ scrubberText.setSections(AppDrawerScrubberSections.getHighlightText(mSections));
+ }
+ }
+
public void updateSections() {
- mSections = (String[]) mAdapter.getSections();
- mSeekBar.setMax(mSections.length - 1);
+ mSectionContainer = new SectionContainer((String[]) mAdapter.getSections());
+ mSectionContainer.initializeScrubberText(mScrubberText);
+ mSeekBar.setMax(mSectionContainer.size() - 1);
+
+ // show a white line if there are no letters, otherwise show transparent
+ Drawable d = mSectionContainer.showLetters() ? new ColorDrawable(Color.TRANSPARENT)
+ : getContext().getResources().getDrawable(R.drawable.seek_back);
+ ((ViewGroup)mSeekBar.getParent()).setBackground(d);
+
}
public void setSource(RecyclerView listView) {
@@ -68,63 +148,128 @@ public class AppDrawerScrubber extends LinearLayout {
private boolean isReady() {
return mListView != null &&
mAdapter != null &&
- mSections != null;
+ mSectionContainer != null;
}
private void init(Context context) {
LayoutInflater.from(context).inflate(R.layout.scrub_layout, this);
+ mScrubberAnimationState = new ScrubberAnimationState();
mSeekBar = (SeekBar) findViewById(R.id.scrubber);
+ mScrubberText = (AutoExpandTextView) findViewById(R.id.scrubberText);
+ mSeekBar.setOnSeekBarChangeListener(mScrubberAnimationState);
+ }
- mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(SeekBar seekBar, final int progress, boolean fromUser) {
- if (!isReady()) {
- return;
- }
- resetScrubber();
+ /**
+ * Handles the animations of the scrubber indicator
+ */
+ private class ScrubberAnimationState implements SeekBar.OnSeekBarChangeListener {
+ private static final long SCRUBBER_DISPLAY_DURATION = 150;
+ private static final float SCRUBBER_SCALE_START = 0f;
+ private static final float SCRUBBER_SCALE_END = 1f;
+ private static final float SCRUBBER_ALPHA_START = 0f;
+ private static final float SCRUBBER_ALPHA_END = 1f;
+
+ private boolean mTouchingTrack = false;
+ private boolean mAnimatingIn = false;
+ private int mLastIndex = -1;
- String section = String.valueOf(mSections[progress]);
+ private void touchTrack(boolean touching) {
+ mTouchingTrack = touching;
- if (mScrubberIndicator != null) {
- float translateX = (progress * seekBar.getWidth()) / mSections.length;
- translateX -= (mScrubberIndicator.getWidth() / 6); // offset for alignment
- mScrubberIndicator.setTranslationX(translateX);
- mScrubberIndicator.setText(section);
+ if (mScrubberIndicator != null) {
+ if (mTouchingTrack) {
+ animateIn();
+ } else if (!mAnimatingIn) { // finish animating in before animating out
+ animateOut();
}
- mLayoutManager.smoothScrollToPosition(mListView, null,
- mAdapter.getPositionForSection(progress));
+ mAdapter.setDragging(mTouchingTrack);
}
+ }
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- resetScrubber();
- if (mScrubberIndicator != null) {
- mScrubberIndicator.setAlpha(1f);
- mScrubberIndicator.setVisibility(View.VISIBLE);
- }
- }
+ private void animateIn() {
+ // start from a scratch position when animating in
+ mScrubberIndicator.animate().cancel();
+ mScrubberIndicator.setPivotX(mScrubberIndicator.getMeasuredWidth() / 2);
+ mScrubberIndicator.setPivotY(mScrubberIndicator.getMeasuredHeight() * 0.8f);
+ mScrubberIndicator.setAlpha(SCRUBBER_ALPHA_START);
+ mScrubberIndicator.setScaleX(SCRUBBER_SCALE_START);
+ mScrubberIndicator.setScaleY(SCRUBBER_SCALE_START);
+ mScrubberIndicator.setVisibility(View.VISIBLE);
+ mAnimatingIn = true;
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- resetScrubber();
- if (mScrubberIndicator != null) {
- mScrubberIndicator.animate().alpha(0f).translationYBy(20f)
- .setDuration(200).setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mScrubberIndicator.setVisibility(View.INVISIBLE);
+ mScrubberIndicator.animate()
+ .alpha(SCRUBBER_ALPHA_END)
+ .scaleX(SCRUBBER_SCALE_END)
+ .scaleY(SCRUBBER_SCALE_END)
+ .setDuration(SCRUBBER_DISPLAY_DURATION)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimatingIn = false;
+ // if the user has stopped touching the seekbar, animate back out
+ if (!mTouchingTrack) {
+ animateOut();
}
- });
- }
+ }
+ })
+ .start();
+ }
+
+ private void animateOut() {
+ mScrubberIndicator.animate()
+ .alpha(SCRUBBER_ALPHA_START)
+ .scaleX(SCRUBBER_SCALE_START)
+ .scaleY(SCRUBBER_SCALE_START)
+ .setDuration(SCRUBBER_DISPLAY_DURATION)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mScrubberIndicator.setVisibility(View.INVISIBLE);
+ }
+ });
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int index, boolean fromUser) {
+ if (!isReady()) {
+ return;
}
- private void resetScrubber() {
- if (mScrubberIndicator != null) {
- mScrubberIndicator.animate().cancel();
- mScrubberIndicator.setTranslationY(0f);
+ if (mScrubberIndicator != null) {
+ // get the index based on the direction the user is scrolling
+ int directionalIndex = mSectionContainer.getDirectionalIndex(mLastIndex, index);
+ String sectionText = mSectionContainer.getHeader(directionalIndex);
+
+ float translateX = (index * seekBar.getWidth()) / (float)mSectionContainer.size();
+ // if we are showing letters, grab the position based on the text view
+ if (mSectionContainer.showLetters()) {
+ translateX = mScrubberText.getPositionOfSection(index);
}
+
+ // center the x position
+ translateX -= mScrubberIndicator.getMeasuredWidth() / 2;
+
+ mScrubberIndicator.setTranslationX(translateX);
+ mScrubberIndicator.setText(sectionText);
}
- });
+
+ // get the index of the underlying list
+ int adapterIndex = mSectionContainer.getAdapterIndex(mLastIndex, index);
+ mLayoutManager.smoothScrollToPosition(mListView, null,
+ mAdapter.getPositionForSection(adapterIndex));
+
+ mLastIndex = index;
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ touchTrack(true);
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ touchTrack(false);
+ }
}
} \ No newline at end of file
diff --git a/src/com/android/launcher3/AppDrawerScrubberSections.java b/src/com/android/launcher3/AppDrawerScrubberSections.java
new file mode 100644
index 0000000..2d4fcaa
--- /dev/null
+++ b/src/com/android/launcher3/AppDrawerScrubberSections.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.launcher3;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class AppDrawerScrubberSections {
+ private static final String TAG = AppDrawerScrubber.class.getSimpleName();
+ private static final String ALPHA_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ private static final int MAX_NUMBER_CUSTOM_HEADERS = 8;
+ private static final int MAX_HEADERS = ALPHA_LETTERS.length() + MAX_NUMBER_CUSTOM_HEADERS;
+ public static final int INVALID_INDEX = -1;
+
+ private AutoExpandTextView.HighlightedText mHighlightedText;
+ private int mPreviousValidIndex;
+ private int mNextValidIndex;
+ private int mAdapterIndex;
+
+ public AppDrawerScrubberSections(String text, boolean highlight, int idx) {
+ mHighlightedText = new AutoExpandTextView.HighlightedText(text, highlight);
+ mAdapterIndex = idx;
+ mPreviousValidIndex = mNextValidIndex = idx;
+ }
+
+ public boolean getHighlight() {
+ return mHighlightedText.mHighlight;
+ }
+
+ public String getText() {
+ return mHighlightedText.mText;
+ }
+
+ public int getPreviousIndex() {
+ return mPreviousValidIndex;
+ }
+
+ public int getNextIndex() {
+ return mNextValidIndex;
+ }
+
+ public int getAdapterIndex() {
+ return mAdapterIndex;
+ }
+
+ private static int getFirstValidIndex(ArrayList<AppDrawerScrubberSections> sections) {
+ for (int i = 0; i < sections.size(); i++) {
+ if (sections.get(i).getHighlight()) {
+ return i;
+ }
+ }
+
+ return INVALID_INDEX;
+ }
+
+ private static void createIndices(ArrayList<AppDrawerScrubberSections> sections) {
+ if (sections == null || sections.size() == 0) {
+ return;
+ }
+
+ // walk forwards and fill out the previous valid index based on the previous highlight
+ int currentIdx = getFirstValidIndex(sections);
+ for (int i = 0; i < sections.size(); i++) {
+ if (sections.get(i).getHighlight()) {
+ currentIdx = i;
+ }
+
+ sections.get(i).mPreviousValidIndex = currentIdx;
+ }
+
+ // currentIdx should be now on the last valid index so walk back and fill the other way
+ for (int i = sections.size() - 1; i >= 0; i--) {
+ if (sections.get(i).getHighlight()) {
+ currentIdx = i;
+ }
+
+ sections.get(i).mNextValidIndex = currentIdx;
+ }
+ }
+
+ public static ArrayList<AutoExpandTextView.HighlightedText> getHighlightText(
+ ArrayList<AppDrawerScrubberSections> sections) {
+ if (sections == null) {
+ return null;
+ }
+
+ ArrayList<AutoExpandTextView.HighlightedText> highlights = new ArrayList<>(sections.size());
+ for (AppDrawerScrubberSections section : sections) {
+ highlights.add(section.mHighlightedText);
+ }
+
+ return highlights;
+ }
+
+ private static void addAlphaLetters(ArrayList<AppDrawerScrubberSections> sections,
+ HashMap<Integer, Integer> foundAlphaLetters) {
+ for (int i = 0; i < ALPHA_LETTERS.length(); i++) {
+ boolean highlighted = foundAlphaLetters.containsKey(i);
+ int index = highlighted
+ ? foundAlphaLetters.get(i) : AppDrawerScrubberSections.INVALID_INDEX;
+
+ sections.add(new AppDrawerScrubberSections(ALPHA_LETTERS.substring(i, i + 1),
+ highlighted, index));
+ }
+ }
+
+ /**
+ * Takes the headers and runs some checks to see if we can create a valid
+ * appDrawerScrubberSection out of it. This list will contain the original header list plus
+ * fill out the remaining sections based on the ALPHA_LETTERS. It will then determine which
+ * ones to highlight as well as what letters to highlight when scrolling over the
+ * grayed out sections
+ * @param headers list of header Strings
+ * @return the list of scrubber sections
+ */
+ public static ArrayList<AppDrawerScrubberSections> createSections(String[] headers) {
+ // check if we have a valid header section
+ if (!validHeaderList(headers)) {
+ return null;
+ }
+
+ // this will track the mapping of ALPHA_LETTERS index to the headers index
+ HashMap<Integer, Integer> foundAlphaLetters = new HashMap<>();
+ ArrayList<AppDrawerScrubberSections> sections = new ArrayList<>(headers.length);
+ boolean inAlphaLetterSection = false;
+
+ for (int i = 0; i < headers.length; i++) {
+ int alphaLetterIndex = TextUtils.isEmpty(headers[i])
+ ? -1 : ALPHA_LETTERS.indexOf(headers[i]);
+
+ // if we found an ALPHA_LETTERS store that in foundAlphaLetters and continue
+ if (alphaLetterIndex >= 0) {
+ foundAlphaLetters.put(alphaLetterIndex, i);
+ inAlphaLetterSection = true;
+ } else {
+ // if we are exiting the ALPHA_LETTERS section, add it here
+ if (inAlphaLetterSection) {
+ addAlphaLetters(sections, foundAlphaLetters);
+ inAlphaLetterSection = false;
+ }
+
+ // add the custom header
+ sections.add(new AppDrawerScrubberSections(headers[i], true, i));
+ }
+ }
+
+ // if the last section are the alpha letters, then add it
+ if (inAlphaLetterSection) {
+ addAlphaLetters(sections, foundAlphaLetters);
+ }
+
+ // create the forward and backwards indices for scrolling over the grayed out sections
+ AppDrawerScrubberSections.createIndices(sections);
+
+ return sections;
+ }
+
+ /**
+ * Walk through the headers and check for a few things:
+ * 1) No more than MAX_NUMBER_CUSTOM_HEADERS headers exist in the headers list or no more
+ * than MAX_HEADERS headers exist in the list
+ * 2) the headers that fall in the ALPHA_LETTERS category are in the same order as ALPHA_LETTERS
+ * 3) There are no headers that exceed length of 1
+ * 4) The alpha letter section is together and not separated by other things
+ */
+ private static boolean validHeaderList(String[] headers) {
+ int numCustomHeaders = 0;
+ int previousAlphaIndex = -1;
+ boolean foundAlphaHeaders = false;
+
+ for (String s : headers) {
+ if (TextUtils.isEmpty(s)) {
+ numCustomHeaders++;
+ continue;
+ }
+
+ if (s.length() > 1) {
+ Log.w(TAG, "Found header " + s + " with length: " + s.length());
+ return false;
+ }
+
+ int alphaIndex = ALPHA_LETTERS.indexOf(s);
+ if (alphaIndex >= 0) {
+ if (previousAlphaIndex != -1) {
+ // if the previous alpha index is >= alphaIndex then it is in the wrong order
+ if (previousAlphaIndex >= alphaIndex) {
+ Log.w(TAG, "Found letter index " + previousAlphaIndex
+ + " which is greater than " + alphaIndex);
+ return false;
+ }
+ }
+
+ // if we've found headers previously and the index is -1 that means the alpha
+ // letters are separated out into two sections so return false
+ if (foundAlphaHeaders && previousAlphaIndex == -1) {
+ Log.w(TAG, "Found alpha letters twice");
+ return false;
+ }
+
+ previousAlphaIndex = alphaIndex;
+ foundAlphaHeaders = true;
+ } else {
+ numCustomHeaders++;
+ previousAlphaIndex = -1;
+ }
+ }
+
+ final int listSize = foundAlphaHeaders
+ ? numCustomHeaders + ALPHA_LETTERS.length()
+ : numCustomHeaders;
+
+ // if one of these conditions are satisfied, then return true
+ if (numCustomHeaders <= MAX_NUMBER_CUSTOM_HEADERS || listSize <= MAX_HEADERS) {
+ return true;
+ }
+
+ if (numCustomHeaders > MAX_NUMBER_CUSTOM_HEADERS) {
+ Log.w(TAG, "Found " + numCustomHeaders + "# custom headers when " +
+ MAX_NUMBER_CUSTOM_HEADERS + " is allowed!");
+ } else if (listSize > MAX_HEADERS) {
+ Log.w(TAG, "Found " + listSize + " headers when " +
+ MAX_HEADERS + " is allowed!");
+ }
+
+ return false;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/launcher3/AutoExpandTextView.java b/src/com/android/launcher3/AutoExpandTextView.java
new file mode 100644
index 0000000..ea7ac89
--- /dev/null
+++ b/src/com/android/launcher3/AutoExpandTextView.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2014 Grantland Chew
+ * Copyright (C) 2015 The CyanogenMod 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.launcher3;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.text.method.TransformationMethod;
+import android.text.style.ForegroundColorSpan;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+/**
+ * A single-line TextView that resizes it's letter spacing to fit the width of the view
+ *
+ * @author Grantland Chew <grantlandchew@gmail.com>
+ * @author Linus Lee <llee@cyngn.com>
+ */
+public class AutoExpandTextView extends TextView {
+ // How precise we want to be when reaching the target textWidth size
+ private static final float PRECISION = 0.01f;
+
+ // Attributes
+ private float mPrecision;
+ private TextPaint mPaint;
+ private float[] mPositions;
+
+ public static class HighlightedText {
+ public String mText;
+ public boolean mHighlight;
+
+ public HighlightedText(String text, boolean highlight) {
+ mText = text;
+ mHighlight = highlight;
+ }
+ }
+
+ public AutoExpandTextView(Context context) {
+ super(context);
+ init(context, null, 0);
+ }
+
+ public AutoExpandTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs, 0);
+ }
+
+ public AutoExpandTextView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context, attrs, defStyle);
+ }
+
+ private void init(Context context, AttributeSet attrs, int defStyle) {
+ float precision = PRECISION;
+
+ if (attrs != null) {
+ TypedArray ta = context.obtainStyledAttributes(
+ attrs,
+ R.styleable.AutofitTextView,
+ defStyle,
+ 0);
+ precision = ta.getFloat(R.styleable.AutofitTextView_precision, precision);
+ }
+
+ mPaint = new TextPaint();
+ setPrecision(precision);
+ }
+
+ /**
+ * @return the amount of precision used to calculate the correct text size to fit within it's
+ * bounds.
+ */
+ public float getPrecision() {
+ return mPrecision;
+ }
+
+ /**
+ * Set the amount of precision used to calculate the correct text size to fit within it's
+ * bounds. Lower precision is more precise and takes more time.
+ *
+ * @param precision The amount of precision.
+ */
+ public void setPrecision(float precision) {
+ if (precision != mPrecision) {
+ mPrecision = precision;
+ refitText();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setLines(int lines) {
+ super.setLines(1);
+ refitText();
+ }
+
+ /**
+ * Only allow max lines of 1
+ */
+ @Override
+ public void setMaxLines(int maxLines) {
+ super.setMaxLines(1);
+ refitText();
+ }
+
+ /**
+ * Re size the font so the specified text fits in the text box assuming the text box is the
+ * specified width.
+ */
+ private void refitText() {
+ CharSequence text = getText();
+
+ if (TextUtils.isEmpty(text)) {
+ return;
+ }
+
+ TransformationMethod method = getTransformationMethod();
+ if (method != null) {
+ text = method.getTransformation(text, this);
+ }
+ int targetWidth = getWidth() - getPaddingLeft() - getPaddingRight();
+ if (targetWidth > 0) {
+ float high = 100;
+ float low = 0;
+
+ mPaint.set(getPaint());
+ mPaint.setTextSize(getTextSize());
+ float letterSpacing = getLetterSpacing(text, mPaint, targetWidth, low, high,
+ mPrecision);
+ mPaint.setLetterSpacing(letterSpacing);
+ calculateSections(text);
+
+ super.setLetterSpacing(letterSpacing);
+ }
+ }
+
+ public float getPositionOfSection(int position) {
+ if (mPositions == null || position >= mPositions.length) {
+ return 0;
+ }
+ return mPositions[position];
+ }
+
+ /**
+ * This calculates the different horizontal positions of each character
+ */
+ private void calculateSections(CharSequence text) {
+ mPositions = new float[text.length()];
+ for (int i = 0; i < text.length(); i++) {
+ if (i == 0) {
+ mPositions[0] = mPaint.measureText(text, 0, 1) / 2;
+ } else {
+ // try to be lazy and just add the width of the newly added char
+ mPositions[i] = mPaint.measureText(text, i, i + 1) + mPositions[i - 1];
+ }
+ }
+ }
+
+ /**
+ * Sets the list of sections in the text view. This will take the first character of each
+ * and space it out in the text view using letter spacing
+ */
+ public void setSections(ArrayList<HighlightedText> sections) {
+ mPositions = null;
+ if (sections == null || sections.size() == 0) {
+ setText("");
+ return;
+ }
+
+ Resources r = getContext().getResources();
+ int highlightColor = r.getColor(R.color.app_scrubber_highlight_color);
+ int grayColor = r.getColor(R.color.app_scrubber_gray_color);
+
+ SpannableStringBuilder builder = new SpannableStringBuilder();
+ for (HighlightedText highlightText : sections) {
+ SpannableString spannable = new SpannableString(highlightText.mText.substring(0, 1));
+ spannable.setSpan(
+ new ForegroundColorSpan(highlightText.mHighlight ? highlightColor : grayColor),
+ 0, spannable.length(), 0);
+ builder.append(spannable);
+ }
+
+ setText(builder);
+ }
+
+ private static float getLetterSpacing(CharSequence text, TextPaint paint, float targetWidth,
+ float low, float high, float precision) {
+ float mid = (low + high) / 2.0f;
+ paint.setLetterSpacing(mid);
+
+ float measuredWidth = paint.measureText(text, 0, text.length());
+
+ if (high - low < precision) {
+ if (measuredWidth < targetWidth) {
+ return mid;
+ } else {
+ return low;
+ }
+ } else if (measuredWidth > targetWidth) {
+ return getLetterSpacing(text, paint, targetWidth, low, mid, precision);
+ } else if (measuredWidth < targetWidth) {
+ return getLetterSpacing(text, paint, targetWidth, mid, high, precision);
+ } else {
+ return mid;
+ }
+ }
+
+ @Override
+ protected void onTextChanged(final CharSequence text, final int start,
+ final int lengthBefore, final int lengthAfter) {
+ super.onTextChanged(text, start, lengthBefore, lengthAfter);
+ refitText();
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ if (w != oldw) {
+ refitText();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5d857ce..be39e52 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -19,6 +19,7 @@ package com.android.launcher3;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
@@ -59,6 +60,7 @@ import android.graphics.Color;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
@@ -1724,6 +1726,17 @@ public class Launcher extends Activity
}
mAppDrawer.setHasFixedSize(true);
mAppDrawer.setAdapter(mAppDrawerAdapter);
+ mAppDrawer.setOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ mAppDrawerAdapter.onScrollStateChanged(recyclerView, newState);
+ }
+
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ mAppDrawerAdapter.onScrolled(recyclerView, dx, dy);
+ }
+ });
initializeScrubber();
}
}
@@ -3645,7 +3658,7 @@ public class Launcher extends Activity
}
boolean material = Utilities.isLmpOrAbove();
- boolean drawer = mDrawerType == AppDrawerListAdapter.DrawerType.Drawer;
+ final boolean drawer = mDrawerType == AppDrawerListAdapter.DrawerType.Drawer;
final Resources res = getResources();
final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime);
@@ -3828,6 +3841,14 @@ public class Launcher extends Activity
mStateAnimation.addListener(new AnimatorListenerAdapter() {
@Override
+ public void onAnimationStart(Animator animation) {
+ if (drawer && mAppsCustomizeContent.getContentType()
+ == AppsCustomizePagedView.ContentType.Applications) {
+ updateStatusBarColor(res.getColor(R.color.app_drawer_drag_background));
+ }
+ }
+
+ @Override
public void onAnimationEnd(Animator animation) {
dispatchOnLauncherTransitionEnd(fromView, animated, false);
dispatchOnLauncherTransitionEnd(toView, animated, false);
@@ -3893,6 +3914,11 @@ public class Launcher extends Activity
toView.setScaleY(1.0f);
toView.setVisibility(View.VISIBLE);
toView.bringToFront();
+ if (drawer && mAppsCustomizeContent.getContentType()
+ == AppsCustomizePagedView.ContentType.Applications) {
+ updateStatusBarColor(res.getColor(R.color.app_drawer_drag_background));
+ toView.setBackgroundColor(res.getColor(R.color.app_drawer_background));
+ }
if (!springLoaded && !LauncherAppState.getInstance().isScreenLarge()) {
// Hide the search bar
@@ -4012,6 +4038,7 @@ public class Launcher extends Activity
content.setPageBackgroundsVisible(false);
} else {
fromView.setBackgroundColor(Color.TRANSPARENT);
+ updateStatusBarColor(Color.TRANSPARENT);
}
final View allAppsButton = getAllAppsButton();
@@ -4201,6 +4228,11 @@ public class Launcher extends Activity
fromView.post(startAnimRunnable);
} else {
fromView.setVisibility(View.GONE);
+ if (drawer && mAppsCustomizeContent.getContentType()
+ == AppsCustomizePagedView.ContentType.Applications) {
+ fromView.setBackgroundColor(Color.TRANSPARENT);
+ updateStatusBarColor(Color.TRANSPARENT, 0);
+ }
dispatchOnLauncherTransitionPrepare(fromView, animated, true);
dispatchOnLauncherTransitionStart(fromView, animated, true);
dispatchOnLauncherTransitionEnd(fromView, animated, true);
@@ -5591,6 +5623,19 @@ public class Launcher extends Activity
if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
}
+ private void updateStatusBarColor(int color) {
+ updateStatusBarColor(color, 300);
+ }
+
+ private void updateStatusBarColor(int color, int duration) {
+ final Window window = getWindow();
+ ObjectAnimator animator = ObjectAnimator.ofInt(window,
+ "statusBarColor", window.getStatusBarColor(), color);
+ animator.setEvaluator(new ArgbEvaluator());
+ animator.setDuration(duration);
+ animator.start();
+ }
+
public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
// Called from search suggestion, not supported in other profiles.
final UserHandleCompat myUser = UserHandleCompat.myUserHandle();