summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcretin45 <cretin45@gmail.com>2015-11-13 16:51:43 -0800
committercretin45 <cretin45@gmail.com>2015-11-23 12:10:32 -0800
commitacab44fc939b4083ab7ec889b0c6d4fe0db00cca (patch)
tree6d7dd6d411404b804814ec4091e007f05dfd4ae5
parent35614fdc7d13179ba9cbc80f15eca6d63cf3229d (diff)
downloadandroid_packages_apps_Trebuchet-acab44fc939b4083ab7ec889b0c6d4fe0db00cca.tar.gz
android_packages_apps_Trebuchet-acab44fc939b4083ab7ec889b0c6d4fe0db00cca.tar.bz2
android_packages_apps_Trebuchet-acab44fc939b4083ab7ec889b0c6d4fe0db00cca.zip
Reimplement the CM scrubber against the new Launcher
PS4: Implement RTL support Change-Id: I4456d54b5924913d1b36e1cfa9a2269150f6fb3e
-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-xhdpi/letter_indicator.pngbin0 -> 25268 bytes
-rw-r--r--res/drawable-xxhdpi/letter_indicator.pngbin0 -> 34597 bytes
-rw-r--r--res/drawable/scrubber_back.xml19
-rw-r--r--res/drawable/seek_back.xml30
-rw-r--r--res/layout/all_apps_container.xml9
-rw-r--r--res/layout/scrub_layout.xml44
-rw-r--r--res/layout/scrubber_container.xml53
-rw-r--r--res/layout/widgets_view.xml9
-rw-r--r--res/values/attrs.xml6
-rw-r--r--res/values/colors.xml8
-rw-r--r--res/values/dimens.xml12
-rw-r--r--src/com/android/launcher3/AutoExpandTextView.java246
-rw-r--r--src/com/android/launcher3/BaseContainerView.java59
-rw-r--r--src/com/android/launcher3/BaseRecyclerView.java66
-rw-r--r--src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java3
-rw-r--r--src/com/android/launcher3/BaseRecyclerViewScrubber.java424
-rw-r--r--src/com/android/launcher3/BaseRecyclerViewScrubberSection.java267
-rw-r--r--src/com/android/launcher3/BubbleTextView.java32
-rw-r--r--src/com/android/launcher3/DeviceProfile.java14
-rw-r--r--src/com/android/launcher3/Launcher.java7
-rw-r--r--src/com/android/launcher3/allapps/AllAppsContainerView.java95
-rw-r--r--src/com/android/launcher3/allapps/AllAppsGridAdapter.java72
-rw-r--r--src/com/android/launcher3/allapps/AllAppsRecyclerView.java183
-rw-r--r--src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java2
-rw-r--r--src/com/android/launcher3/widget/WidgetsContainerView.java6
-rw-r--r--src/com/android/launcher3/widget/WidgetsRecyclerView.java18
28 files changed, 1618 insertions, 66 deletions
diff --git a/res/drawable-hdpi/letter_indicator.png b/res/drawable-hdpi/letter_indicator.png
new file mode 100644
index 000000000..4770d819d
--- /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 000000000..2ecfe7c34
--- /dev/null
+++ b/res/drawable-mdpi/letter_indicator.png
Binary files differ
diff --git a/res/drawable-xhdpi/letter_indicator.png b/res/drawable-xhdpi/letter_indicator.png
new file mode 100644
index 000000000..6f2186017
--- /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 000000000..acbacb067
--- /dev/null
+++ b/res/drawable-xxhdpi/letter_indicator.png
Binary files differ
diff --git a/res/drawable/scrubber_back.xml b/res/drawable/scrubber_back.xml
new file mode 100644
index 000000000..c5022dec5
--- /dev/null
+++ b/res/drawable/scrubber_back.xml
@@ -0,0 +1,19 @@
+<?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="@color/scrubber_background"/>
+</shape>
diff --git a/res/drawable/seek_back.xml b/res/drawable/seek_back.xml
new file mode 100644
index 000000000..d97a870ea
--- /dev/null
+++ b/res/drawable/seek_back.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="360dp"
+ android:height="48dp"
+ android:viewportWidth="360"
+ android:viewportHeight="48">
+
+ <path
+ android:strokeColor="#FFFFFF"
+ android:strokeWidth="2"
+ android:strokeMiterLimit="10"
+ android:strokeLineCap="round"
+ android:pathData="M16,24h328" />
+</vector> \ No newline at end of file
diff --git a/res/layout/all_apps_container.xml b/res/layout/all_apps_container.xml
index 626edafab..3a2c96cd0 100644
--- a/res/layout/all_apps_container.xml
+++ b/res/layout/all_apps_container.xml
@@ -35,4 +35,13 @@
android:focusable="true"
android:descendantFocusability="afterDescendants" />
+ <ViewStub
+ android:id="@+id/scrubber_container_stub"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inflatedId="@+id/scrubber_container"
+ android:layout="@layout/scrubber_container"
+ android:layout_gravity="bottom"
+ android:clipToPadding="false"/>
+
</com.android.launcher3.allapps.AllAppsRecyclerViewContainerView> \ No newline at end of file
diff --git a/res/layout/scrub_layout.xml b/res/layout/scrub_layout.xml
new file mode 100644
index 000000000..11ee381d0
--- /dev/null
+++ b/res/layout/scrub_layout.xml
@@ -0,0 +1,44 @@
+<?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.
+-->
+<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="@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">
+
+ <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" />
+
+ <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" />
+
+</FrameLayout>
diff --git a/res/layout/scrubber_container.xml b/res/layout/scrubber_container.xml
new file mode 100644
index 000000000..4fe84755f
--- /dev/null
+++ b/res/layout/scrubber_container.xml
@@ -0,0 +1,53 @@
+<?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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/scrubber_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:clipToPadding="false">
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:layout_below="@+id/scrubberIndicator"
+ android:background="@drawable/scrubber_back"
+ android:clipToPadding="false">
+
+ <com.android.launcher3.BaseRecyclerViewScrubber
+ android:id="@+id/base_scrubber"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clickable="true"
+ android:layout_gravity="bottom"/>
+ </FrameLayout>
+
+ <TextView
+ android:id="@+id/scrubberIndicator"
+ android:background="@drawable/letter_indicator"
+ android:layout_width="100dp"
+ android:layout_marginLeft="@dimen/app_drawer_scrubber_padding"
+ android:layout_marginRight="@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="-40dp"
+ android:visibility="invisible"
+ android:layout_height="100dp" />
+</RelativeLayout>
diff --git a/res/layout/widgets_view.xml b/res/layout/widgets_view.xml
index 755634f82..1f276adb9 100644
--- a/res/layout/widgets_view.xml
+++ b/res/layout/widgets_view.xml
@@ -46,6 +46,15 @@
android:layout_gravity="center"
android:elevation="15dp"
android:visibility="gone" />
+
+ <ViewStub
+ android:id="@+id/scrubber_container_stub"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inflatedId="@+id/scrubber_container"
+ android:layout="@layout/scrubber_container"
+ android:layout_gravity="bottom"
+ android:clipToPadding="false"/>
</FrameLayout>
</com.android.launcher3.widget.WidgetsContainerView> \ No newline at end of file
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 7ffebce9b..d7f9ef4fa 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -128,4 +128,10 @@
<attr name="layout_ignoreTopInsets" />
<attr name="layout_ignoreBottomInsets" />
</declare-styleable>
+
+ <declare-styleable name="AutofitTextView">
+ <attr name="minTextSize" format="dimension" />
+ <attr name="precision" format="float" />
+ <attr name="sizeToFit" format="boolean" />
+ </declare-styleable>
</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 8a7f62743..b70e1f895 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -33,8 +33,9 @@
<color name="folder_edge_effect_color">#FF757575</color>
<color name="quantum_panel_text_color">#FF666666</color>
+ <color name="quantum_panel_text_color_dark">#FFF</color>
<color name="quantum_panel_bg_color">#FFF5F5F5</color>
- <color name="quantum_panel_bg_color_dark">#FF374248</color>
+ <color name="quantum_panel_bg_color_dark">#76000000</color>
<color name="outline_color">#FFFFFFFF</color>
@@ -44,10 +45,15 @@
<!-- All Apps -->
<color name="all_apps_grid_section_text_color">#009688</color>
+ <color name="all_apps_grid_section_text_color_dark">#FFF</color>
<color name="all_apps_search_market_button_focused_bg_color">#DDDDDD</color>
<!-- Widgets view -->
<color name="widgets_view_section_text_color">#FFFFFF</color>
<color name="widgets_view_item_text_color">#C4C4C4</color>
<color name="widgets_cell_color">#263238</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 e3c81941c..799ea9803 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -64,12 +64,15 @@
<!-- All Apps -->
<dimen name="all_apps_grid_view_start_margin">0dp</dimen>
+ <dimen name="all_apps_grid_view_start_margin_with_sections">36dp</dimen>
<dimen name="all_apps_grid_section_y_offset">8dp</dimen>
<dimen name="all_apps_grid_section_text_size">24sp</dimen>
<dimen name="all_apps_search_bar_height">48dp</dimen>
<dimen name="all_apps_search_bar_prediction_bar_padding">8dp</dimen>
<dimen name="all_apps_icon_top_bottom_padding">8dp</dimen>
<dimen name="all_apps_icon_width_gap">24dp</dimen>
+ <dimen name="all_apps_icon_width_gap_with_sections">12dp</dimen>
+ <dimen name="all_apps_icon_size_with_sections">48dp</dimen>
<!-- The top padding should account for the existing all_apps_list_top_bottom_padding -->
<dimen name="all_apps_prediction_icon_top_padding">8dp</dimen>
<dimen name="all_apps_prediction_icon_bottom_padding">18dp</dimen>
@@ -78,6 +81,8 @@
<dimen name="all_apps_empty_search_bg_top_offset">144dp</dimen>
<dimen name="all_apps_background_canvas_width">700dp</dimen>
<dimen name="all_apps_background_canvas_height">475dp</dimen>
+ <dimen name="all_apps_icon_size_grid">55dp</dimen>
+ <dimen name="all_apps_icon_size_ragged">48dp</dimen>
<!-- Widget tray -->
<dimen name="widget_container_inset">8dp</dimen>
@@ -140,10 +145,15 @@
<dimen name="blur_size_click_shadow">4dp</dimen>
<dimen name="click_shadow_high_shift">2dp</dimen>
-<!-- Pending widget -->
+ <dimen name="app_drawer_scrubber_padding">20dp</dimen>
<dimen name="pending_widget_min_padding">8dp</dimen>
<dimen name="pending_widget_elevation">2dp</dimen>
+ <!-- Scrubber -->
+ <dimen name="scrubber_bottom_padding">30dp</dimen>
+ <dimen name="scrubber_height">48dp</dimen>
+ <dimen name="app_drawer_scrubber_padding">20dp</dimen>
+
<!-- Folder open animation -->
<integer name="folder_translate_y_dist">300</integer>
<integer name="folder_icon_translate_y_dist">100</integer>
diff --git a/src/com/android/launcher3/AutoExpandTextView.java b/src/com/android/launcher3/AutoExpandTextView.java
new file mode 100644
index 000000000..ea7ac896e
--- /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/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
index c11824054..ac2afa944 100644
--- a/src/com/android/launcher3/BaseContainerView.java
+++ b/src/com/android/launcher3/BaseContainerView.java
@@ -20,7 +20,10 @@ import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.View;
+import android.view.ViewStub;
import android.widget.LinearLayout;
+import android.widget.TextView;
/**
* A base container view, which supports resizing.
@@ -43,6 +46,11 @@ public abstract class BaseContainerView extends LinearLayout implements Insettab
// The inset to apply to the edges and between the search bar and the container
private int mContainerBoundsInset;
private boolean mHasSearchBar;
+ private boolean mUseScrubber;
+
+ protected View mScrubberContainerView;
+ protected BaseRecyclerViewScrubber mScrubber;
+ protected final int mScrubberHeight;
public BaseContainerView(Context context) {
this(context, null);
@@ -55,6 +63,7 @@ public abstract class BaseContainerView extends LinearLayout implements Insettab
public BaseContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContainerBoundsInset = getResources().getDimensionPixelSize(R.dimen.container_bounds_inset);
+ mScrubberHeight = getResources().getDimensionPixelSize(R.dimen.scrubber_height);
}
@Override
@@ -67,6 +76,10 @@ public abstract class BaseContainerView extends LinearLayout implements Insettab
mHasSearchBar = true;
}
+ protected boolean hasSearchBar() {
+ return mHasSearchBar;
+ }
+
/**
* Sets the search bar bounds for this container view to match.
*/
@@ -87,10 +100,46 @@ public abstract class BaseContainerView extends LinearLayout implements Insettab
});
}
+ public final void setUseScrubber(boolean use) {
+ mUseScrubber = use;
+ if (use) {
+ ViewStub stub = (ViewStub) findViewById(R.id.scrubber_container_stub);
+ mScrubberContainerView = stub.inflate();
+ if (mScrubberContainerView == null) {
+ throw new IllegalStateException(
+ "Layout must contain an id: R.id.scrubber_container");
+ }
+ mScrubber = (BaseRecyclerViewScrubber)
+ mScrubberContainerView.findViewById(R.id.base_scrubber);
+ BaseRecyclerView recyclerView = getRecyclerView();
+ if (recyclerView != null) {
+ mScrubber.setRecycler(recyclerView);
+ mScrubber
+ .setScrubberIndicator((TextView) mScrubberContainerView
+ .findViewById(R.id.scrubberIndicator));
+ mScrubber.updateSections();
+ }
+ } else {
+ removeView(mScrubberContainerView);
+ BaseRecyclerView recyclerView = getRecyclerView();
+ if (recyclerView != null) {
+ recyclerView.setUseScrollbar(true);
+ }
+ }
+ }
+
+ public final boolean userScrubber() {
+ return mUseScrubber;
+ }
+
+ protected void updateBackgroundAndPaddings() {
+ updateBackgroundAndPaddings(false);
+ }
+
/**
* Update the backgrounds and padding in response to a change in the bounds or insets.
*/
- protected void updateBackgroundAndPaddings() {
+ protected void updateBackgroundAndPaddings(boolean force) {
Rect padding;
Rect searchBarBounds = new Rect();
if (!isValidSearchBarBounds(mFixedSearchBarBounds)) {
@@ -119,7 +168,8 @@ public abstract class BaseContainerView extends LinearLayout implements Insettab
// If either the computed container padding has changed, or the computed search bar bounds
// has changed, then notify the container
- if (!padding.equals(mContentPadding) || !searchBarBounds.equals(mSearchBarBounds)) {
+ if (force || !padding.equals(mContentPadding) ||
+ !searchBarBounds.equals(mSearchBarBounds)) {
mContentPadding.set(padding);
mContentBounds.set(padding.left, padding.top,
getMeasuredWidth() - padding.right,
@@ -135,6 +185,11 @@ public abstract class BaseContainerView extends LinearLayout implements Insettab
protected abstract void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding);
/**
+ * This might be null if the container doesn't have a recycler.
+ */
+ protected abstract BaseRecyclerView getRecyclerView();
+
+ /**
* Returns whether the search bar bounds we got are considered valid.
*/
private boolean isValidSearchBarBounds(Rect searchBarBounds) {
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index f0d8b3b3d..77925b5b3 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -57,6 +57,7 @@ public abstract class BaseRecyclerView extends RecyclerView
}
protected BaseRecyclerViewFastScrollBar mScrollbar;
+ protected boolean mUseScrollbar = false;
private int mDownX;
private int mDownY;
@@ -74,7 +75,6 @@ public abstract class BaseRecyclerView extends RecyclerView
public BaseRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP;
- mScrollbar = new BaseRecyclerViewFastScrollBar(this, getResources());
ScrollListener listener = new ScrollListener();
setOnScrollListener(listener);
@@ -93,12 +93,16 @@ public abstract class BaseRecyclerView extends RecyclerView
// initiate that here if the recycler view scroll state is not
// RecyclerView.SCROLL_STATE_IDLE.
- onUpdateScrollbar(dy);
+ if (mUseScrollbar) {
+ onUpdateScrollbar(dy);
+ }
}
}
public void reset() {
- mScrollbar.reattachThumbToScroll();
+ if (mUseScrollbar) {
+ mScrollbar.reattachThumbToScroll();
+ }
}
@Override
@@ -137,19 +141,28 @@ public abstract class BaseRecyclerView extends RecyclerView
if (shouldStopScroll(ev)) {
stopScroll();
}
- mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
+ if (mScrollbar != null) {
+ mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
+ }
break;
case MotionEvent.ACTION_MOVE:
mLastY = y;
- mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
+ if (mScrollbar != null) {
+ mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
+ }
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
onFastScrollCompleted();
- mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
+ if (mScrollbar != null) {
+ mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
+ }
break;
}
- return mScrollbar.isDraggingThumb();
+ if (mUseScrollbar) {
+ return mScrollbar.isDraggingThumb();
+ }
+ return false;
}
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
@@ -183,7 +196,10 @@ public abstract class BaseRecyclerView extends RecyclerView
* Returns the scroll bar width when the user is scrolling.
*/
public int getMaxScrollbarWidth() {
- return mScrollbar.getThumbMaxWidth();
+ if (mUseScrollbar) {
+ return mScrollbar.getThumbMaxWidth();
+ }
+ return 0;
}
/**
@@ -204,9 +220,12 @@ public abstract class BaseRecyclerView extends RecyclerView
* AvailableScrollBarHeight = Total height of the visible view - thumb height
*/
protected int getAvailableScrollBarHeight() {
- int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
- int availableScrollBarHeight = visibleHeight - mScrollbar.getThumbHeight();
- return availableScrollBarHeight;
+ if (mUseScrollbar) {
+ int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
+ int availableScrollBarHeight = visibleHeight - mScrollbar.getThumbHeight();
+ return availableScrollBarHeight;
+ }
+ return 0;
}
/**
@@ -223,11 +242,23 @@ public abstract class BaseRecyclerView extends RecyclerView
return defaultInactiveThumbColor;
}
+ public void setUseScrollbar(boolean useScrollbar) {
+ mUseScrollbar = useScrollbar;
+ if (useScrollbar) {
+ mScrollbar = new BaseRecyclerViewFastScrollBar(this, getResources());
+ } else {
+ mScrollbar = null;
+ }
+ invalidate();
+ }
+
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
- onUpdateScrollbar(0);
- mScrollbar.draw(canvas);
+ if (mUseScrollbar) {
+ onUpdateScrollbar(0);
+ mScrollbar.draw(canvas);
+ }
}
/**
@@ -241,6 +272,9 @@ public abstract class BaseRecyclerView extends RecyclerView
*/
protected void synchronizeScrollBarThumbOffsetToViewScroll(ScrollPositionState scrollPosState,
int rowCount) {
+ if (!mUseScrollbar) {
+ return;
+ }
// Only show the scrollbar if there is height to be scrolled
int availableScrollBarHeight = getAvailableScrollBarHeight();
int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight);
@@ -273,6 +307,12 @@ public abstract class BaseRecyclerView extends RecyclerView
*/
public abstract String scrollToPositionAtProgress(float touchFraction);
+ public abstract String scrollToSection(String sectionName);
+
+ public abstract String[] getSectionNames();
+
+ public void setFastScrollDragging(boolean dragging) {}
+
/**
* Updates the bounds for the scrollbar.
* <p>Override in each subclass of this base class.
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
index fcee7e8dd..e7b79927a 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
@@ -38,6 +38,7 @@ public class BaseRecyclerViewFastScrollBar {
public interface FastScrollFocusableView {
void setFastScrollFocused(boolean focused, boolean animated);
+ void setFastScrollDimmed(boolean dimmed, boolean animated);
}
private final static int MAX_TRACK_ALPHA = 30;
@@ -193,6 +194,7 @@ public class BaseRecyclerViewFastScrollBar {
Math.abs(y - downY) > config.getScaledTouchSlop()) {
mRv.getParent().requestDisallowInterceptTouchEvent(true);
mIsDragging = true;
+ mRv.setFastScrollDragging(mIsDragging);
if (mCanThumbDetach) {
mIsThumbDetached = true;
}
@@ -220,6 +222,7 @@ public class BaseRecyclerViewFastScrollBar {
mIgnoreDragGesture = false;
if (mIsDragging) {
mIsDragging = false;
+ mRv.setFastScrollDragging(mIsDragging);
mPopup.animateVisibility(false);
animateScrollbar(false);
}
diff --git a/src/com/android/launcher3/BaseRecyclerViewScrubber.java b/src/com/android/launcher3/BaseRecyclerViewScrubber.java
new file mode 100644
index 000000000..1692548a4
--- /dev/null
+++ b/src/com/android/launcher3/BaseRecyclerViewScrubber.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2013 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.PointF;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.LinearSmoothScroller;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import java.lang.IllegalArgumentException;
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * BaseRecyclerViewScrubber
+ * <pre>
+ * This is the scrubber at the bottom of a BaseRecyclerView
+ * </pre>
+ *
+ * @see {@link LinearLayout}
+ */
+public class BaseRecyclerViewScrubber extends LinearLayout {
+ private BaseRecyclerView mBaseRecyclerView;
+ private TextView mScrubberIndicator;
+ private SeekBar mSeekBar;
+ private AutoExpandTextView mScrubberText;
+ private SectionContainer mSectionContainer;
+ private ScrubberAnimationState mScrubberAnimationState;
+ private Drawable mTransparentDrawable;
+ private boolean mIsRtl;
+
+ private static final int MSG_SET_TARGET = 1000;
+ private static final int MSG_ANIMATE_PICK = MSG_SET_TARGET + 1;
+
+ /**
+ * UiHandler
+ * <pre>
+ * Using a handler for sending signals to perform certain actions. The reason for
+ * using this is to be able to remove and replace a signal if signals are being
+ * sent too fast (e.g. user scrubbing like crazy). This allows the touch loop to
+ * complete then later run the animations in their own loops.
+ * </pre>
+ */
+ private class UiHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SET_TARGET:
+ int adapterIndex = msg.arg1;
+ performSetTarget(adapterIndex);
+
+ break;
+ case MSG_ANIMATE_PICK:
+ int index = msg.arg1;
+ int width = msg.arg2;
+ int lastIndex = (Integer)msg.obj;
+ performAnimatePickMessage(index, width, lastIndex);
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+
+ /**
+ * Overidden to remove identical calls if they are called subsequently fast enough.
+ *
+ * This is the final point that is public in the call chain. Other calls to sendMessageXXX
+ * will eventually call this function which calls "enqueueMessage" which is private.
+ *
+ * @param msg {@link Message}
+ * @param uptimeMillis {@link Long}
+ *
+ * @throws IllegalArgumentException {@link IllegalArgumentException}
+ */
+ @Override
+ public boolean sendMessageAtTime(Message msg, long uptimeMillis) throws
+ IllegalArgumentException {
+ if (msg == null) {
+ throw new IllegalArgumentException("'msg' cannot be null!");
+ }
+ if (hasMessages(msg.what)) {
+ removeMessages(msg.what);
+ }
+ return super.sendMessageAtTime(msg, uptimeMillis);
+ }
+
+ }
+ private Handler mUiHandler = new UiHandler();
+ private void sendSetTargetMessage(int adapterIndex) {
+ Message msg = mUiHandler.obtainMessage(MSG_SET_TARGET);
+ msg.what = MSG_SET_TARGET;
+ msg.arg1 = adapterIndex;
+ mUiHandler.sendMessage(msg);
+ }
+ private void performSetTarget(int adapterIndex) {
+ mBaseRecyclerView.scrollToSection(mSectionContainer.getSectionName(adapterIndex, mIsRtl));
+ }
+ private void sendAnimatePickMessage(int index, int width, int lastIndex) {
+ Message msg = mUiHandler.obtainMessage(MSG_ANIMATE_PICK);
+ msg.what = MSG_ANIMATE_PICK;
+ msg.arg1 = index;
+ msg.arg2 = width;
+ msg.obj = lastIndex;
+ mUiHandler.sendMessage(msg);
+ }
+ private void performAnimatePickMessage(int index, int width, int lastIndex) {
+ if (mScrubberIndicator != null) {
+ // get the index based on the direction the user is scrolling
+ int directionalIndex = mSectionContainer.getDirectionalIndex(lastIndex, index);
+ String sectionText = mSectionContainer.getSectionName(directionalIndex, mIsRtl);
+ float translateX = (index * width) / (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;
+ if (mIsRtl) {
+ translateX = -translateX;
+ }
+ mScrubberIndicator.setTranslationX(translateX);
+ mScrubberIndicator.setText(sectionText);
+ }
+ }
+
+ /**
+ * Constructor
+ *
+ * @param context {@link Context}
+ * @param attrs {@link AttributeSet}
+ */
+ public BaseRecyclerViewScrubber(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param context {@link Context}
+ */
+ public BaseRecyclerViewScrubber(Context context) {
+ super(context);
+ init(context);
+ }
+
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+ mIsRtl = Utilities.isRtl(getResources());
+ updateSections();
+ }
+
+ /**
+ * Simple container class that tries to abstract out the knowledge of complex sections vs
+ * simple string sections
+ */
+ private static class SectionContainer {
+ private BaseRecyclerViewScrubberSection.
+ RtlIndexArrayList<BaseRecyclerViewScrubberSection> mSections;
+ private String[] mSectionNames;
+ private final boolean mIsRtl;
+
+ public SectionContainer(String[] sections, boolean isRtl) {
+ mIsRtl = isRtl;
+ mSections = BaseRecyclerViewScrubberSection.createSections(sections, isRtl);
+ mSectionNames = sections;
+ if (isRtl) {
+ final int N = mSectionNames.length;
+ for(int i = 0; i < N / 2; i++) {
+ String temp = mSectionNames[i];
+ mSectionNames[i] = mSectionNames[N - i - 1];
+ mSectionNames[N - i - 1] = temp;
+ }
+ Collections.reverse(mSections);
+ }
+ }
+
+ public int size() {
+ return showLetters() ? mSections.size() : mSectionNames.length;
+ }
+
+ public String getSectionName(int idx, boolean isRtl) {
+ if (size() == 0) {
+ return null;
+ }
+ return showLetters() ? mSections.get(idx, isRtl).getText() : mSectionNames[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 mSectionNames index (aka the underlying adapter index).
+ */
+ public int getAdapterIndex(int prevIdx, int curIdx) {
+ if (!showLetters() || size() == 0) {
+ 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), mIsRtl).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() || size() == 0 || mSections.get(curIdx, mIsRtl).getHighlight()) {
+ return curIdx;
+ }
+
+ if (prevIdx < curIdx) {
+ if (mIsRtl) {
+ return mSections.get(curIdx).getPreviousIndex();
+ } else {
+ return mSections.get(curIdx).getNextIndex();
+ }
+ } else {
+ if (mIsRtl) {
+ 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(BaseRecyclerViewScrubberSection.getHighlightText(mSections));
+ }
+ }
+
+ public void updateSections() {
+ if (mBaseRecyclerView != null) {
+ mSectionContainer = new SectionContainer(mBaseRecyclerView.getSectionNames(), mIsRtl);
+ mSectionContainer.initializeScrubberText(mScrubberText);
+ mSeekBar.setMax(mSectionContainer.size() - 1);
+
+ // show a white line if there are no letters, otherwise show transparent
+ Drawable d = mSectionContainer.showLetters() ? mTransparentDrawable
+ : getContext().getResources().getDrawable(R.drawable.seek_back);
+ ((ViewGroup) mSeekBar.getParent()).setBackground(d);
+ }
+ }
+
+ public void setRecycler(BaseRecyclerView baseRecyclerView) {
+ mBaseRecyclerView = baseRecyclerView;
+ }
+
+ public void setScrubberIndicator(TextView scrubberIndicator) {
+ mScrubberIndicator = scrubberIndicator;
+ }
+
+ private boolean isReady() {
+ return mBaseRecyclerView != null &&
+ mSectionContainer != null;
+ }
+
+ private void init(Context context) {
+ mIsRtl = Utilities.isRtl(context.getResources());
+ LayoutInflater.from(context).inflate(R.layout.scrub_layout, this);
+ mTransparentDrawable = new ColorDrawable(Color.TRANSPARENT);
+ mScrubberAnimationState = new ScrubberAnimationState();
+ mSeekBar = (SeekBar) findViewById(R.id.scrubber);
+ mScrubberText = (AutoExpandTextView) findViewById(R.id.scrubberText);
+ mSeekBar.setOnSeekBarChangeListener(mScrubberAnimationState);
+ }
+
+ /**
+ * Handles the animations of the scrubber indicator
+ */
+ private class ScrubberAnimationState implements SeekBar.OnSeekBarChangeListener {
+ private static final long SCRUBBER_DISPLAY_DURATION_IN = 60;
+ private static final long SCRUBBER_DISPLAY_DURATION_OUT = 150;
+ private static final long SCRUBBER_DISPLAY_DELAY_IN = 0;
+ private static final long SCRUBBER_DISPLAY_DELAY_OUT = 200;
+ 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;
+
+ private void touchTrack(boolean touching) {
+ mTouchingTrack = touching;
+
+ if (mScrubberIndicator != null) {
+ if (mTouchingTrack) {
+ animateIn();
+ } else if (!mAnimatingIn) { // finish animating in before animating out
+ animateOut();
+ }
+
+ mBaseRecyclerView.setFastScrollDragging(mTouchingTrack);
+ }
+ }
+
+ private void animateIn() {
+ if (mScrubberIndicator == null) {
+ return;
+ }
+ // start from a scratch position when animating in
+ mScrubberIndicator.animate().cancel();
+ mScrubberIndicator.setPivotX(mScrubberIndicator.getMeasuredWidth() / 2);
+ mScrubberIndicator.setPivotY(mScrubberIndicator.getMeasuredHeight() * 0.9f);
+ mScrubberIndicator.setAlpha(SCRUBBER_ALPHA_START);
+ mScrubberIndicator.setScaleX(SCRUBBER_SCALE_START);
+ mScrubberIndicator.setScaleY(SCRUBBER_SCALE_START);
+ mScrubberIndicator.setVisibility(View.VISIBLE);
+ mAnimatingIn = true;
+
+ mScrubberIndicator.animate()
+ .alpha(SCRUBBER_ALPHA_END)
+ .scaleX(SCRUBBER_SCALE_END)
+ .scaleY(SCRUBBER_SCALE_END)
+ .setStartDelay(SCRUBBER_DISPLAY_DELAY_IN)
+ .setDuration(SCRUBBER_DISPLAY_DURATION_IN)
+ .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() {
+ if (mScrubberIndicator == null) {
+ return;
+ }
+ mScrubberIndicator.animate()
+ .alpha(SCRUBBER_ALPHA_START)
+ .scaleX(SCRUBBER_SCALE_START)
+ .scaleY(SCRUBBER_SCALE_START)
+ .setStartDelay(SCRUBBER_DISPLAY_DELAY_OUT)
+ .setDuration(SCRUBBER_DISPLAY_DURATION_OUT)
+ .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;
+ }
+ progressChanged(seekBar, index, fromUser);
+ }
+
+ private void progressChanged(SeekBar seekBar, int index, boolean fromUser) {
+
+ sendAnimatePickMessage(index, seekBar.getWidth(), mLastIndex);
+
+ // get the index of the underlying list
+ int adapterIndex = mSectionContainer.getDirectionalIndex(mLastIndex, index);
+ // Post set target index on queue to get processed by Looper later
+ sendSetTargetMessage(adapterIndex);
+
+ mLastIndex = index;
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ touchTrack(true);
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ touchTrack(false);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/BaseRecyclerViewScrubberSection.java b/src/com/android/launcher3/BaseRecyclerViewScrubberSection.java
new file mode 100644
index 000000000..1d17ea887
--- /dev/null
+++ b/src/com/android/launcher3/BaseRecyclerViewScrubberSection.java
@@ -0,0 +1,267 @@
+/*
+ * 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 android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+
+public class BaseRecyclerViewScrubberSection {
+ private static final String TAG = "BRVScrubberSections";
+ private static final String ALPHA_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ private static final int MAX_NUMBER_CUSTOM_SECTIONS = 8;
+ private static final int MAX_SECTIONS = ALPHA_LETTERS.length() + MAX_NUMBER_CUSTOM_SECTIONS;
+ public static final int INVALID_INDEX = -1;
+
+ private AutoExpandTextView.HighlightedText mHighlightedText;
+ private int mPreviousValidIndex;
+ private int mNextValidIndex;
+ private int mAdapterIndex;
+
+ public BaseRecyclerViewScrubberSection(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(RtlIndexArrayList<BaseRecyclerViewScrubberSection> sections,
+ boolean isRtl) {
+ for (int i = 0; i < sections.size(); i++) {
+ if (sections.get(i, isRtl).getHighlight()) {
+ return i;
+ }
+ }
+
+ return INVALID_INDEX;
+ }
+
+ private static void createIndices(RtlIndexArrayList<BaseRecyclerViewScrubberSection> sections,
+ boolean isRtl) {
+ 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, isRtl);
+ for (int i = 0; i < sections.size(); i++) {
+ if (sections.get(i, isRtl).getHighlight()) {
+ currentIdx = i;
+ }
+
+ sections.get(i, isRtl).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, isRtl).getHighlight()) {
+ currentIdx = i;
+ }
+
+ sections.get(i, isRtl).mNextValidIndex = currentIdx;
+ }
+ }
+
+ public static ArrayList<AutoExpandTextView.HighlightedText> getHighlightText(
+ RtlIndexArrayList<BaseRecyclerViewScrubberSection> sections) {
+ if (sections == null) {
+ return null;
+ }
+
+ ArrayList<AutoExpandTextView.HighlightedText> highlights = new ArrayList<>(sections.size());
+ for (BaseRecyclerViewScrubberSection section : sections) {
+ highlights.add(section.mHighlightedText);
+ }
+
+ return highlights;
+ }
+
+ private static void addAlphaLetters(RtlIndexArrayList<BaseRecyclerViewScrubberSection> 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) : BaseRecyclerViewScrubberSection.INVALID_INDEX;
+
+ sections.add(new BaseRecyclerViewScrubberSection(ALPHA_LETTERS.substring(i, i + 1),
+ highlighted, index));
+ }
+ }
+
+ /**
+ * Takes the sections 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 sectionNames list of sectionName Strings
+ * @return the list of scrubber sections
+ */
+ public static RtlIndexArrayList<BaseRecyclerViewScrubberSection>
+ createSections(String[] sectionNames, boolean isRtl) {
+ // check if we have a valid header section
+ if (!validSectionNameList(sectionNames)) {
+ return null;
+ }
+
+ // this will track the mapping of ALPHA_LETTERS index to the headers index
+ HashMap<Integer, Integer> foundAlphaLetters = new HashMap<>();
+ RtlIndexArrayList<BaseRecyclerViewScrubberSection> sections =
+ new RtlIndexArrayList<>(sectionNames.length);
+ boolean inAlphaLetterSection = false;
+
+ for (int i = 0; i < sectionNames.length; i++) {
+ int alphaLetterIndex = TextUtils.isEmpty(sectionNames[i])
+ ? -1 : ALPHA_LETTERS.indexOf(sectionNames[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 BaseRecyclerViewScrubberSection(sectionNames[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
+ BaseRecyclerViewScrubberSection.createIndices(sections, isRtl);
+
+ return sections;
+ }
+
+ /**
+ * Walk through the sectionNames and check for a few things:
+ * 1) No more than MAX_NUMBER_CUSTOM_SECTIONS sectionNames exist in the sectionNames list or no more
+ * than MAX_SECTIONS sectionNames 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 sectionNames that exceed length of 1
+ * 4) The alpha letter sectionName is together and not separated by other things
+ */
+ private static boolean validSectionNameList(String[] sectionNames) {
+ int numCustomSections = 0;
+ int previousAlphaIndex = -1;
+ boolean foundAlphaSections = false;
+
+ for (String s : sectionNames) {
+ if (TextUtils.isEmpty(s)) {
+ numCustomSections++;
+ continue;
+ }
+
+ if (s.length() > 1) {
+ Log.w(TAG, "Found section " + 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 (foundAlphaSections && previousAlphaIndex == -1) {
+ Log.w(TAG, "Found alpha letters twice");
+ return false;
+ }
+
+ previousAlphaIndex = alphaIndex;
+ foundAlphaSections = true;
+ } else {
+ numCustomSections++;
+ previousAlphaIndex = -1;
+ }
+ }
+
+ final int listSize = foundAlphaSections
+ ? numCustomSections + ALPHA_LETTERS.length()
+ : numCustomSections;
+
+ // if one of these conditions are satisfied, then return true
+ if (numCustomSections <= MAX_NUMBER_CUSTOM_SECTIONS || listSize <= MAX_SECTIONS) {
+ return true;
+ }
+
+ if (numCustomSections > MAX_NUMBER_CUSTOM_SECTIONS) {
+ Log.w(TAG, "Found " + numCustomSections + "# custom sections when " +
+ MAX_NUMBER_CUSTOM_SECTIONS + " is allowed!");
+ } else if (listSize > MAX_SECTIONS) {
+ Log.w(TAG, "Found " + listSize + " sections when " +
+ MAX_SECTIONS + " is allowed!");
+ }
+
+ return false;
+ }
+
+ public static class RtlIndexArrayList<T> extends ArrayList<T> {
+
+ public RtlIndexArrayList(int capacity) {
+ super(capacity);
+ }
+
+ public T get(int index, boolean isRtl) {
+ if (isRtl) {
+ index = size() - 1 - index;
+ }
+ return super.get(index);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 507087824..205c113a7 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -86,7 +86,7 @@ public class BubbleTextView extends TextView
private final boolean mDeferShadowGenerationOnTouch;
private final boolean mCustomShadowsEnabled;
private final boolean mLayoutHorizontal;
- private final int mIconSize;
+ private int mIconSize;
private int mTextColor;
private boolean mStayPressed;
@@ -94,9 +94,11 @@ public class BubbleTextView extends TextView
private boolean mDisableRelayout = false;
private ObjectAnimator mFastScrollFocusAnimator;
+ private ObjectAnimator mFastScrollDimAnimator;
private Paint mFastScrollFocusBgPaint;
private float mFastScrollFocusFraction;
private boolean mFastScrollFocused;
+ private boolean mFastScrollDimmed;
private final int mFastScrollMode = FAST_SCROLL_FOCUS_MODE_SCALE_ICON;
private IconLoadRequest mIconLoadRequest;
@@ -433,6 +435,10 @@ public class BubbleTextView extends TextView
if (mBackground != null) mBackground.setCallback(null);
}
+ public void setIconSize(int iconSize) {
+ mIconSize = iconSize;
+ }
+
@Override
public void setTextColor(int color) {
mTextColor = color;
@@ -628,6 +634,30 @@ public class BubbleTextView extends TextView
}
}
+ @Override
+ public void setFastScrollDimmed(boolean dimmed, boolean animated) {
+ if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_NONE) {
+ return;
+ }
+
+ if (!animated) {
+ mFastScrollDimmed = dimmed;
+ setAlpha(dimmed ? 0.4f : 1f);
+ } else if (mFastScrollDimmed != dimmed) {
+ mFastScrollDimmed = dimmed;
+
+ // Clean up the previous dim animator
+ if (mFastScrollDimAnimator != null) {
+ mFastScrollDimAnimator.cancel();
+ }
+ mFastScrollDimAnimator = ObjectAnimator.ofFloat(this, View.ALPHA,
+ dimmed ? 0.4f : 1f);
+ mFastScrollDimAnimator.setDuration(dimmed ?
+ FAST_SCROLL_FOCUS_FADE_IN_DURATION : FAST_SCROLL_FOCUS_FADE_OUT_DURATION);
+ mFastScrollDimAnimator.start();
+ }
+ }
+
/**
* Interface to be implemented by the grand parent to allow click shadow effect.
*/
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 774594fe2..f77b34862 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -33,6 +33,8 @@ import android.view.ViewGroup.MarginLayoutParams;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
+import com.android.launcher3.allapps.AllAppsContainerView;
+
public class DeviceProfile {
public final InvariantDeviceProfile inv;
@@ -225,11 +227,13 @@ public class DeviceProfile {
/**
* @param recyclerViewWidth the available width of the AllAppsRecyclerView
*/
- public void updateAppsViewNumCols(Resources res, int recyclerViewWidth) {
- int appsViewLeftMarginPx =
- res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
- int allAppsCellWidthGap =
- res.getDimensionPixelSize(R.dimen.all_apps_icon_width_gap);
+ public void updateAppsViewNumCols(Resources res, int recyclerViewWidth, int gridStrategy) {
+ int appsViewLeftMarginPx = gridStrategy == AllAppsContainerView.SECTION_STRATEGY_GRID ?
+ res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin) :
+ res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin_with_sections);
+ int allAppsCellWidthGap = gridStrategy == AllAppsContainerView.SECTION_STRATEGY_GRID ?
+ res.getDimensionPixelSize(R.dimen.all_apps_icon_width_gap) :
+ res.getDimensionPixelSize(R.dimen.all_apps_icon_width_gap_with_sections);
int availableAppsWidthPx = (recyclerViewWidth > 0) ? recyclerViewWidth : availableWidthPx;
int numAppsCols = (availableAppsWidthPx - appsViewLeftMarginPx) /
(allAppsIconSizePx + allAppsCellWidthGap);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 6faea2084..1976ca982 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -348,6 +348,8 @@ public class Launcher extends Activity
private DeviceProfile mDeviceProfile;
+ private boolean mUseScrubber = true;
+
// This is set to the view that launched the activity that navigated the user away from
// launcher. Since there is no callback for when the activity has finished launching, enable
// the press state and keep this reference to reset the press state when we return to launcher.
@@ -1448,6 +1450,11 @@ public class Launcher extends Activity
mAppsView.setSearchBarController(mAppsView.newDefaultAppSearchController());
}
+ mAppsView.setUseScrubber(mUseScrubber);
+ mAppsView.setSectionStrategy(AllAppsContainerView.SECTION_STRATEGY_RAGGED);
+ mAppsView.setGridTheme(AllAppsContainerView.GRID_THEME_DARK);
+ mWidgetsView.setUseScrubber(false);
+
// Setup the drag controller (drop targets have to be added in reverse order in priority)
dragController.setDragScoller(mWorkspace);
dragController.setScrollView(mDragLayer);
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 88c6acada..bff7752af 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -35,6 +35,7 @@ import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.android.launcher3.AppInfo;
import com.android.launcher3.BaseContainerView;
+import com.android.launcher3.BaseRecyclerView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeleteDropTarget;
import com.android.launcher3.DeviceProfile;
@@ -130,8 +131,14 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
LauncherTransitionable, View.OnTouchListener, View.OnLongClickListener,
AllAppsSearchBarController.Callbacks {
+ public static final int SECTION_STRATEGY_GRID = 1;
+ public static final int SECTION_STRATEGY_RAGGED = 2;
+
+ public static final int GRID_THEME_LIGHT = 1;
+ public static final int GRID_THEME_DARK = 2;
+
private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3;
- private static final int MAX_NUM_MERGES_PHONE = 2;
+ private static final int MAX_NUM_MERGES_PHONE = 1;
@Thunk Launcher mLauncher;
@Thunk AlphabeticalAppsList mApps;
@@ -148,6 +155,10 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
private View mSearchBarView;
private SpannableStringBuilder mSearchQueryBuilder = null;
+ private int mSectionStrategy = SECTION_STRATEGY_RAGGED;
+ private int mGridTheme = GRID_THEME_DARK;
+ private int mLastGridTheme = -1;
+
private int mSectionNamesMargin;
private int mNumAppsPerRow;
private int mNumPredictedAppsPerRow;
@@ -178,9 +189,12 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
Resources res = context.getResources();
mLauncher = (Launcher) context;
- mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
+ mSectionNamesMargin = mSectionStrategy == SECTION_STRATEGY_GRID ?
+ res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin) :
+ res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin_with_sections);
mApps = new AlphabeticalAppsList(context);
- mAdapter = new AllAppsGridAdapter(mLauncher, mApps, this, mLauncher, this);
+ mAdapter = new AllAppsGridAdapter(mLauncher, mApps, this, mLauncher,
+ this, mSectionStrategy, mGridTheme);
mApps.setAdapter(mAdapter);
mLayoutManager = mAdapter.getLayoutManager();
mItemDecoration = mAdapter.getItemDecoration();
@@ -196,6 +210,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
*/
public void setPredictedApps(List<ComponentKey> apps) {
mApps.setPredictedApps(apps);
+ updateScrubber();
}
/**
@@ -203,6 +218,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
*/
public void setApps(List<AppInfo> apps) {
mApps.setApps(apps);
+ updateScrubber();
}
/**
@@ -210,6 +226,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
*/
public void addApps(List<AppInfo> apps) {
mApps.addApps(apps);
+ updateScrubber();
}
/**
@@ -217,6 +234,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
*/
public void updateApps(List<AppInfo> apps) {
mApps.updateApps(apps);
+ updateScrubber();
}
/**
@@ -224,6 +242,29 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
*/
public void removeApps(List<AppInfo> apps) {
mApps.removeApps(apps);
+ updateScrubber();
+ }
+
+ private void updateScrubber() {
+ if (userScrubber()) {
+ mScrubber.updateSections();
+ }
+ }
+
+ public void setSectionStrategy(int sectionStrategy) {
+ Resources res = getResources();
+ mSectionStrategy = sectionStrategy;
+ mSectionNamesMargin = mSectionStrategy == SECTION_STRATEGY_GRID ?
+ res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin) :
+ res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin_with_sections);
+ mAdapter.setSectionStrategy(mSectionStrategy);
+ mAppsRecyclerView.setSectionStrategy(mSectionStrategy);
+ }
+
+ public void setGridTheme(int gridTheme) {
+ mGridTheme = gridTheme;
+ mAdapter.setGridTheme(mGridTheme);
+ updateBackgroundAndPaddings(true);
}
/**
@@ -316,6 +357,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
// Load the all apps recycler view
mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
mAppsRecyclerView.setApps(mApps);
+ mAppsRecyclerView.setSectionStrategy(mSectionStrategy);
mAppsRecyclerView.setLayoutManager(mLayoutManager);
mAppsRecyclerView.setAdapter(mAdapter);
mAppsRecyclerView.setHasFixedSize(true);
@@ -337,7 +379,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
int availableWidth = !mContentBounds.isEmpty() ? mContentBounds.width() :
MeasureSpec.getSize(widthMeasureSpec);
DeviceProfile grid = mLauncher.getDeviceProfile();
- grid.updateAppsViewNumCols(getResources(), availableWidth);
+ grid.updateAppsViewNumCols(getResources(), availableWidth,
+ mSectionStrategy);
if (mNumAppsPerRow != grid.allAppsNumCols ||
mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
mNumAppsPerRow = grid.allAppsNumCols;
@@ -345,7 +388,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
// If there is a start margin to draw section names, determine how we are going to merge
// app sections
- boolean mergeSectionsFully = mSectionNamesMargin == 0 || !grid.isPhone;
+ boolean mergeSectionsFully = mSectionStrategy == SECTION_STRATEGY_GRID;
AlphabeticalAppsList.MergeAlgorithm mergeAlgorithm = mergeSectionsFully ?
new FullMergeAlgorithm() :
new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f),
@@ -369,8 +412,10 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
boolean isRtl = Utilities.isRtl(getResources());
// TODO: Use quantum_panel instead of quantum_panel_shape
+ int bgRes = mGridTheme == GRID_THEME_DARK ? R.drawable.quantum_panel_shape_dark :
+ R.drawable.quantum_panel_shape;
InsetDrawable background = new InsetDrawable(
- getResources().getDrawable(R.drawable.quantum_panel_shape), padding.left, 0,
+ getResources().getDrawable(bgRes), padding.left, 0,
padding.right, 0);
Rect bgPadding = new Rect();
background.getPadding(bgPadding);
@@ -389,12 +434,24 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
// names)
int startInset = Math.max(mSectionNamesMargin, mAppsRecyclerView.getMaxScrollbarWidth());
int topBottomPadding = mRecyclerViewTopBottomPadding;
+ final boolean useScubber = userScrubber();
if (isRtl) {
mAppsRecyclerView.setPadding(padding.left + mAppsRecyclerView.getMaxScrollbarWidth(),
- topBottomPadding, padding.right + startInset, topBottomPadding);
+ topBottomPadding, padding.right + startInset, useScubber ?
+ mScrubberHeight + topBottomPadding : topBottomPadding);
+ if (useScubber) {
+ mScrubberContainerView
+ .setPadding(padding.left + mAppsRecyclerView.getMaxScrollbarWidth(),
+ 0, padding.right, 0);
+ }
} else {
mAppsRecyclerView.setPadding(padding.left + startInset, topBottomPadding,
- padding.right + mAppsRecyclerView.getMaxScrollbarWidth(), topBottomPadding);
+ padding.right + mAppsRecyclerView.getMaxScrollbarWidth(),
+ useScubber ? mScrubberHeight + topBottomPadding : topBottomPadding);
+ if (useScubber) {
+ mScrubberContainerView.setPadding(padding.left, 0,
+ padding.right + mAppsRecyclerView.getMaxScrollbarWidth(), 0);
+ }
}
// Inset the search bar to fit its bounds above the container
@@ -556,7 +613,9 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
if (toWorkspace) {
// Reset the search bar and base recycler view after transitioning home
- mSearchBarController.reset();
+ if (hasSearchBar()) {
+ mSearchBarController.reset();
+ }
mAppsRecyclerView.reset();
}
}
@@ -614,6 +673,12 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
if (apps != null) {
mApps.setOrderedFilter(apps);
+ if (mGridTheme != GRID_THEME_LIGHT) {
+ mLastGridTheme = mGridTheme;
+ mGridTheme = GRID_THEME_LIGHT;
+ updateBackgroundAndPaddings(true);
+ mAdapter.setGridTheme(mGridTheme);
+ }
mAdapter.setLastSearchQuery(query);
mAppsRecyclerView.onSearchResultsChanged();
}
@@ -623,10 +688,20 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
public void clearSearchResult() {
mApps.setOrderedFilter(null);
mAppsRecyclerView.onSearchResultsChanged();
-
+ if (mLastGridTheme != -1 && mLastGridTheme != GRID_THEME_LIGHT) {
+ mGridTheme = mLastGridTheme;
+ updateBackgroundAndPaddings(true);
+ mAdapter.setGridTheme(mGridTheme);
+ mLastGridTheme = -1;
+ }
// Clear the search query
mSearchQueryBuilder.clear();
mSearchQueryBuilder.clearSpans();
Selection.setSelection(mSearchQueryBuilder, 0);
}
+
+ @Override
+ protected BaseRecyclerView getRecyclerView() {
+ return mAppsRecyclerView;
+ }
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 1f95133d4..a48390732 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -24,7 +24,6 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.net.Uri;
@@ -69,6 +68,10 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
// The message to continue to a market search when there are no filtered results
public static final int SEARCH_MARKET_VIEW_TYPE = 5;
+ private boolean mIconsDimmed = false;
+
+ private int mGridTheme;
+
/**
* ViewHolder for each icon.
*/
@@ -142,7 +145,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
public class GridItemDecoration extends RecyclerView.ItemDecoration {
private static final boolean DEBUG_SECTION_MARGIN = false;
- private static final boolean FADE_OUT_SECTIONS = false;
+ private static final boolean FADE_OUT_SECTIONS = true;
private HashMap<String, PointF> mCachedSectionBounds = new HashMap<>();
private Rect mTmpBounds = new Rect();
@@ -349,12 +352,16 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
// Section drawing
@Thunk int mSectionNamesMargin;
@Thunk int mSectionHeaderOffset;
+ @Thunk int mSectionStrategy;
@Thunk Paint mSectionTextPaint;
@Thunk Paint mPredictedAppsDividerPaint;
+ private int mIconSize;
+ private int mAllAppsTextColor;
+
public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps,
View.OnTouchListener touchListener, View.OnClickListener iconClickListener,
- View.OnLongClickListener iconLongClickListener) {
+ View.OnLongClickListener iconLongClickListener, int sectionStrategy, int gridTheme) {
Resources res = launcher.getResources();
mLauncher = launcher;
mApps = apps;
@@ -367,13 +374,31 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
mTouchListener = touchListener;
mIconClickListener = iconClickListener;
mIconLongClickListener = iconLongClickListener;
- mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
+ mSectionStrategy = sectionStrategy;
+ mGridTheme = gridTheme;
+ mSectionNamesMargin = mSectionStrategy ==
+ AllAppsContainerView.SECTION_STRATEGY_GRID ?
+ res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin) :
+ res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin_with_sections);
+
+ mIconSize = mSectionStrategy ==
+ AllAppsContainerView.SECTION_STRATEGY_GRID ?
+ res.getDimensionPixelSize(R.dimen.all_apps_icon_size_grid) :
+ res.getDimensionPixelSize(R.dimen.all_apps_icon_size_ragged);
+
+ mAllAppsTextColor = mGridTheme == AllAppsContainerView.GRID_THEME_DARK ?
+ res.getColor(R.color.quantum_panel_text_color_dark) :
+ res.getColor(R.color.quantum_panel_text_color);
+
mSectionHeaderOffset = res.getDimensionPixelSize(R.dimen.all_apps_grid_section_y_offset);
mSectionTextPaint = new Paint();
mSectionTextPaint.setTextSize(res.getDimensionPixelSize(
R.dimen.all_apps_grid_section_text_size));
- mSectionTextPaint.setColor(res.getColor(R.color.all_apps_grid_section_text_color));
+ int sectionTextColorId = mGridTheme == AllAppsContainerView.GRID_THEME_DARK ?
+ R.color.all_apps_grid_section_text_color_dark :
+ R.color.all_apps_grid_section_text_color;
+ mSectionTextPaint.setColor(res.getColor(sectionTextColorId));
mSectionTextPaint.setAntiAlias(true);
mPredictedAppsDividerPaint = new Paint();
@@ -408,6 +433,19 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
mIsRtl = rtl;
}
+ public void setSectionStrategy(int sectionStrategy) {
+ Resources res = mLauncher.getResources();
+ mSectionStrategy = sectionStrategy;
+ mSectionNamesMargin = mSectionStrategy ==
+ AllAppsContainerView.SECTION_STRATEGY_GRID ?
+ res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin) :
+ res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin_with_sections);
+ mIconSize = mSectionStrategy ==
+ AllAppsContainerView.SECTION_STRATEGY_GRID ?
+ res.getDimensionPixelSize(R.dimen.all_apps_icon_size_grid) :
+ res.getDimensionPixelSize(R.dimen.all_apps_icon_size_ragged);
+ }
+
/**
* Sets the last search query that was made, used to show when there are no results and to also
* seed the intent for searching the market.
@@ -501,12 +539,17 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
case ICON_VIEW_TYPE: {
AppInfo info = mApps.getAdapterItems().get(position).appInfo;
BubbleTextView icon = (BubbleTextView) holder.mContent;
+ icon.setIconSize(mIconSize);
+ icon.setTextColor(mAllAppsTextColor);
icon.applyFromApplicationInfo(info);
+ icon.setFastScrollDimmed(mIconsDimmed, !mIconsDimmed);
break;
}
case PREDICTION_ICON_VIEW_TYPE: {
AppInfo info = mApps.getAdapterItems().get(position).appInfo;
BubbleTextView icon = (BubbleTextView) holder.mContent;
+ icon.setIconSize(mIconSize);
+ icon.setTextColor(mAllAppsTextColor);
icon.applyFromApplicationInfo(info);
break;
}
@@ -542,6 +585,25 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
return item.viewType;
}
+ public void setIconsDimmed(boolean iconsDimmed) {
+ if (mIconsDimmed != iconsDimmed) {
+ mIconsDimmed = iconsDimmed;
+ notifyDataSetChanged();
+ }
+ }
+
+ public void setGridTheme(int gridTheme) {
+ mGridTheme = gridTheme;
+ int sectionTextColorId = mGridTheme == AllAppsContainerView.GRID_THEME_DARK ?
+ R.color.all_apps_grid_section_text_color_dark :
+ R.color.all_apps_grid_section_text_color;
+ mSectionTextPaint.setColor(mLauncher.getResources().getColor(sectionTextColorId));
+ Resources res = mLauncher.getResources();
+ mAllAppsTextColor = mGridTheme == AllAppsContainerView.GRID_THEME_DARK ?
+ res.getColor(R.color.all_apps_grid_section_text_color_dark) :
+ res.getColor(R.color.all_apps_grid_section_text_color);
+ }
+
/**
* Creates a new market search intent.
*/
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 2f66e2cad..6c853a037 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.allapps;
-import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
@@ -34,6 +33,7 @@ import com.android.launcher3.Stats;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.Thunk;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -50,9 +50,15 @@ public class AllAppsRecyclerView extends BaseRecyclerView
private AlphabeticalAppsList mApps;
private int mNumAppsPerRow;
+ private int mSectionStrategy = AllAppsContainerView.SECTION_STRATEGY_RAGGED;
+
+ private boolean mFocusSection = false;
@Thunk BaseRecyclerViewFastScrollBar.FastScrollFocusableView mLastFastScrollFocusedView;
+ @Thunk ArrayList<BaseRecyclerViewFastScrollBar.FastScrollFocusableView>
+ mLastFastScrollFocusedViews = new ArrayList();
@Thunk int mPrevFastScrollFocusedPosition;
+ @Thunk AlphabeticalAppsList.SectionInfo mPrevFastScrollFocusedSection;
@Thunk int mFastScrollFrameIndex;
@Thunk final int[] mFastScrollFrames = new int[10];
@@ -81,7 +87,9 @@ public class AllAppsRecyclerView extends BaseRecyclerView
super(context, attrs, defStyleAttr);
Resources res = getResources();
- mScrollbar.setDetachThumbOnFastScroll();
+ if (mUseScrollbar) {
+ mScrollbar.setDetachThumbOnFastScroll();
+ }
mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
R.dimen.all_apps_empty_search_bg_top_offset);
}
@@ -109,13 +117,20 @@ public class AllAppsRecyclerView extends BaseRecyclerView
pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
}
+ public void setSectionStrategy(int sectionStrategy) {
+ mSectionStrategy = sectionStrategy;
+ mFocusSection = mSectionStrategy == AllAppsContainerView.SECTION_STRATEGY_RAGGED;
+ }
+
/**
* Scrolls this recycler view to the top.
*/
public void scrollToTop() {
- // Ensure we reattach the scrollbar if it was previously detached while fast-scrolling
- if (mScrollbar.isThumbDetached()) {
- mScrollbar.reattachThumbToScroll();
+ if (mUseScrollbar) {
+ // Ensure we reattach the scrollbar if it was previously detached while fast-scrolling
+ if (mScrollbar.isThumbDetached()) {
+ mScrollbar.reattachThumbToScroll();
+ }
}
scrollToPosition(0);
}
@@ -235,10 +250,18 @@ public class AllAppsRecyclerView extends BaseRecyclerView
}
if (mPrevFastScrollFocusedPosition != lastInfo.fastScrollToItem.position) {
+ if (mFocusSection) {
+ setSectionFastScrollDimmed(mPrevFastScrollFocusedPosition, true, true);
+ } else if (mLastFastScrollFocusedView != null){
+ mLastFastScrollFocusedView.setFastScrollDimmed(true, true);
+ }
mPrevFastScrollFocusedPosition = lastInfo.fastScrollToItem.position;
-
- // Reset the last focused view
- if (mLastFastScrollFocusedView != null) {
+ mPrevFastScrollFocusedSection =
+ getSectionInfoForPosition(lastInfo.fastScrollToItem.position);
+ // Reset the last focused section
+ if (mFocusSection) {
+ clearSectionFocusedItems();
+ } else if (mLastFastScrollFocusedView != null) {
mLastFastScrollFocusedView.setFastScrollFocused(false, true);
mLastFastScrollFocusedView = null;
}
@@ -246,12 +269,17 @@ public class AllAppsRecyclerView extends BaseRecyclerView
if (mFastScrollMode == FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON) {
smoothSnapToPosition(mPrevFastScrollFocusedPosition, mScrollPosState);
} else if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
- final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition);
- if (vh != null &&
- vh.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
- mLastFastScrollFocusedView =
- (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView;
- mLastFastScrollFocusedView.setFastScrollFocused(true, true);
+ if (mFocusSection) {
+ setSectionFastScrollFocused(mPrevFastScrollFocusedPosition);
+ } else {
+ final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition);
+ if (vh != null &&
+ vh.itemView instanceof
+ BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
+ mLastFastScrollFocusedView =
+ (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView;
+ mLastFastScrollFocusedView.setFastScrollFocused(true, true);
+ }
}
} else {
throw new RuntimeException("Unexpected fast scroll mode");
@@ -264,11 +292,14 @@ public class AllAppsRecyclerView extends BaseRecyclerView
public void onFastScrollCompleted() {
super.onFastScrollCompleted();
// Reset and clean up the last focused view
- if (mLastFastScrollFocusedView != null) {
+ if (mFocusSection) {
+ clearSectionFocusedItems();
+ } else if (mLastFastScrollFocusedView != null) {
mLastFastScrollFocusedView.setFastScrollFocused(false, true);
mLastFastScrollFocusedView = null;
}
mPrevFastScrollFocusedPosition = -1;
+ mPrevFastScrollFocusedSection = null;
}
/**
@@ -276,6 +307,9 @@ public class AllAppsRecyclerView extends BaseRecyclerView
*/
@Override
public void onUpdateScrollbar(int dy) {
+ if (!mUseScrollbar) {
+ return;
+ }
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
// Skip early if there are no items or we haven't been measured
@@ -294,7 +328,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView
// Only show the scrollbar if there is height to be scrolled
int availableScrollBarHeight = getAvailableScrollBarHeight();
- int availableScrollHeight = getAvailableScrollHeight(mApps.getNumAppRows(), mScrollPosState.rowHeight);
+ int availableScrollHeight = getAvailableScrollHeight(mApps.getNumAppRows(),
+ mScrollPosState.rowHeight);
if (availableScrollHeight <= 0) {
mScrollbar.setThumbOffset(-1, -1);
return;
@@ -354,6 +389,97 @@ public class AllAppsRecyclerView extends BaseRecyclerView
}
}
+ @Override
+ public String scrollToSection(String sectionName) {
+ List<AlphabeticalAppsList.FastScrollSectionInfo> scrollSectionInfos =
+ mApps.getFastScrollerSections();
+ if (scrollSectionInfos != null) {
+ for (int i = 0; i < scrollSectionInfos.size(); i++) {
+ AlphabeticalAppsList.FastScrollSectionInfo info = scrollSectionInfos.get(i);
+ if (info.sectionName.equals(sectionName)) {
+ scrollToPositionAtProgress(info.touchFraction);
+ return info.sectionName;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String[] getSectionNames() {
+ List<AlphabeticalAppsList.FastScrollSectionInfo> scrollSectionInfos =
+ mApps.getFastScrollerSections();
+ if (scrollSectionInfos != null) {
+ String[] sectionNames = new String[scrollSectionInfos.size()];
+ for (int i = 0; i < scrollSectionInfos.size(); i++) {
+ AlphabeticalAppsList.FastScrollSectionInfo info = scrollSectionInfos.get(i);
+ sectionNames[i] = info.sectionName;
+ }
+
+ return sectionNames;
+ }
+ return new String[0];
+ }
+
+ private AlphabeticalAppsList.SectionInfo getSectionInfoForPosition(int position) {
+ List<AlphabeticalAppsList.SectionInfo> sections =
+ mApps.getSections();
+ for (AlphabeticalAppsList.SectionInfo section : sections) {
+ if (section.firstAppItem.position == position) {
+ return section;
+ }
+ }
+ return null;
+ }
+
+ private void setSectionFastScrollFocused(int position) {
+ if (mPrevFastScrollFocusedSection != null) {
+ for (int i = 0; i < mPrevFastScrollFocusedSection.numApps; i++) {
+ int sectionPosition = position+i;
+ final ViewHolder vh = findViewHolderForAdapterPosition(sectionPosition);
+ if (vh != null &&
+ vh.itemView instanceof
+ BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
+ final BaseRecyclerViewFastScrollBar.FastScrollFocusableView view =
+ (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView;
+ view.setFastScrollFocused(true, true);
+ mLastFastScrollFocusedViews.add(view);
+ }
+ }
+ }
+ }
+
+ private void setSectionFastScrollDimmed(int position, boolean dimmed, boolean animate) {
+ if (mPrevFastScrollFocusedSection != null) {
+ for (int i = 0; i < mPrevFastScrollFocusedSection.numApps; i++) {
+ int sectionPosition = position+i;
+ final ViewHolder vh = findViewHolderForAdapterPosition(sectionPosition);
+ if (vh != null &&
+ vh.itemView instanceof
+ BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
+ final BaseRecyclerViewFastScrollBar.FastScrollFocusableView view =
+ (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView;
+ view.setFastScrollDimmed(dimmed, animate);
+ }
+ }
+ }
+ }
+
+ private void clearSectionFocusedItems() {
+ final int N = mLastFastScrollFocusedViews.size();
+ for (int i = 0; i < N; i++) {
+ BaseRecyclerViewFastScrollBar.FastScrollFocusableView view =
+ mLastFastScrollFocusedViews.get(i);
+ view.setFastScrollFocused(false, true);
+ }
+ mLastFastScrollFocusedViews.clear();
+ }
+
+ @Override
+ public void setFastScrollDragging(boolean dragging) {
+ ((AllAppsGridAdapter) getAdapter()).setIconsDimmed(dragging);
+ }
+
/**
* This runnable runs a single frame of the smooth scroll animation and posts the next frame
* if necessary.
@@ -362,18 +488,27 @@ public class AllAppsRecyclerView extends BaseRecyclerView
@Override
public void run() {
if (mFastScrollFrameIndex < mFastScrollFrames.length) {
+ if (mFocusSection) {
+ setSectionFastScrollDimmed(mPrevFastScrollFocusedPosition, false, true);
+ }
scrollBy(0, mFastScrollFrames[mFastScrollFrameIndex]);
mFastScrollFrameIndex++;
postOnAnimation(mSmoothSnapNextFrameRunnable);
} else {
- // Animation completed, set the fast scroll state on the target view
- final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition);
- if (vh != null &&
- vh.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView &&
- mLastFastScrollFocusedView != vh.itemView) {
- mLastFastScrollFocusedView =
- (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView;
- mLastFastScrollFocusedView.setFastScrollFocused(true, true);
+ if (mFocusSection) {
+ setSectionFastScrollFocused(mPrevFastScrollFocusedPosition);
+ } else {
+ // Animation completed, set the fast scroll state on the target view
+ final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition);
+ if (vh != null &&
+ vh.itemView instanceof
+ BaseRecyclerViewFastScrollBar.FastScrollFocusableView &&
+ mLastFastScrollFocusedView != vh.itemView) {
+ mLastFastScrollFocusedView =
+ (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView;
+ mLastFastScrollFocusedView.setFastScrollFocused(true, true);
+ mLastFastScrollFocusedView.setFastScrollDimmed(false, true);
+ }
}
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
index 14e2a1863..d5ebdab07 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
@@ -17,9 +17,7 @@ package com.android.launcher3.allapps;
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.util.AttributeSet;
-import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.android.launcher3.BubbleTextView;
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 0c6ea31bb..268e26ebb 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -29,6 +29,7 @@ import android.view.View;
import android.widget.Toast;
import com.android.launcher3.BaseContainerView;
+import com.android.launcher3.BaseRecyclerView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeleteDropTarget;
import com.android.launcher3.DeviceProfile;
@@ -366,4 +367,9 @@ public class WidgetsContainerView extends BaseContainerView
}
return mWidgetPreviewLoader;
}
+
+ @Override
+ protected BaseRecyclerView getRecyclerView() {
+ return mView;
+ }
} \ No newline at end of file
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 884bdc418..ac32f154e 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -126,20 +126,34 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
// Skip early if there are no widgets.
int rowCount = mWidgets.getPackageSize();
if (rowCount == 0) {
- mScrollbar.setThumbOffset(-1, -1);
+ if (mUseScrollbar) {
+ mScrollbar.setThumbOffset(-1, -1);
+ }
return;
}
// Skip early if, there no child laid out in the container.
getCurScrollState(mScrollPosState);
if (mScrollPosState.rowIndex < 0) {
- mScrollbar.setThumbOffset(-1, -1);
+ if (mUseScrollbar) {
+ mScrollbar.setThumbOffset(-1, -1);
+ }
return;
}
synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount);
}
+ @Override
+ public String scrollToSection(String sectionName) {
+ return null;
+ }
+
+ @Override
+ public String[] getSectionNames() {
+ return new String[0];
+ }
+
/**
* Returns the current scroll state.
*/