Refactoring fast scroller.
- Fixing issue with fast scroller not fitting name width. - Refactoring fast scrolling/scroll bar code out of base recycler view - Adding animations to fast scroller to match design - Smooth scrolling when jumping between app rows - Fixing issue with fast scroller jumping when you first pick it up - Fixing issue with wrong background paddings being used Bug: 21874346 Bug: 22031923 Change-Id: I9f011b1f375751f437604b900e95a2942d3f4601
This commit is contained in:
parent
e5106b687f
commit
b1777447d9
|
@ -1,19 +1,29 @@
|
||||||
|
-keep class com.android.launcher3.BaseRecyclerViewFastScrollBar {
|
||||||
|
public void setWidth(int);
|
||||||
|
public int getWidth();
|
||||||
|
public void setTrackAlpha(int);
|
||||||
|
public int getTrackAlpha();
|
||||||
|
}
|
||||||
|
|
||||||
|
-keep class com.android.launcher3.BaseRecyclerViewFastScrollPopup {
|
||||||
|
public void setAlpha(float);
|
||||||
|
public float getAlpha();
|
||||||
|
}
|
||||||
|
|
||||||
|
-keep class com.android.launcher3.BubbleTextView {
|
||||||
|
public void setFastScrollFocus(float);
|
||||||
|
public float getFastScrollFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
-keep class com.android.launcher3.ButtonDropTarget {
|
||||||
|
public int getTextColor();
|
||||||
|
}
|
||||||
|
|
||||||
-keep class com.android.launcher3.CellLayout {
|
-keep class com.android.launcher3.CellLayout {
|
||||||
public float getBackgroundAlpha();
|
public float getBackgroundAlpha();
|
||||||
public void setBackgroundAlpha(float);
|
public void setBackgroundAlpha(float);
|
||||||
}
|
}
|
||||||
|
|
||||||
-keep class com.android.launcher3.DragLayer$LayoutParams {
|
|
||||||
public void setWidth(int);
|
|
||||||
public int getWidth();
|
|
||||||
public void setHeight(int);
|
|
||||||
public int getHeight();
|
|
||||||
public void setX(int);
|
|
||||||
public int getX();
|
|
||||||
public void setY(int);
|
|
||||||
public int getY();
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep class com.android.launcher3.CellLayout$LayoutParams {
|
-keep class com.android.launcher3.CellLayout$LayoutParams {
|
||||||
public void setWidth(int);
|
public void setWidth(int);
|
||||||
public int getWidth();
|
public int getWidth();
|
||||||
|
@ -25,9 +35,20 @@
|
||||||
public int getY();
|
public int getY();
|
||||||
}
|
}
|
||||||
|
|
||||||
-keep class com.android.launcher3.Workspace {
|
-keep class com.android.launcher3.DragLayer$LayoutParams {
|
||||||
public float getBackgroundAlpha();
|
public void setWidth(int);
|
||||||
public void setBackgroundAlpha(float);
|
public int getWidth();
|
||||||
|
public void setHeight(int);
|
||||||
|
public int getHeight();
|
||||||
|
public void setX(int);
|
||||||
|
public int getX();
|
||||||
|
public void setY(int);
|
||||||
|
public int getY();
|
||||||
|
}
|
||||||
|
|
||||||
|
-keep class com.android.launcher3.FastBitmapDrawable {
|
||||||
|
public int getBrightness();
|
||||||
|
public void setBrightness(int);
|
||||||
}
|
}
|
||||||
|
|
||||||
-keep class com.android.launcher3.MemoryDumpActivity {
|
-keep class com.android.launcher3.MemoryDumpActivity {
|
||||||
|
@ -39,16 +60,7 @@
|
||||||
public void setAnimationProgress(float);
|
public void setAnimationProgress(float);
|
||||||
}
|
}
|
||||||
|
|
||||||
-keep class com.android.launcher3.FastBitmapDrawable {
|
-keep class com.android.launcher3.Workspace {
|
||||||
public int getBrightness();
|
public float getBackgroundAlpha();
|
||||||
public void setBrightness(int);
|
public void setBackgroundAlpha(float);
|
||||||
}
|
|
||||||
|
|
||||||
-keep class com.android.launcher3.BaseRecyclerView {
|
|
||||||
public void setFastScrollerAlpha(float);
|
|
||||||
public float getFastScrollerAlpha();
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep class com.android.launcher3.ButtonDropTarget {
|
|
||||||
public int getTextColor();
|
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@
|
||||||
-->
|
-->
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
<solid android:color="@color/all_apps_scrollbar_thumb_color" />
|
<solid android:color="@color/container_fastscroll_thumb_active_color" />
|
||||||
<size
|
<size
|
||||||
android:width="64dp"
|
android:width="64dp"
|
||||||
android:height="64dp" />
|
android:height="64dp" />
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
Copyright (C) 2015 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
<solid android:color="@color/all_apps_scrollbar_thumb_color" />
|
|
||||||
<size android:width="@dimen/all_apps_fast_scroll_bar_width" />
|
|
||||||
</shape>
|
|
|
@ -16,7 +16,7 @@
|
||||||
-->
|
-->
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
<solid android:color="@color/all_apps_scrollbar_thumb_color" />
|
<solid android:color="@color/container_fastscroll_thumb_active_color" />
|
||||||
<size
|
<size
|
||||||
android:width="64dp"
|
android:width="64dp"
|
||||||
android:height="64dp" />
|
android:height="64dp" />
|
|
@ -38,8 +38,8 @@
|
||||||
android:id="@+id/prediction_bar"
|
android:id="@+id/prediction_bar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingTop="@dimen/all_apps_prediction_bar_top_bottom_padding"
|
android:paddingTop="@dimen/all_apps_prediction_bar_top_padding"
|
||||||
android:paddingBottom="@dimen/all_apps_prediction_bar_top_bottom_padding"
|
android:paddingBottom="@dimen/all_apps_prediction_bar_bottom_padding"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:descendantFocusability="afterDescendants"
|
android:descendantFocusability="afterDescendants"
|
||||||
|
|
|
@ -63,8 +63,8 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="@dimen/all_apps_search_bar_height"
|
android:layout_height="@dimen/all_apps_search_bar_height"
|
||||||
android:layout_gravity="end|center_vertical"
|
android:layout_gravity="end|center_vertical"
|
||||||
android:layout_marginEnd="6dp"
|
android:layout_marginEnd="4dp"
|
||||||
android:layout_marginRight="6dp"
|
android:layout_marginRight="4dp"
|
||||||
android:contentDescription="@string/all_apps_search_bar_hint"
|
android:contentDescription="@string/all_apps_search_bar_hint"
|
||||||
android:paddingBottom="13dp"
|
android:paddingBottom="13dp"
|
||||||
android:paddingTop="13dp"
|
android:paddingTop="13dp"
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<resources>
|
<resources>
|
||||||
<!-- All Apps -->
|
<!-- All Apps -->
|
||||||
<dimen name="all_apps_search_bar_height">54dp</dimen>
|
<dimen name="all_apps_search_bar_height">54dp</dimen>
|
||||||
<dimen name="all_apps_icon_top_bottom_padding">16dp</dimen>
|
<dimen name="all_apps_icon_top_bottom_padding">14dp</dimen>
|
||||||
|
|
||||||
<!-- QSB -->
|
<!-- QSB -->
|
||||||
<dimen name="toolbar_button_vertical_padding">8dip</dimen>
|
<dimen name="toolbar_button_vertical_padding">8dip</dimen>
|
||||||
|
|
|
@ -39,11 +39,15 @@
|
||||||
<color name="outline_color">#FFFFFFFF</color>
|
<color name="outline_color">#FFFFFFFF</color>
|
||||||
<color name="widget_text_panel">#FF374248</color>
|
<color name="widget_text_panel">#FF374248</color>
|
||||||
|
|
||||||
|
<!-- Containers -->
|
||||||
|
<color name="container_fastscroll_thumb_inactive_color">#42000000</color>
|
||||||
|
<color name="container_fastscroll_thumb_active_color">#009688</color>
|
||||||
|
|
||||||
<!-- All Apps -->
|
<!-- All Apps -->
|
||||||
<color name="all_apps_scrollbar_thumb_color">#009688</color>
|
|
||||||
<color name="all_apps_grid_section_text_color">#009688</color>
|
<color name="all_apps_grid_section_text_color">#009688</color>
|
||||||
|
|
||||||
<!-- Widgets view -->
|
<!-- Widgets view -->
|
||||||
|
<color name="widgets_view_fastscroll_thumb_inactive_color">#42FFFFFF</color>
|
||||||
<color name="widgets_view_section_text_color">#FFFFFF</color>
|
<color name="widgets_view_section_text_color">#FFFFFF</color>
|
||||||
<color name="widgets_view_item_text_color">#C4C4C4</color>
|
<color name="widgets_view_item_text_color">#C4C4C4</color>
|
||||||
<color name="widgets_cell_color">#263238</color>
|
<color name="widgets_cell_color">#263238</color>
|
||||||
|
|
|
@ -49,12 +49,20 @@
|
||||||
<dimen name="toolbar_button_vertical_padding">4dip</dimen>
|
<dimen name="toolbar_button_vertical_padding">4dip</dimen>
|
||||||
<dimen name="toolbar_button_horizontal_padding">12dip</dimen>
|
<dimen name="toolbar_button_horizontal_padding">12dip</dimen>
|
||||||
|
|
||||||
<!-- All Apps -->
|
<!-- Container -->
|
||||||
<!-- Note: This needs to match the fixed insets for the search box. -->
|
<!-- Note: This needs to match the fixed insets for the search box. -->
|
||||||
<dimen name="container_bounds_inset">8dp</dimen>
|
<dimen name="container_bounds_inset">8dp</dimen>
|
||||||
<!-- Notes: container_bounds_inset - quantum_panel_outer_padding -->
|
<!-- Notes: container_bounds_inset - quantum_panel_outer_padding -->
|
||||||
<dimen name="container_bounds_minus_quantum_panel_padding_inset">4dp</dimen>
|
<dimen name="container_bounds_minus_quantum_panel_padding_inset">4dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="container_fastscroll_thumb_min_width">4dp</dimen>
|
||||||
|
<dimen name="container_fastscroll_thumb_max_width">8dp</dimen>
|
||||||
|
<dimen name="container_fastscroll_thumb_height">64dp</dimen>
|
||||||
|
<dimen name="container_fastscroll_thumb_touch_inset">-24dp</dimen>
|
||||||
|
<dimen name="container_fastscroll_popup_size">72dp</dimen>
|
||||||
|
<dimen name="container_fastscroll_popup_text_size">48dp</dimen>
|
||||||
|
|
||||||
|
<!-- All Apps -->
|
||||||
<dimen name="all_apps_grid_view_start_margin">0dp</dimen>
|
<dimen name="all_apps_grid_view_start_margin">0dp</dimen>
|
||||||
<dimen name="all_apps_grid_section_y_offset">8dp</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_grid_section_text_size">24sp</dimen>
|
||||||
|
@ -62,16 +70,10 @@
|
||||||
<dimen name="all_apps_search_bar_prediction_bar_padding">8dp</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_top_bottom_padding">8dp</dimen>
|
||||||
<dimen name="all_apps_icon_width_gap">24dp</dimen>
|
<dimen name="all_apps_icon_width_gap">24dp</dimen>
|
||||||
<dimen name="all_apps_prediction_bar_top_bottom_padding">16dp</dimen>
|
<!-- The top padding should account for the general all_apps_list_top_bottom_padding -->
|
||||||
|
<dimen name="all_apps_prediction_bar_top_padding">0dp</dimen>
|
||||||
<dimen name="all_apps_fast_scroll_bar_width">4dp</dimen>
|
<dimen name="all_apps_prediction_bar_bottom_padding">16dp</dimen>
|
||||||
<dimen name="all_apps_fast_scroll_scrubber_touch_inset">-24dp</dimen>
|
<dimen name="all_apps_list_top_bottom_padding">8dp</dimen>
|
||||||
<dimen name="all_apps_fast_scroll_popup_size">72dp</dimen>
|
|
||||||
<dimen name="all_apps_fast_scroll_text_size">48dp</dimen>
|
|
||||||
|
|
||||||
<dimen name="all_apps_header_max_elevation">4dp</dimen>
|
|
||||||
<dimen name="all_apps_header_scroll_to_elevation">16dp</dimen>
|
|
||||||
<dimen name="all_apps_header_shadow_height">6dp</dimen>
|
|
||||||
|
|
||||||
<!-- Widget tray -->
|
<!-- Widget tray -->
|
||||||
<dimen name="widget_container_inset">8dp</dimen>
|
<dimen name="widget_container_inset">8dp</dimen>
|
||||||
|
|
|
@ -16,24 +16,15 @@
|
||||||
|
|
||||||
package com.android.launcher3;
|
package com.android.launcher3;
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.animation.AnimatorListenerAdapter;
|
|
||||||
import android.animation.ObjectAnimator;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewConfiguration;
|
|
||||||
|
|
||||||
import com.android.launcher3.util.Thunk;
|
import com.android.launcher3.util.Thunk;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A base {@link RecyclerView}, which does the following:
|
* A base {@link RecyclerView}, which does the following:
|
||||||
* <ul>
|
* <ul>
|
||||||
|
@ -41,7 +32,7 @@ import com.android.launcher3.util.Thunk;
|
||||||
* <li> Enable fast scroller.
|
* <li> Enable fast scroller.
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class BaseRecyclerView extends RecyclerView
|
public abstract class BaseRecyclerView extends RecyclerView
|
||||||
implements RecyclerView.OnItemTouchListener {
|
implements RecyclerView.OnItemTouchListener {
|
||||||
|
|
||||||
private static final int SCROLL_DELTA_THRESHOLD_DP = 4;
|
private static final int SCROLL_DELTA_THRESHOLD_DP = 4;
|
||||||
|
@ -50,14 +41,8 @@ public class BaseRecyclerView extends RecyclerView
|
||||||
@Thunk int mDy = 0;
|
@Thunk int mDy = 0;
|
||||||
private float mDeltaThreshold;
|
private float mDeltaThreshold;
|
||||||
|
|
||||||
//
|
|
||||||
// Keeps track of variables required for the second function of this class: fast scroller.
|
|
||||||
//
|
|
||||||
|
|
||||||
private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current scroll state of the recycler view. We use this in updateVerticalScrollbarBounds()
|
* The current scroll state of the recycler view. We use this in onUpdateScrollbar()
|
||||||
* and scrollToPositionAtProgress() to determine the scroll position of the recycler view so
|
* and scrollToPositionAtProgress() to determine the scroll position of the recycler view so
|
||||||
* that we can calculate what the scroll bar looks like, and where to jump to from the fast
|
* that we can calculate what the scroll bar looks like, and where to jump to from the fast
|
||||||
* scroller.
|
* scroller.
|
||||||
|
@ -70,27 +55,12 @@ public class BaseRecyclerView extends RecyclerView
|
||||||
// The height of a given row (they are currently all the same height)
|
// The height of a given row (they are currently all the same height)
|
||||||
public int rowHeight;
|
public int rowHeight;
|
||||||
}
|
}
|
||||||
// Should be maintained inside overriden method #updateVerticalScrollbarBounds
|
|
||||||
public ScrollPositionState scrollPosState = new ScrollPositionState();
|
|
||||||
public Rect verticalScrollbarBounds = new Rect();
|
|
||||||
|
|
||||||
private boolean mDraggingFastScroller;
|
protected BaseRecyclerViewFastScrollBar mScrollbar;
|
||||||
|
|
||||||
private Drawable mScrollbar;
|
|
||||||
private Drawable mFastScrollerBg;
|
|
||||||
private Rect mTmpFastScrollerInvalidateRect = new Rect();
|
|
||||||
private Rect mFastScrollerBounds = new Rect();
|
|
||||||
|
|
||||||
private String mFastScrollSectionName;
|
|
||||||
private Paint mFastScrollTextPaint;
|
|
||||||
private Rect mFastScrollTextBounds = new Rect();
|
|
||||||
private float mFastScrollAlpha;
|
|
||||||
|
|
||||||
private int mDownX;
|
private int mDownX;
|
||||||
private int mDownY;
|
private int mDownY;
|
||||||
private int mLastY;
|
private int mLastY;
|
||||||
private int mScrollbarWidth;
|
|
||||||
private int mScrollbarInset;
|
|
||||||
protected Rect mBackgroundPadding = new Rect();
|
protected Rect mBackgroundPadding = new Rect();
|
||||||
|
|
||||||
public BaseRecyclerView(Context context) {
|
public BaseRecyclerView(Context context) {
|
||||||
|
@ -104,25 +74,10 @@ public class BaseRecyclerView extends RecyclerView
|
||||||
public BaseRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
public BaseRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
super(context, attrs, defStyleAttr);
|
super(context, attrs, defStyleAttr);
|
||||||
mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP;
|
mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP;
|
||||||
|
mScrollbar = new BaseRecyclerViewFastScrollBar(this, getResources());
|
||||||
|
|
||||||
ScrollListener listener = new ScrollListener();
|
ScrollListener listener = new ScrollListener();
|
||||||
setOnScrollListener(listener);
|
setOnScrollListener(listener);
|
||||||
|
|
||||||
Resources res = context.getResources();
|
|
||||||
int fastScrollerSize = res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_popup_size);
|
|
||||||
mScrollbar = res.getDrawable(R.drawable.all_apps_scrollbar_thumb);
|
|
||||||
mFastScrollerBg = res.getDrawable(R.drawable.all_apps_fastscroll_bg);
|
|
||||||
mFastScrollerBg.setBounds(0, 0, fastScrollerSize, fastScrollerSize);
|
|
||||||
mFastScrollTextPaint = new Paint();
|
|
||||||
mFastScrollTextPaint.setColor(Color.WHITE);
|
|
||||||
mFastScrollTextPaint.setAntiAlias(true);
|
|
||||||
mFastScrollTextPaint.setTextSize(res.getDimensionPixelSize(
|
|
||||||
R.dimen.all_apps_fast_scroll_text_size));
|
|
||||||
mScrollbarWidth = res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_bar_width);
|
|
||||||
mScrollbarInset =
|
|
||||||
res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_scrubber_touch_inset);
|
|
||||||
setFastScrollerAlpha(mFastScrollAlpha);
|
|
||||||
setOverScrollMode(View.OVER_SCROLL_NEVER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ScrollListener extends OnScrollListener {
|
private class ScrollListener extends OnScrollListener {
|
||||||
|
@ -133,6 +88,10 @@ public class BaseRecyclerView extends RecyclerView
|
||||||
@Override
|
@Override
|
||||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||||
mDy = dy;
|
mDy = dy;
|
||||||
|
|
||||||
|
// TODO(winsonc): If we want to animate the section heads while scrolling, we can
|
||||||
|
// initiate that here if the recycler view scroll state is not
|
||||||
|
// RecyclerView.SCROLL_STATE_IDLE.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,8 +120,6 @@ public class BaseRecyclerView extends RecyclerView
|
||||||
* it is already showing).
|
* it is already showing).
|
||||||
*/
|
*/
|
||||||
private boolean handleTouchEvent(MotionEvent ev) {
|
private boolean handleTouchEvent(MotionEvent ev) {
|
||||||
ViewConfiguration config = ViewConfiguration.get(getContext());
|
|
||||||
|
|
||||||
int action = ev.getAction();
|
int action = ev.getAction();
|
||||||
int x = (int) ev.getX();
|
int x = (int) ev.getX();
|
||||||
int y = (int) ev.getY();
|
int y = (int) ev.getY();
|
||||||
|
@ -174,41 +131,19 @@ public class BaseRecyclerView extends RecyclerView
|
||||||
if (shouldStopScroll(ev)) {
|
if (shouldStopScroll(ev)) {
|
||||||
stopScroll();
|
stopScroll();
|
||||||
}
|
}
|
||||||
|
mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
|
||||||
break;
|
break;
|
||||||
case MotionEvent.ACTION_MOVE:
|
case MotionEvent.ACTION_MOVE:
|
||||||
// Check if we are scrolling
|
|
||||||
if (!mDraggingFastScroller && isPointNearScrollbar(mDownX, mDownY) &&
|
|
||||||
Math.abs(y - mDownY) > config.getScaledTouchSlop()) {
|
|
||||||
getParent().requestDisallowInterceptTouchEvent(true);
|
|
||||||
mDraggingFastScroller = true;
|
|
||||||
animateFastScrollerVisibility(true);
|
|
||||||
}
|
|
||||||
if (mDraggingFastScroller) {
|
|
||||||
mLastY = y;
|
mLastY = y;
|
||||||
|
mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
|
||||||
// Scroll to the right position, and update the section name
|
|
||||||
int top = getPaddingTop() + (mFastScrollerBg.getBounds().height() / 2);
|
|
||||||
int bottom = getHeight() - getPaddingBottom() -
|
|
||||||
(mFastScrollerBg.getBounds().height() / 2);
|
|
||||||
float boundedY = (float) Math.max(top, Math.min(bottom, y));
|
|
||||||
mFastScrollSectionName = scrollToPositionAtProgress((boundedY - top) /
|
|
||||||
(bottom - top));
|
|
||||||
|
|
||||||
// Combine the old and new fast scroller bounds to create the full invalidate
|
|
||||||
// rect
|
|
||||||
mTmpFastScrollerInvalidateRect.set(mFastScrollerBounds);
|
|
||||||
updateFastScrollerBounds();
|
|
||||||
mTmpFastScrollerInvalidateRect.union(mFastScrollerBounds);
|
|
||||||
invalidateFastScroller(mTmpFastScrollerInvalidateRect);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case MotionEvent.ACTION_UP:
|
case MotionEvent.ACTION_UP:
|
||||||
case MotionEvent.ACTION_CANCEL:
|
case MotionEvent.ACTION_CANCEL:
|
||||||
mDraggingFastScroller = false;
|
onFastScrollCompleted();
|
||||||
animateFastScrollerVisibility(false);
|
mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return mDraggingFastScroller;
|
return mScrollbar.isDragging();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
||||||
|
@ -234,159 +169,117 @@ public class BaseRecyclerView extends RecyclerView
|
||||||
mBackgroundPadding.set(padding);
|
mBackgroundPadding.set(padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Rect getBackgroundPadding() {
|
||||||
|
return mBackgroundPadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the scroll bar width when the user is scrolling.
|
||||||
|
*/
|
||||||
|
public int getMaxScrollbarWidth() {
|
||||||
|
return mScrollbar.getThumbMaxWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the available scroll height:
|
||||||
|
* AvailableScrollHeight = Total height of the all items - last page height
|
||||||
|
*
|
||||||
|
* This assumes that all rows are the same height.
|
||||||
|
*
|
||||||
|
* @param yOffset the offset from the top of the recycler view to start tracking.
|
||||||
|
*/
|
||||||
|
protected int getAvailableScrollHeight(int rowCount, int rowHeight, int yOffset) {
|
||||||
|
int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
|
||||||
|
int scrollHeight = getPaddingTop() + yOffset + rowCount * rowHeight + getPaddingBottom();
|
||||||
|
int availableScrollHeight = scrollHeight - visibleHeight;
|
||||||
|
return availableScrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the available scroll bar height:
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the track color (ignoring alpha), can be overridden by each subclass.
|
||||||
|
*/
|
||||||
|
public int getFastScrollerTrackColor(int defaultTrackColor) {
|
||||||
|
return defaultTrackColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the inactive thumb color, can be overridden by each subclass.
|
||||||
|
*/
|
||||||
|
public int getFastScrollerThumbInactiveColor(int defaultInactiveThumbColor) {
|
||||||
|
return defaultInactiveThumbColor;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void dispatchDraw(Canvas canvas) {
|
protected void dispatchDraw(Canvas canvas) {
|
||||||
super.dispatchDraw(canvas);
|
super.dispatchDraw(canvas);
|
||||||
drawVerticalScrubber(canvas);
|
onUpdateScrollbar();
|
||||||
drawFastScrollerPopup(canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the vertical scrollbar.
|
|
||||||
*/
|
|
||||||
private void drawVerticalScrubber(Canvas canvas) {
|
|
||||||
updateVerticalScrollbarBounds();
|
|
||||||
|
|
||||||
// Draw the scroll bar
|
|
||||||
int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
|
|
||||||
canvas.translate(verticalScrollbarBounds.left, verticalScrollbarBounds.top);
|
|
||||||
mScrollbar.setBounds(0, 0, mScrollbarWidth, verticalScrollbarBounds.height());
|
|
||||||
mScrollbar.draw(canvas);
|
mScrollbar.draw(canvas);
|
||||||
canvas.restoreToCount(restoreCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws the fast scroller popup.
|
* Updates the scrollbar thumb offset to match the visible scroll of the recycler view. It does
|
||||||
|
* this by mapping the available scroll area of the recycler view to the available space for the
|
||||||
|
* scroll bar.
|
||||||
|
*
|
||||||
|
* @param scrollPosState the current scroll position
|
||||||
|
* @param rowCount the number of rows, used to calculate the total scroll height (assumes that
|
||||||
|
* all rows are the same height)
|
||||||
|
* @param yOffset the offset to start tracking in the recycler view (only used for all apps)
|
||||||
*/
|
*/
|
||||||
private void drawFastScrollerPopup(Canvas canvas) {
|
protected void synchronizeScrollBarThumbOffsetToViewScroll(ScrollPositionState scrollPosState,
|
||||||
if (mFastScrollAlpha > 0f && mFastScrollSectionName != null && !mFastScrollSectionName.isEmpty()) {
|
int rowCount, int yOffset) {
|
||||||
// Draw the fast scroller popup
|
int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight,
|
||||||
int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
|
yOffset);
|
||||||
canvas.translate(mFastScrollerBounds.left, mFastScrollerBounds.top);
|
int availableScrollBarHeight = getAvailableScrollBarHeight();
|
||||||
mFastScrollerBg.setAlpha((int) (mFastScrollAlpha * 255));
|
|
||||||
mFastScrollerBg.draw(canvas);
|
// Only show the scrollbar if there is height to be scrolled
|
||||||
mFastScrollTextPaint.setAlpha((int) (mFastScrollAlpha * 255));
|
if (availableScrollHeight <= 0) {
|
||||||
mFastScrollTextPaint.getTextBounds(mFastScrollSectionName, 0,
|
mScrollbar.setScrollbarThumbOffset(-1, -1);
|
||||||
mFastScrollSectionName.length(), mFastScrollTextBounds);
|
return;
|
||||||
float textWidth = mFastScrollTextPaint.measureText(mFastScrollSectionName);
|
|
||||||
canvas.drawText(mFastScrollSectionName,
|
|
||||||
(mFastScrollerBounds.width() - textWidth) / 2,
|
|
||||||
mFastScrollerBounds.height() -
|
|
||||||
(mFastScrollerBounds.height() - mFastScrollTextBounds.height()) / 2,
|
|
||||||
mFastScrollTextPaint);
|
|
||||||
canvas.restoreToCount(restoreCount);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Calculate the current scroll position, the scrollY of the recycler view accounts for the
|
||||||
* Returns the scroll bar width.
|
// view padding, while the scrollBarY is drawn right up to the background padding (ignoring
|
||||||
*/
|
// padding)
|
||||||
public int getScrollbarWidth() {
|
int scrollY = getPaddingTop() + yOffset +
|
||||||
return mScrollbarWidth;
|
(scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset;
|
||||||
}
|
int scrollBarY = mBackgroundPadding.top +
|
||||||
|
(int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
|
||||||
|
|
||||||
/**
|
// Calculate the position and size of the scroll bar
|
||||||
* Sets the fast scroller alpha.
|
int scrollBarX;
|
||||||
*/
|
if (Utilities.isRtl(getResources())) {
|
||||||
public void setFastScrollerAlpha(float alpha) {
|
scrollBarX = mBackgroundPadding.left;
|
||||||
mFastScrollAlpha = alpha;
|
} else {
|
||||||
invalidateFastScroller(mFastScrollerBounds);
|
scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getWidth();
|
||||||
}
|
}
|
||||||
|
mScrollbar.setScrollbarThumbOffset(scrollBarX, scrollBarY);
|
||||||
/**
|
|
||||||
* Returns the fast scroller alpha.
|
|
||||||
*/
|
|
||||||
public float getFastScrollerAlpha() {
|
|
||||||
return mFastScrollAlpha;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps the touch (from 0..1) to the adapter position that should be visible.
|
* Maps the touch (from 0..1) to the adapter position that should be visible.
|
||||||
* <p>Override in each subclass of this base class.
|
* <p>Override in each subclass of this base class.
|
||||||
*/
|
*/
|
||||||
public String scrollToPositionAtProgress(float touchFraction) {
|
public abstract String scrollToPositionAtProgress(float touchFraction);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the bounds for the scrollbar.
|
* Updates the bounds for the scrollbar.
|
||||||
* <p>Override in each subclass of this base class.
|
* <p>Override in each subclass of this base class.
|
||||||
*/
|
*/
|
||||||
public void updateVerticalScrollbarBounds() {};
|
public abstract void onUpdateScrollbar();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Animates the visibility of the fast scroller popup.
|
* <p>Override in each subclass of this base class.
|
||||||
*/
|
*/
|
||||||
private void animateFastScrollerVisibility(final boolean visible) {
|
public void onFastScrollCompleted() {}
|
||||||
ObjectAnimator anim = ObjectAnimator.ofFloat(this, "fastScrollerAlpha", visible ? 1f : 0f);
|
|
||||||
anim.setDuration(visible ? 200 : 150);
|
|
||||||
anim.addListener(new AnimatorListenerAdapter() {
|
|
||||||
@Override
|
|
||||||
public void onAnimationStart(Animator animation) {
|
|
||||||
if (visible) {
|
|
||||||
onFastScrollingStart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animation) {
|
|
||||||
if (!visible) {
|
|
||||||
onFastScrollingEnd();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
anim.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To be overridden by subclasses.
|
|
||||||
*/
|
|
||||||
protected void onFastScrollingStart() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To be overridden by subclasses.
|
|
||||||
*/
|
|
||||||
protected void onFastScrollingEnd() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invalidates the fast scroller popup.
|
|
||||||
*/
|
|
||||||
protected void invalidateFastScroller(Rect bounds) {
|
|
||||||
invalidate(bounds.left, bounds.top, bounds.right, bounds.bottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether a given point is near the scrollbar.
|
|
||||||
*/
|
|
||||||
private boolean isPointNearScrollbar(int x, int y) {
|
|
||||||
// Check if we are scrolling
|
|
||||||
updateVerticalScrollbarBounds();
|
|
||||||
verticalScrollbarBounds.inset(mScrollbarInset, mScrollbarInset);
|
|
||||||
return verticalScrollbarBounds.contains(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the bounds for the fast scroller.
|
|
||||||
*/
|
|
||||||
private void updateFastScrollerBounds() {
|
|
||||||
if (mFastScrollAlpha > 0f && !mFastScrollSectionName.isEmpty()) {
|
|
||||||
int x;
|
|
||||||
int y;
|
|
||||||
|
|
||||||
// Calculate the position for the fast scroller popup
|
|
||||||
Rect bgBounds = mFastScrollerBg.getBounds();
|
|
||||||
if (Utilities.isRtl(getResources())) {
|
|
||||||
x = mBackgroundPadding.left + (2 * getScrollbarWidth());
|
|
||||||
} else {
|
|
||||||
x = getWidth() - mBackgroundPadding.right - (2 * getScrollbarWidth()) -
|
|
||||||
bgBounds.width();
|
|
||||||
}
|
|
||||||
y = mLastY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgBounds.height());
|
|
||||||
y = Math.max(getPaddingTop(), Math.min(y, getHeight() - getPaddingBottom() -
|
|
||||||
bgBounds.height()));
|
|
||||||
mFastScrollerBounds.set(x, y, x + bgBounds.width(), y + bgBounds.height());
|
|
||||||
} else {
|
|
||||||
mFastScrollerBounds.setEmpty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -0,0 +1,232 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.android.launcher3;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorSet;
|
||||||
|
import android.animation.ArgbEvaluator;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The track and scrollbar that shows when you scroll the list.
|
||||||
|
*/
|
||||||
|
public class BaseRecyclerViewFastScrollBar {
|
||||||
|
|
||||||
|
public interface FastScrollFocusableView {
|
||||||
|
void setFastScrollFocused(boolean focused, boolean animated);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final static int MAX_TRACK_ALPHA = 30;
|
||||||
|
private final static int SCROLL_BAR_VIS_DURATION = 150;
|
||||||
|
|
||||||
|
private BaseRecyclerView mRv;
|
||||||
|
private BaseRecyclerViewFastScrollPopup mPopup;
|
||||||
|
|
||||||
|
private AnimatorSet mScrollbarAnimator;
|
||||||
|
|
||||||
|
private int mThumbInactiveColor;
|
||||||
|
private int mThumbActiveColor;
|
||||||
|
private Point mThumbOffset = new Point(-1, -1);
|
||||||
|
private Paint mThumbPaint;
|
||||||
|
private Paint mTrackPaint;
|
||||||
|
private int mThumbMinWidth;
|
||||||
|
private int mThumbMaxWidth;
|
||||||
|
private int mThumbWidth;
|
||||||
|
private int mThumbHeight;
|
||||||
|
// The inset is the buffer around which a point will still register as a click on the scrollbar
|
||||||
|
private int mTouchInset;
|
||||||
|
private boolean mIsDragging;
|
||||||
|
|
||||||
|
// This is the offset from the top of the scrollbar when the user first starts touching. To
|
||||||
|
// prevent jumping, this offset is applied as the user scrolls.
|
||||||
|
private int mTouchOffset;
|
||||||
|
|
||||||
|
private Rect mInvalidateRect = new Rect();
|
||||||
|
private Rect mTmpRect = new Rect();
|
||||||
|
|
||||||
|
public BaseRecyclerViewFastScrollBar(BaseRecyclerView rv, Resources res) {
|
||||||
|
mRv = rv;
|
||||||
|
mPopup = new BaseRecyclerViewFastScrollPopup(rv, res);
|
||||||
|
mTrackPaint = new Paint();
|
||||||
|
mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK));
|
||||||
|
mTrackPaint.setAlpha(0);
|
||||||
|
mThumbInactiveColor = rv.getFastScrollerThumbInactiveColor(
|
||||||
|
res.getColor(R.color.container_fastscroll_thumb_inactive_color));
|
||||||
|
mThumbActiveColor = res.getColor(R.color.container_fastscroll_thumb_active_color);
|
||||||
|
mThumbPaint = new Paint();
|
||||||
|
mThumbPaint.setColor(mThumbInactiveColor);
|
||||||
|
mThumbWidth = mThumbMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
|
||||||
|
mThumbMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
|
||||||
|
mThumbHeight = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_height);
|
||||||
|
mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScrollbarThumbOffset(int x, int y) {
|
||||||
|
if (mThumbOffset.x == x && mThumbOffset.y == y) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
|
||||||
|
mThumbOffset.set(x, y);
|
||||||
|
mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
|
||||||
|
mRv.getHeight()));
|
||||||
|
mRv.invalidate(mInvalidateRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setter/getter for the search bar width for animations
|
||||||
|
public void setWidth(int width) {
|
||||||
|
mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
|
||||||
|
mThumbWidth = width;
|
||||||
|
mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
|
||||||
|
mRv.getHeight()));
|
||||||
|
mRv.invalidate(mInvalidateRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return mThumbWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setter/getter for the track background alpha for animations
|
||||||
|
public void setTrackAlpha(int alpha) {
|
||||||
|
mTrackPaint.setAlpha(alpha);
|
||||||
|
mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
|
||||||
|
mRv.invalidate(mInvalidateRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTrackAlpha() {
|
||||||
|
return mTrackPaint.getAlpha();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getThumbHeight() {
|
||||||
|
return mThumbHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getThumbMaxWidth() {
|
||||||
|
return mThumbMaxWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDragging() {
|
||||||
|
return mIsDragging;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the touch event and determines whether to show the fast scroller (or updates it if
|
||||||
|
* it is already showing).
|
||||||
|
*/
|
||||||
|
public void handleTouchEvent(MotionEvent ev, int downX, int downY, int lastY) {
|
||||||
|
ViewConfiguration config = ViewConfiguration.get(mRv.getContext());
|
||||||
|
|
||||||
|
int action = ev.getAction();
|
||||||
|
int y = (int) ev.getY();
|
||||||
|
switch (action) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
if (isNearPoint(downX, downY)) {
|
||||||
|
mTouchOffset = downY - mThumbOffset.y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
// Check if we should start scrolling
|
||||||
|
if (!mIsDragging && isNearPoint(downX, downY) &&
|
||||||
|
Math.abs(y - downY) > config.getScaledTouchSlop()) {
|
||||||
|
mRv.getParent().requestDisallowInterceptTouchEvent(true);
|
||||||
|
mIsDragging = true;
|
||||||
|
mTouchOffset += (lastY - downY);
|
||||||
|
mPopup.animateVisibility(true);
|
||||||
|
animateScrollbar(true);
|
||||||
|
}
|
||||||
|
if (mIsDragging) {
|
||||||
|
// Update the fastscroller section name at this touch position
|
||||||
|
int top = mRv.getBackgroundPadding().top;
|
||||||
|
int bottom = mRv.getHeight() - mRv.getBackgroundPadding().bottom - mThumbHeight;
|
||||||
|
float boundedY = (float) Math.max(top, Math.min(bottom, y - mTouchOffset));
|
||||||
|
String sectionName = mRv.scrollToPositionAtProgress((boundedY - top) /
|
||||||
|
(bottom - top));
|
||||||
|
mPopup.setSectionName(sectionName);
|
||||||
|
mPopup.animateVisibility(!sectionName.isEmpty());
|
||||||
|
mRv.invalidate(mPopup.updateFastScrollerBounds(mRv, lastY));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
mIsDragging = false;
|
||||||
|
mTouchOffset = 0;
|
||||||
|
mPopup.animateVisibility(false);
|
||||||
|
animateScrollbar(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void draw(Canvas canvas) {
|
||||||
|
if (mThumbOffset.x < 0 || mThumbOffset.y < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the scroll bar track and thumb
|
||||||
|
if (mTrackPaint.getAlpha() > 0) {
|
||||||
|
canvas.drawRect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight(), mTrackPaint);
|
||||||
|
}
|
||||||
|
canvas.drawRect(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
|
||||||
|
mThumbOffset.y + mThumbHeight, mThumbPaint);
|
||||||
|
|
||||||
|
// Draw the popup
|
||||||
|
mPopup.draw(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animates the width and color of the scrollbar.
|
||||||
|
*/
|
||||||
|
private void animateScrollbar(boolean isScrolling) {
|
||||||
|
if (mScrollbarAnimator != null) {
|
||||||
|
mScrollbarAnimator.cancel();
|
||||||
|
}
|
||||||
|
ObjectAnimator trackAlphaAnim = ObjectAnimator.ofInt(this, "trackAlpha",
|
||||||
|
isScrolling ? MAX_TRACK_ALPHA : 0);
|
||||||
|
ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "width",
|
||||||
|
isScrolling ? mThumbMaxWidth : mThumbMinWidth);
|
||||||
|
ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
|
||||||
|
mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
|
||||||
|
colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationUpdate(ValueAnimator animator) {
|
||||||
|
mThumbPaint.setColor((Integer) animator.getAnimatedValue());
|
||||||
|
mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
|
||||||
|
mThumbOffset.y + mThumbHeight);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mScrollbarAnimator = new AnimatorSet();
|
||||||
|
mScrollbarAnimator.playTogether(trackAlphaAnim, thumbWidthAnim, colorAnimation);
|
||||||
|
mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
|
||||||
|
mScrollbarAnimator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the specified points are near the scroll bar bounds.
|
||||||
|
*/
|
||||||
|
private boolean isNearPoint(int x, int y) {
|
||||||
|
mTmpRect.set(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
|
||||||
|
mThumbOffset.y + mThumbHeight);
|
||||||
|
mTmpRect.inset(mTouchInset, mTouchInset);
|
||||||
|
return mTmpRect.contains(x, y);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.android.launcher3;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The fast scroller popup that shows the section name the list will jump to.
|
||||||
|
*/
|
||||||
|
public class BaseRecyclerViewFastScrollPopup {
|
||||||
|
|
||||||
|
private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
|
||||||
|
|
||||||
|
private Resources mRes;
|
||||||
|
private BaseRecyclerView mRv;
|
||||||
|
|
||||||
|
private Drawable mBg;
|
||||||
|
// The absolute bounds of the fast scroller bg
|
||||||
|
private Rect mBgBounds = new Rect();
|
||||||
|
private int mBgOriginalSize;
|
||||||
|
private Rect mInvalidateRect = new Rect();
|
||||||
|
private Rect mTmpRect = new Rect();
|
||||||
|
|
||||||
|
private String mSectionName;
|
||||||
|
private Paint mTextPaint;
|
||||||
|
private Rect mTextBounds = new Rect();
|
||||||
|
private float mAlpha;
|
||||||
|
|
||||||
|
private Animator mAlphaAnimator;
|
||||||
|
private boolean mVisible;
|
||||||
|
|
||||||
|
public BaseRecyclerViewFastScrollPopup(BaseRecyclerView rv, Resources res) {
|
||||||
|
mRes = res;
|
||||||
|
mRv = rv;
|
||||||
|
mBgOriginalSize = res.getDimensionPixelSize(R.dimen.container_fastscroll_popup_size);
|
||||||
|
mBg = res.getDrawable(R.drawable.container_fastscroll_popup_bg);
|
||||||
|
mBg.setBounds(0, 0, mBgOriginalSize, mBgOriginalSize);
|
||||||
|
mTextPaint = new Paint();
|
||||||
|
mTextPaint.setColor(Color.WHITE);
|
||||||
|
mTextPaint.setAntiAlias(true);
|
||||||
|
mTextPaint.setTextSize(res.getDimensionPixelSize(R.dimen.container_fastscroll_popup_text_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the section name.
|
||||||
|
*/
|
||||||
|
public void setSectionName(String sectionName) {
|
||||||
|
if (!sectionName.equals(mSectionName)) {
|
||||||
|
mSectionName = sectionName;
|
||||||
|
mTextPaint.getTextBounds(sectionName, 0, sectionName.length(), mTextBounds);
|
||||||
|
// Update the width to use measureText since that is more accurate
|
||||||
|
mTextBounds.right = (int) (mTextBounds.left + mTextPaint.measureText(sectionName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the bounds for the fast scroller.
|
||||||
|
* @return the invalidation rect for this update.
|
||||||
|
*/
|
||||||
|
public Rect updateFastScrollerBounds(BaseRecyclerView rv, int lastTouchY) {
|
||||||
|
mInvalidateRect.set(mBgBounds);
|
||||||
|
|
||||||
|
if (isVisible()) {
|
||||||
|
// Calculate the dimensions and position of the fast scroller popup
|
||||||
|
int edgePadding = rv.getMaxScrollbarWidth();
|
||||||
|
int bgPadding = (mBgOriginalSize - mTextBounds.height()) / 2;
|
||||||
|
int bgHeight = mBgOriginalSize;
|
||||||
|
int bgWidth = Math.max(mBgOriginalSize, mTextBounds.width() + (2 * bgPadding));
|
||||||
|
if (Utilities.isRtl(mRes)) {
|
||||||
|
mBgBounds.left = rv.getBackgroundPadding().left + (2 * rv.getMaxScrollbarWidth());
|
||||||
|
mBgBounds.right = mBgBounds.left + bgWidth;
|
||||||
|
} else {
|
||||||
|
mBgBounds.right = rv.getWidth() - rv.getBackgroundPadding().right -
|
||||||
|
(2 * rv.getMaxScrollbarWidth());
|
||||||
|
mBgBounds.left = mBgBounds.right - bgWidth;
|
||||||
|
}
|
||||||
|
mBgBounds.top = lastTouchY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgHeight);
|
||||||
|
mBgBounds.top = Math.max(edgePadding,
|
||||||
|
Math.min(mBgBounds.top, rv.getHeight() - edgePadding - bgHeight));
|
||||||
|
mBgBounds.bottom = mBgBounds.top + bgHeight;
|
||||||
|
} else {
|
||||||
|
mBgBounds.setEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine the old and new fast scroller bounds to create the full invalidate rect
|
||||||
|
mInvalidateRect.union(mBgBounds);
|
||||||
|
return mInvalidateRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animates the visibility of the fast scroller popup.
|
||||||
|
*/
|
||||||
|
public void animateVisibility(boolean visible) {
|
||||||
|
if (mVisible != visible) {
|
||||||
|
mVisible = visible;
|
||||||
|
if (mAlphaAnimator != null) {
|
||||||
|
mAlphaAnimator.cancel();
|
||||||
|
}
|
||||||
|
mAlphaAnimator = ObjectAnimator.ofFloat(this, "alpha", visible ? 1f : 0f);
|
||||||
|
mAlphaAnimator.setDuration(visible ? 200 : 150);
|
||||||
|
mAlphaAnimator.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setter/getter for the popup alpha for animations
|
||||||
|
public void setAlpha(float alpha) {
|
||||||
|
mAlpha = alpha;
|
||||||
|
mRv.invalidate(mBgBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getAlpha() {
|
||||||
|
return mAlpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return mBgOriginalSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void draw(Canvas c) {
|
||||||
|
if (isVisible()) {
|
||||||
|
// Draw the fast scroller popup
|
||||||
|
int restoreCount = c.save(Canvas.MATRIX_SAVE_FLAG);
|
||||||
|
c.translate(mBgBounds.left, mBgBounds.top);
|
||||||
|
mTmpRect.set(mBgBounds);
|
||||||
|
mTmpRect.offsetTo(0, 0);
|
||||||
|
mBg.setBounds(mTmpRect);
|
||||||
|
mBg.setAlpha((int) (mAlpha * 255));
|
||||||
|
mBg.draw(c);
|
||||||
|
mTextPaint.setAlpha((int) (mAlpha * 255));
|
||||||
|
c.drawText(mSectionName, (mBgBounds.width() - mTextBounds.width()) / 2,
|
||||||
|
mBgBounds.height() - (mBgBounds.height() - mTextBounds.height()) / 2,
|
||||||
|
mTextPaint);
|
||||||
|
c.restoreToCount(restoreCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVisible() {
|
||||||
|
return (mAlpha > 0f) && (mSectionName != null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package com.android.launcher3;
|
package com.android.launcher3;
|
||||||
|
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
|
@ -24,6 +25,7 @@ import android.content.res.Resources.Theme;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
import android.graphics.Region;
|
import android.graphics.Region;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
@ -34,6 +36,8 @@ import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.ViewConfiguration;
|
import android.view.ViewConfiguration;
|
||||||
import android.view.ViewParent;
|
import android.view.ViewParent;
|
||||||
|
import android.view.animation.AccelerateInterpolator;
|
||||||
|
import android.view.animation.DecelerateInterpolator;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import com.android.launcher3.IconCache.IconLoadRequest;
|
import com.android.launcher3.IconCache.IconLoadRequest;
|
||||||
import com.android.launcher3.model.PackageItemInfo;
|
import com.android.launcher3.model.PackageItemInfo;
|
||||||
|
@ -43,7 +47,8 @@ import com.android.launcher3.model.PackageItemInfo;
|
||||||
* because we want to make the bubble taller than the text and TextView's clip is
|
* because we want to make the bubble taller than the text and TextView's clip is
|
||||||
* too aggressive.
|
* too aggressive.
|
||||||
*/
|
*/
|
||||||
public class BubbleTextView extends TextView {
|
public class BubbleTextView extends TextView
|
||||||
|
implements BaseRecyclerViewFastScrollBar.FastScrollFocusableView {
|
||||||
|
|
||||||
private static SparseArray<Theme> sPreloaderThemes = new SparseArray<Theme>(2);
|
private static SparseArray<Theme> sPreloaderThemes = new SparseArray<Theme>(2);
|
||||||
|
|
||||||
|
@ -56,6 +61,13 @@ public class BubbleTextView extends TextView {
|
||||||
private static final int DISPLAY_WORKSPACE = 0;
|
private static final int DISPLAY_WORKSPACE = 0;
|
||||||
private static final int DISPLAY_ALL_APPS = 1;
|
private static final int DISPLAY_ALL_APPS = 1;
|
||||||
|
|
||||||
|
private static final float FAST_SCROLL_FOCUS_MAX_SCALE = 1.15f;
|
||||||
|
private static final int FAST_SCROLL_FOCUS_MODE_NONE = 0;
|
||||||
|
private static final int FAST_SCROLL_FOCUS_MODE_SCALE_ICON = 1;
|
||||||
|
private static final int FAST_SCROLL_FOCUS_MODE_DRAW_CIRCLE_BG = 2;
|
||||||
|
private static final int FAST_SCROLL_FOCUS_FADE_IN_DURATION = 175;
|
||||||
|
private static final int FAST_SCROLL_FOCUS_FADE_OUT_DURATION = 125;
|
||||||
|
|
||||||
private final Launcher mLauncher;
|
private final Launcher mLauncher;
|
||||||
private Drawable mIcon;
|
private Drawable mIcon;
|
||||||
private final Drawable mBackground;
|
private final Drawable mBackground;
|
||||||
|
@ -79,6 +91,12 @@ public class BubbleTextView extends TextView {
|
||||||
private boolean mIgnorePressedStateChange;
|
private boolean mIgnorePressedStateChange;
|
||||||
private boolean mDisableRelayout = false;
|
private boolean mDisableRelayout = false;
|
||||||
|
|
||||||
|
private ObjectAnimator mFastScrollFocusAnimator;
|
||||||
|
private Paint mFastScrollFocusBgPaint;
|
||||||
|
private float mFastScrollFocusFraction;
|
||||||
|
private boolean mFastScrollFocused;
|
||||||
|
private final int mFastScrollMode = FAST_SCROLL_FOCUS_MODE_SCALE_ICON;
|
||||||
|
|
||||||
private IconLoadRequest mIconLoadRequest;
|
private IconLoadRequest mIconLoadRequest;
|
||||||
|
|
||||||
public BubbleTextView(Context context) {
|
public BubbleTextView(Context context) {
|
||||||
|
@ -131,6 +149,13 @@ public class BubbleTextView extends TextView {
|
||||||
setShadowLayer(SHADOW_LARGE_RADIUS, 0.0f, SHADOW_Y_OFFSET, SHADOW_LARGE_COLOUR);
|
setShadowLayer(SHADOW_LARGE_RADIUS, 0.0f, SHADOW_Y_OFFSET, SHADOW_LARGE_COLOUR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_DRAW_CIRCLE_BG) {
|
||||||
|
mFastScrollFocusBgPaint = new Paint();
|
||||||
|
mFastScrollFocusBgPaint.setAntiAlias(true);
|
||||||
|
mFastScrollFocusBgPaint.setColor(
|
||||||
|
getResources().getColor(R.color.container_fastscroll_thumb_active_color));
|
||||||
|
}
|
||||||
|
|
||||||
setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate());
|
setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,7 +360,18 @@ public class BubbleTextView extends TextView {
|
||||||
@Override
|
@Override
|
||||||
public void draw(Canvas canvas) {
|
public void draw(Canvas canvas) {
|
||||||
if (!mCustomShadowsEnabled) {
|
if (!mCustomShadowsEnabled) {
|
||||||
|
// Draw the fast scroll focus bg if we have one
|
||||||
|
if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_DRAW_CIRCLE_BG &&
|
||||||
|
mFastScrollFocusFraction > 0f) {
|
||||||
|
DeviceProfile grid = mLauncher.getDeviceProfile();
|
||||||
|
int iconCenterX = getScrollX() + (getWidth() / 2);
|
||||||
|
int iconCenterY = getScrollY() + getPaddingTop() + (grid.iconSizePx / 2);
|
||||||
|
canvas.drawCircle(iconCenterX, iconCenterY,
|
||||||
|
mFastScrollFocusFraction * (getWidth() / 2), mFastScrollFocusBgPaint);
|
||||||
|
}
|
||||||
|
|
||||||
super.draw(canvas);
|
super.draw(canvas);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -538,6 +574,51 @@ public class BubbleTextView extends TextView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setters & getters for the animation
|
||||||
|
public void setFastScrollFocus(float fraction) {
|
||||||
|
mFastScrollFocusFraction = fraction;
|
||||||
|
if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_SCALE_ICON) {
|
||||||
|
setScaleX(1f + fraction * (FAST_SCROLL_FOCUS_MAX_SCALE - 1f));
|
||||||
|
setScaleY(1f + fraction * (FAST_SCROLL_FOCUS_MAX_SCALE - 1f));
|
||||||
|
} else {
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getFastScrollFocus() {
|
||||||
|
return mFastScrollFocusFraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFastScrollFocused(final boolean focused, boolean animated) {
|
||||||
|
if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_NONE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mFastScrollFocused != focused) {
|
||||||
|
mFastScrollFocused = focused;
|
||||||
|
|
||||||
|
if (animated) {
|
||||||
|
// Clean up the previous focus animator
|
||||||
|
if (mFastScrollFocusAnimator != null) {
|
||||||
|
mFastScrollFocusAnimator.cancel();
|
||||||
|
}
|
||||||
|
mFastScrollFocusAnimator = ObjectAnimator.ofFloat(this, "fastScrollFocus",
|
||||||
|
focused ? 1f : 0f);
|
||||||
|
if (focused) {
|
||||||
|
mFastScrollFocusAnimator.setInterpolator(new DecelerateInterpolator());
|
||||||
|
} else {
|
||||||
|
mFastScrollFocusAnimator.setInterpolator(new AccelerateInterpolator());
|
||||||
|
}
|
||||||
|
mFastScrollFocusAnimator.setDuration(focused ?
|
||||||
|
FAST_SCROLL_FOCUS_FADE_IN_DURATION : FAST_SCROLL_FOCUS_FADE_OUT_DURATION);
|
||||||
|
mFastScrollFocusAnimator.start();
|
||||||
|
} else {
|
||||||
|
mFastScrollFocusFraction = focused ? 1f : 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface to be implemented by the grand parent to allow click shadow effect.
|
* Interface to be implemented by the grand parent to allow click shadow effect.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1125,27 +1125,6 @@ public class Launcher extends Activity
|
||||||
public void forceExitFullImmersion();
|
public void forceExitFullImmersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface LauncherAppsCallbacks {
|
|
||||||
/**
|
|
||||||
* Updates launcher to the available space that AllApps can take so as not to overlap with
|
|
||||||
* any other views.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void onAllAppsBoundsChanged(Rect bounds);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to dismiss all apps if it is showing.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void dismissAllApps();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the search manager to be used for app search.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void setSearchManager(Object manager);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface LauncherSearchCallbacks {
|
public interface LauncherSearchCallbacks {
|
||||||
/**
|
/**
|
||||||
* Called when the search overlay is shown.
|
* Called when the search overlay is shown.
|
||||||
|
|
|
@ -143,13 +143,6 @@ public class LauncherAppState {
|
||||||
return mModel;
|
return mModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO(winsonc, hyunyoungs): We need to respect this
|
|
||||||
*/
|
|
||||||
boolean shouldShowAppOrWidgetProvider(ComponentName componentName) {
|
|
||||||
return mAppFilter == null || mAppFilter.shouldShowApp(componentName);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void setLauncherProvider(LauncherProvider provider) {
|
static void setLauncherProvider(LauncherProvider provider) {
|
||||||
sLauncherProvider = new WeakReference<LauncherProvider>(provider);
|
sLauncherProvider = new WeakReference<LauncherProvider>(provider);
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,10 +70,12 @@ public interface LauncherCallbacks {
|
||||||
/*
|
/*
|
||||||
* Extension points for replacing the search experience
|
* Extension points for replacing the search experience
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public boolean forceDisableVoiceButtonProxy();
|
public boolean forceDisableVoiceButtonProxy();
|
||||||
public boolean providesSearch();
|
public boolean providesSearch();
|
||||||
public boolean startSearch(String initialQuery, boolean selectInitialQuery,
|
public boolean startSearch(String initialQuery, boolean selectInitialQuery,
|
||||||
Bundle appSearchData, Rect sourceBounds);
|
Bundle appSearchData, Rect sourceBounds);
|
||||||
|
@Deprecated
|
||||||
public void startVoice();
|
public void startVoice();
|
||||||
public boolean hasCustomContentToLeft();
|
public boolean hasCustomContentToLeft();
|
||||||
public void populateCustomContentContainer();
|
public void populateCustomContentContainer();
|
||||||
|
|
|
@ -637,6 +637,25 @@ public final class Utilities {
|
||||||
return -fm.top + fm.bottom;
|
return -fm.top + fm.bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience println with multiple args.
|
||||||
|
*/
|
||||||
|
public static void println(String key, Object... args) {
|
||||||
|
StringBuilder b = new StringBuilder();
|
||||||
|
b.append(key);
|
||||||
|
b.append(": ");
|
||||||
|
boolean isFirstArgument = true;
|
||||||
|
for (Object arg : args) {
|
||||||
|
if (isFirstArgument) {
|
||||||
|
isFirstArgument = false;
|
||||||
|
} else {
|
||||||
|
b.append(", ");
|
||||||
|
}
|
||||||
|
b.append(arg);
|
||||||
|
}
|
||||||
|
System.out.println(b.toString());
|
||||||
|
}
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
|
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
|
||||||
public static boolean isRtl(Resources res) {
|
public static boolean isRtl(Resources res) {
|
||||||
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) &&
|
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) &&
|
||||||
|
|
|
@ -17,7 +17,6 @@ package com.android.launcher3.allapps;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
|
@ -155,6 +154,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||||
private int mSectionNamesMargin;
|
private int mSectionNamesMargin;
|
||||||
private int mNumAppsPerRow;
|
private int mNumAppsPerRow;
|
||||||
private int mNumPredictedAppsPerRow;
|
private int mNumPredictedAppsPerRow;
|
||||||
|
private int mRecyclerViewTopBottomPadding;
|
||||||
// This coordinate is relative to this container view
|
// This coordinate is relative to this container view
|
||||||
private final Point mBoundsCheckLastTouchDownPos = new Point(-1, -1);
|
private final Point mBoundsCheckLastTouchDownPos = new Point(-1, -1);
|
||||||
// This coordinate is relative to its parent
|
// This coordinate is relative to its parent
|
||||||
|
@ -189,7 +189,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||||
mPredictionBarHeight = (int) (grid.allAppsIconSizePx + grid.iconDrawablePaddingOriginalPx +
|
mPredictionBarHeight = (int) (grid.allAppsIconSizePx + grid.iconDrawablePaddingOriginalPx +
|
||||||
Utilities.calculateTextHeight(grid.allAppsIconTextSizePx) +
|
Utilities.calculateTextHeight(grid.allAppsIconTextSizePx) +
|
||||||
2 * res.getDimensionPixelSize(R.dimen.all_apps_icon_top_bottom_padding) +
|
2 * res.getDimensionPixelSize(R.dimen.all_apps_icon_top_bottom_padding) +
|
||||||
2 * res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_top_bottom_padding));
|
res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_top_padding) +
|
||||||
|
res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_bottom_padding));
|
||||||
mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
|
mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
|
||||||
mApps = new AlphabeticalAppsList(context);
|
mApps = new AlphabeticalAppsList(context);
|
||||||
mApps.setAdapterChangedCallback(this);
|
mApps.setAdapterChangedCallback(this);
|
||||||
|
@ -199,6 +200,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||||
mApps.setAdapter(mAdapter);
|
mApps.setAdapter(mAdapter);
|
||||||
mLayoutManager = mAdapter.getLayoutManager();
|
mLayoutManager = mAdapter.getLayoutManager();
|
||||||
mItemDecoration = mAdapter.getItemDecoration();
|
mItemDecoration = mAdapter.getItemDecoration();
|
||||||
|
mRecyclerViewTopBottomPadding =
|
||||||
|
res.getDimensionPixelSize(R.dimen.all_apps_list_top_bottom_padding);
|
||||||
|
|
||||||
mSearchQueryBuilder = new SpannableStringBuilder();
|
mSearchQueryBuilder = new SpannableStringBuilder();
|
||||||
Selection.setSelection(mSearchQueryBuilder, 0);
|
Selection.setSelection(mSearchQueryBuilder, 0);
|
||||||
|
@ -414,7 +417,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||||
new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f),
|
new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f),
|
||||||
MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE);
|
MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE);
|
||||||
|
|
||||||
mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
|
mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow);
|
||||||
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
|
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
|
||||||
mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm);
|
mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm);
|
||||||
}
|
}
|
||||||
|
@ -431,14 +434,16 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||||
protected void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding) {
|
protected void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding) {
|
||||||
boolean isRtl = Utilities.isRtl(getResources());
|
boolean isRtl = Utilities.isRtl(getResources());
|
||||||
|
|
||||||
// TODO: Use quantum_panel instead of quantum_panel_shape.
|
// TODO: Use quantum_panel instead of quantum_panel_shape
|
||||||
InsetDrawable background = new InsetDrawable(
|
InsetDrawable background = new InsetDrawable(
|
||||||
getResources().getDrawable(R.drawable.quantum_panel_shape), padding.left, 0,
|
getResources().getDrawable(R.drawable.quantum_panel_shape), padding.left, 0,
|
||||||
padding.right, 0);
|
padding.right, 0);
|
||||||
|
Rect bgPadding = new Rect();
|
||||||
|
background.getPadding(bgPadding);
|
||||||
mContainerView.setBackground(background);
|
mContainerView.setBackground(background);
|
||||||
mRevealView.setBackground(background.getConstantState().newDrawable());
|
mRevealView.setBackground(background.getConstantState().newDrawable());
|
||||||
mAppsRecyclerView.updateBackgroundPadding(padding);
|
mAppsRecyclerView.updateBackgroundPadding(bgPadding);
|
||||||
mAdapter.updateBackgroundPadding(padding);
|
mAdapter.updateBackgroundPadding(bgPadding);
|
||||||
|
|
||||||
// Hack: We are going to let the recycler view take the full width, so reset the padding on
|
// Hack: We are going to let the recycler view take the full width, so reset the padding on
|
||||||
// the container to zero after setting the background and apply the top-bottom padding to
|
// the container to zero after setting the background and apply the top-bottom padding to
|
||||||
|
@ -448,13 +453,14 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||||
|
|
||||||
// Pad the recycler view by the background padding plus the start margin (for the section
|
// Pad the recycler view by the background padding plus the start margin (for the section
|
||||||
// names)
|
// names)
|
||||||
int startInset = Math.max(mSectionNamesMargin, mAppsRecyclerView.getScrollbarWidth());
|
int startInset = Math.max(mSectionNamesMargin, mAppsRecyclerView.getMaxScrollbarWidth());
|
||||||
|
int topBottomPadding = mRecyclerViewTopBottomPadding;
|
||||||
if (isRtl) {
|
if (isRtl) {
|
||||||
mAppsRecyclerView.setPadding(padding.left + mAppsRecyclerView.getScrollbarWidth(), 0,
|
mAppsRecyclerView.setPadding(padding.left + mAppsRecyclerView.getMaxScrollbarWidth(),
|
||||||
padding.right + startInset, 0);
|
topBottomPadding, padding.right + startInset, topBottomPadding);
|
||||||
} else {
|
} else {
|
||||||
mAppsRecyclerView.setPadding(padding.left + startInset, 0,
|
mAppsRecyclerView.setPadding(padding.left + startInset, topBottomPadding,
|
||||||
padding.right + mAppsRecyclerView.getScrollbarWidth(), 0);
|
padding.right + mAppsRecyclerView.getMaxScrollbarWidth(), topBottomPadding);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inset the search bar to fit its bounds above the container
|
// Inset the search bar to fit its bounds above the container
|
||||||
|
@ -474,8 +480,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||||
// Update the prediction bar insets as well
|
// Update the prediction bar insets as well
|
||||||
mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar);
|
mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar);
|
||||||
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
|
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
|
||||||
lp.leftMargin = padding.left + mAppsRecyclerView.getScrollbarWidth();
|
lp.leftMargin = padding.left + mAppsRecyclerView.getMaxScrollbarWidth();
|
||||||
lp.rightMargin = padding.right + mAppsRecyclerView.getScrollbarWidth();
|
lp.rightMargin = padding.right + mAppsRecyclerView.getMaxScrollbarWidth();
|
||||||
mPredictionBarView.requestLayout();
|
mPredictionBarView.requestLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -337,7 +337,7 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
|
||||||
mPredictedAppsDividerPaint.setColor(0x1E000000);
|
mPredictedAppsDividerPaint.setColor(0x1E000000);
|
||||||
mPredictedAppsDividerPaint.setAntiAlias(true);
|
mPredictedAppsDividerPaint.setAntiAlias(true);
|
||||||
mPredictionBarBottomPadding =
|
mPredictionBarBottomPadding =
|
||||||
res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_top_bottom_padding);
|
res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_bottom_padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,10 +22,10 @@ import android.support.v7.widget.RecyclerView;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import com.android.launcher3.BaseRecyclerView;
|
import com.android.launcher3.BaseRecyclerView;
|
||||||
|
import com.android.launcher3.BaseRecyclerViewFastScrollBar;
|
||||||
import com.android.launcher3.DeviceProfile;
|
import com.android.launcher3.DeviceProfile;
|
||||||
import com.android.launcher3.Launcher;
|
import com.android.launcher3.Launcher;
|
||||||
import com.android.launcher3.Stats;
|
import com.android.launcher3.Stats;
|
||||||
import com.android.launcher3.Utilities;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -35,13 +35,25 @@ import java.util.List;
|
||||||
public class AllAppsRecyclerView extends BaseRecyclerView
|
public class AllAppsRecyclerView extends BaseRecyclerView
|
||||||
implements Stats.LaunchSourceProvider {
|
implements Stats.LaunchSourceProvider {
|
||||||
|
|
||||||
|
private static final int FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON = 0;
|
||||||
|
private static final int FAST_SCROLL_MODE_FREE_SCROLL = 1;
|
||||||
|
|
||||||
|
private static final int FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW = 0;
|
||||||
|
private static final int FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_SECTIONS = 1;
|
||||||
|
|
||||||
private AlphabeticalAppsList mApps;
|
private AlphabeticalAppsList mApps;
|
||||||
private int mNumAppsPerRow;
|
private int mNumAppsPerRow;
|
||||||
private int mNumPredictedAppsPerRow;
|
|
||||||
private int mPredictionBarHeight;
|
private int mPredictionBarHeight;
|
||||||
private int mLastFastscrollPosition = -1;
|
|
||||||
|
private BaseRecyclerViewFastScrollBar.FastScrollFocusableView mLastFastScrollFocusedView;
|
||||||
|
private int mPrevFastScrollFocusedPosition;
|
||||||
|
private int mFastScrollFrameIndex;
|
||||||
|
private int[] mFastScrollFrames = new int[10];
|
||||||
|
private final int mFastScrollMode = FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON;
|
||||||
|
private final int mScrollBarMode = FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW;
|
||||||
|
|
||||||
private Launcher mLauncher;
|
private Launcher mLauncher;
|
||||||
|
private ScrollPositionState mScrollPosState = new ScrollPositionState();
|
||||||
|
|
||||||
public AllAppsRecyclerView(Context context) {
|
public AllAppsRecyclerView(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
|
@ -59,6 +71,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
||||||
int defStyleRes) {
|
int defStyleRes) {
|
||||||
super(context, attrs, defStyleAttr);
|
super(context, attrs, defStyleAttr);
|
||||||
mLauncher = (Launcher) context;
|
mLauncher = (Launcher) context;
|
||||||
|
setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,9 +84,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
||||||
/**
|
/**
|
||||||
* Sets the number of apps per row in this recycler view.
|
* Sets the number of apps per row in this recycler view.
|
||||||
*/
|
*/
|
||||||
public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) {
|
public void setNumAppsPerRow(int numAppsPerRow) {
|
||||||
mNumAppsPerRow = numAppsPerRow;
|
mNumAppsPerRow = numAppsPerRow;
|
||||||
mNumPredictedAppsPerRow = numPredictedAppsPerRow;
|
|
||||||
|
|
||||||
DeviceProfile grid = mLauncher.getDeviceProfile();
|
DeviceProfile grid = mLauncher.getDeviceProfile();
|
||||||
RecyclerView.RecycledViewPool pool = getRecycledViewPool();
|
RecyclerView.RecycledViewPool pool = getRecycledViewPool();
|
||||||
|
@ -103,11 +115,12 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
||||||
*/
|
*/
|
||||||
public int getScrollPosition() {
|
public int getScrollPosition() {
|
||||||
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
|
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
|
||||||
getCurScrollState(scrollPosState, items);
|
getCurScrollState(mScrollPosState, items);
|
||||||
if (scrollPosState.rowIndex != -1) {
|
if (mScrollPosState.rowIndex != -1) {
|
||||||
int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
|
int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
|
||||||
return getPaddingTop() + (scrollPosState.rowIndex * scrollPosState.rowHeight) +
|
return getPaddingTop() + predictionBarHeight +
|
||||||
predictionBarHeight - scrollPosState.rowTopOffset;
|
(mScrollPosState.rowIndex * mScrollPosState.rowHeight) -
|
||||||
|
mScrollPosState.rowTopOffset;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -132,143 +145,159 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onFastScrollingEnd() {
|
|
||||||
mLastFastscrollPosition = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps the touch (from 0..1) to the adapter position that should be visible.
|
* Maps the touch (from 0..1) to the adapter position that should be visible.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String scrollToPositionAtProgress(float touchFraction) {
|
public String scrollToPositionAtProgress(float touchFraction) {
|
||||||
// Ensure that we have any sections
|
int rowCount = mApps.getNumAppRows();
|
||||||
List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
|
if (rowCount == 0) {
|
||||||
mApps.getFastScrollerSections();
|
|
||||||
if (fastScrollSections.isEmpty()) {
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop the scroller if it is scrolling
|
// Stop the scroller if it is scrolling
|
||||||
LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
|
|
||||||
stopScroll();
|
stopScroll();
|
||||||
|
|
||||||
// If there is a prediction bar, then capture the appropriate area for the prediction bar
|
// Find the fastscroll section that maps to this touch fraction
|
||||||
float predictionBarFraction = 0f;
|
List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
|
||||||
if (!mApps.getPredictedApps().isEmpty()) {
|
mApps.getFastScrollerSections();
|
||||||
predictionBarFraction = (float) mNumPredictedAppsPerRow / mApps.getSize();
|
AlphabeticalAppsList.FastScrollSectionInfo lastInfo = fastScrollSections.get(0);
|
||||||
if (touchFraction <= predictionBarFraction) {
|
if (mScrollBarMode == FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW) {
|
||||||
// Scroll to the top of the view, where the prediction bar is
|
|
||||||
layoutManager.scrollToPositionWithOffset(0, 0);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since the app ranges are from 0..1, we need to map the touch fraction back to 0..1 from
|
|
||||||
// predictionBarFraction..1
|
|
||||||
touchFraction = (touchFraction - predictionBarFraction) *
|
|
||||||
(1f / (1f - predictionBarFraction));
|
|
||||||
AlphabeticalAppsList.FastScrollSectionInfo lastScrollSection = fastScrollSections.get(0);
|
|
||||||
for (int i = 1; i < fastScrollSections.size(); i++) {
|
for (int i = 1; i < fastScrollSections.size(); i++) {
|
||||||
AlphabeticalAppsList.FastScrollSectionInfo scrollSection = fastScrollSections.get(i);
|
AlphabeticalAppsList.FastScrollSectionInfo info = fastScrollSections.get(i);
|
||||||
if (lastScrollSection.appRangeFraction <= touchFraction &&
|
if (info.touchFraction > touchFraction) {
|
||||||
touchFraction < scrollSection.appRangeFraction) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
lastScrollSection = scrollSection;
|
lastInfo = info;
|
||||||
|
}
|
||||||
|
} else if (mScrollBarMode == FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_SECTIONS){
|
||||||
|
lastInfo = fastScrollSections.get((int) (touchFraction * (fastScrollSections.size() - 1)));
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Unexpected scroll bar mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll to the view at the position, anchored at the top of the screen. We call the scroll
|
// Map the touch position back to the scroll of the recycler view
|
||||||
// method on the LayoutManager directly since it is not exposed by RecyclerView.
|
getCurScrollState(mScrollPosState, mApps.getAdapterItems());
|
||||||
if (mLastFastscrollPosition != lastScrollSection.appItem.position) {
|
int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
|
||||||
mLastFastscrollPosition = lastScrollSection.appItem.position;
|
int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight,
|
||||||
layoutManager.scrollToPositionWithOffset(lastScrollSection.appItem.position, 0);
|
predictionBarHeight);
|
||||||
|
LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
|
||||||
|
if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
|
||||||
|
layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
|
||||||
}
|
}
|
||||||
|
|
||||||
return lastScrollSection.sectionName;
|
if (mPrevFastScrollFocusedPosition != lastInfo.fastScrollToItem.position) {
|
||||||
|
mPrevFastScrollFocusedPosition = lastInfo.fastScrollToItem.position;
|
||||||
|
|
||||||
|
// Reset the last focused view
|
||||||
|
if (mLastFastScrollFocusedView != null) {
|
||||||
|
mLastFastScrollFocusedView.setFastScrollFocused(false, true);
|
||||||
|
mLastFastScrollFocusedView = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if (mFastScrollMode == FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON) {
|
||||||
* Returns the row index for a app index in the list.
|
smoothSnapToPosition(mPrevFastScrollFocusedPosition, mScrollPosState);
|
||||||
*/
|
} else if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
|
||||||
private int findRowForAppIndex(int index) {
|
final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition);
|
||||||
List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
|
if (vh != null &&
|
||||||
int appIndex = 0;
|
vh.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
|
||||||
int rowCount = 0;
|
mLastFastScrollFocusedView =
|
||||||
for (AlphabeticalAppsList.SectionInfo info : sections) {
|
(BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView;
|
||||||
int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow);
|
mLastFastScrollFocusedView.setFastScrollFocused(true, true);
|
||||||
if (appIndex + info.numApps > index) {
|
|
||||||
return rowCount + ((index - appIndex) / mNumAppsPerRow);
|
|
||||||
}
|
}
|
||||||
appIndex += info.numApps;
|
} else {
|
||||||
rowCount += numRowsInSection;
|
throw new RuntimeException("Unexpected fast scroll mode");
|
||||||
}
|
}
|
||||||
return appIndex;
|
}
|
||||||
|
return lastInfo.sectionName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Returns the total number of rows in the list.
|
public void onFastScrollCompleted() {
|
||||||
*/
|
super.onFastScrollCompleted();
|
||||||
private int getNumRows() {
|
// Reset and clean up the last focused view
|
||||||
List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
|
if (mLastFastScrollFocusedView != null) {
|
||||||
int rowCount = 0;
|
mLastFastScrollFocusedView.setFastScrollFocused(false, true);
|
||||||
for (AlphabeticalAppsList.SectionInfo info : sections) {
|
mLastFastScrollFocusedView = null;
|
||||||
int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow);
|
|
||||||
rowCount += numRowsInSection;
|
|
||||||
}
|
}
|
||||||
return rowCount;
|
mPrevFastScrollFocusedPosition = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the bounds for the scrollbar.
|
* Updates the bounds for the scrollbar.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void updateVerticalScrollbarBounds() {
|
public void onUpdateScrollbar() {
|
||||||
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
|
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
|
||||||
|
|
||||||
// Skip early if there are no items or we haven't been measured
|
// Skip early if there are no items or we haven't been measured
|
||||||
if (items.isEmpty() || mNumAppsPerRow == 0) {
|
if (items.isEmpty() || mNumAppsPerRow == 0) {
|
||||||
verticalScrollbarBounds.setEmpty();
|
mScrollbar.setScrollbarThumbOffset(-1, -1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the index and height of the first visible row (all rows have the same height)
|
// Find the index and height of the first visible row (all rows have the same height)
|
||||||
int x, y;
|
int rowCount = mApps.getNumAppRows();
|
||||||
int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
|
getCurScrollState(mScrollPosState, items);
|
||||||
int rowCount = getNumRows();
|
if (mScrollPosState.rowIndex < 0) {
|
||||||
getCurScrollState(scrollPosState, items);
|
mScrollbar.setScrollbarThumbOffset(-1, -1);
|
||||||
if (scrollPosState.rowIndex != -1) {
|
|
||||||
int height = getHeight() - getPaddingTop() - getPaddingBottom();
|
|
||||||
int totalScrollHeight = rowCount * scrollPosState.rowHeight + predictionBarHeight;
|
|
||||||
if (totalScrollHeight > height) {
|
|
||||||
int scrollbarHeight = (int) (height / ((float) totalScrollHeight / height));
|
|
||||||
|
|
||||||
// Calculate the position and size of the scroll bar
|
|
||||||
if (Utilities.isRtl(getResources())) {
|
|
||||||
x = mBackgroundPadding.left;
|
|
||||||
} else {
|
|
||||||
x = getWidth() - mBackgroundPadding.right - getScrollbarWidth();
|
|
||||||
}
|
|
||||||
|
|
||||||
// To calculate the offset, we compute the percentage of the total scrollable height
|
|
||||||
// that the user has already scrolled and then map that to the scroll bar bounds
|
|
||||||
int availableY = totalScrollHeight - height;
|
|
||||||
int availableScrollY = height - scrollbarHeight;
|
|
||||||
y = (scrollPosState.rowIndex * scrollPosState.rowHeight) + predictionBarHeight
|
|
||||||
- scrollPosState.rowTopOffset;
|
|
||||||
y = getPaddingTop() +
|
|
||||||
(int) (((float) (getPaddingTop() + y) / availableY) * availableScrollY);
|
|
||||||
|
|
||||||
verticalScrollbarBounds.set(x, y, x + getScrollbarWidth(), y + scrollbarHeight);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
verticalScrollbarBounds.setEmpty();
|
int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
|
||||||
|
synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, predictionBarHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current scroll state.
|
* This runnable runs a single frame of the smooth scroll animation and posts the next frame
|
||||||
|
* if necessary.
|
||||||
|
*/
|
||||||
|
private Runnable mSmoothSnapNextFrameRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (mFastScrollFrameIndex < mFastScrollFrames.length) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smoothly snaps to a given position. We do this manually by calculating the keyframes
|
||||||
|
* ourselves and animating the scroll on the recycler view.
|
||||||
|
*/
|
||||||
|
private void smoothSnapToPosition(final int position, ScrollPositionState scrollPosState) {
|
||||||
|
removeCallbacks(mSmoothSnapNextFrameRunnable);
|
||||||
|
|
||||||
|
// Calculate the full animation from the current scroll position to the final scroll
|
||||||
|
// position, and then run the animation for the duration.
|
||||||
|
int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
|
||||||
|
int curScrollY = getPaddingTop() + predictionBarHeight +
|
||||||
|
(scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset;
|
||||||
|
int newScrollY = getScrollAtPosition(position, scrollPosState.rowHeight);
|
||||||
|
int numFrames = mFastScrollFrames.length;
|
||||||
|
for (int i = 0; i < numFrames; i++) {
|
||||||
|
// TODO(winsonc): We can interpolate this as well.
|
||||||
|
mFastScrollFrames[i] = (newScrollY - curScrollY) / numFrames;
|
||||||
|
}
|
||||||
|
mFastScrollFrameIndex = 0;
|
||||||
|
postOnAnimation(mSmoothSnapNextFrameRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current scroll state of the apps rows, not including the prediction
|
||||||
|
* bar.
|
||||||
*/
|
*/
|
||||||
private void getCurScrollState(ScrollPositionState stateOut,
|
private void getCurScrollState(ScrollPositionState stateOut,
|
||||||
List<AlphabeticalAppsList.AdapterItem> items) {
|
List<AlphabeticalAppsList.AdapterItem> items) {
|
||||||
|
@ -288,7 +317,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
||||||
if (position != NO_POSITION) {
|
if (position != NO_POSITION) {
|
||||||
AlphabeticalAppsList.AdapterItem item = items.get(position);
|
AlphabeticalAppsList.AdapterItem item = items.get(position);
|
||||||
if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) {
|
if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) {
|
||||||
stateOut.rowIndex = findRowForAppIndex(item.appIndex);
|
stateOut.rowIndex = item.rowIndex;
|
||||||
stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
|
stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
|
||||||
stateOut.rowHeight = child.getHeight();
|
stateOut.rowHeight = child.getHeight();
|
||||||
break;
|
break;
|
||||||
|
@ -296,4 +325,17 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the scrollY for the given position in the adapter.
|
||||||
|
*/
|
||||||
|
private int getScrollAtPosition(int position, int rowHeight) {
|
||||||
|
AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
|
||||||
|
if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) {
|
||||||
|
int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
|
||||||
|
return getPaddingTop() + predictionBarHeight + item.rowIndex * rowHeight;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,15 +62,13 @@ public class AlphabeticalAppsList {
|
||||||
public static class FastScrollSectionInfo {
|
public static class FastScrollSectionInfo {
|
||||||
// The section name
|
// The section name
|
||||||
public String sectionName;
|
public String sectionName;
|
||||||
// To map the touch (from 0..1) to the index in the app list to jump to in the fast
|
|
||||||
// scroller, we use the fraction in range (0..1) of the app index / total app count.
|
|
||||||
public float appRangeFraction;
|
|
||||||
// The AdapterItem to scroll to for this section
|
// The AdapterItem to scroll to for this section
|
||||||
public AdapterItem appItem;
|
public AdapterItem fastScrollToItem;
|
||||||
|
// The touch fraction that should map to this fast scroll section info
|
||||||
|
public float touchFraction;
|
||||||
|
|
||||||
public FastScrollSectionInfo(String sectionName, float appRangeFraction) {
|
public FastScrollSectionInfo(String sectionName) {
|
||||||
this.sectionName = sectionName;
|
this.sectionName = sectionName;
|
||||||
this.appRangeFraction = appRangeFraction;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +81,8 @@ public class AlphabeticalAppsList {
|
||||||
public int position;
|
public int position;
|
||||||
// The type of this item
|
// The type of this item
|
||||||
public int viewType;
|
public int viewType;
|
||||||
|
// The row that this item shows up on
|
||||||
|
public int rowIndex;
|
||||||
|
|
||||||
/** Section & App properties */
|
/** Section & App properties */
|
||||||
// The section for this item
|
// The section for this item
|
||||||
|
@ -94,6 +94,8 @@ public class AlphabeticalAppsList {
|
||||||
public String sectionName = null;
|
public String sectionName = null;
|
||||||
// The index of this app in the section
|
// The index of this app in the section
|
||||||
public int sectionAppIndex = -1;
|
public int sectionAppIndex = -1;
|
||||||
|
// The index of this app in the row
|
||||||
|
public int rowAppIndex;
|
||||||
// The associated AppInfo for the app
|
// The associated AppInfo for the app
|
||||||
public AppInfo appInfo = null;
|
public AppInfo appInfo = null;
|
||||||
// The index of this app not including sections
|
// The index of this app not including sections
|
||||||
|
@ -172,6 +174,7 @@ public class AlphabeticalAppsList {
|
||||||
private AdapterChangedCallback mAdapterChangedCallback;
|
private AdapterChangedCallback mAdapterChangedCallback;
|
||||||
private int mNumAppsPerRow;
|
private int mNumAppsPerRow;
|
||||||
private int mNumPredictedAppsPerRow;
|
private int mNumPredictedAppsPerRow;
|
||||||
|
private int mNumAppRowsInAdapter;
|
||||||
|
|
||||||
public AlphabeticalAppsList(Context context) {
|
public AlphabeticalAppsList(Context context) {
|
||||||
mLauncher = (Launcher) context;
|
mLauncher = (Launcher) context;
|
||||||
|
@ -240,6 +243,13 @@ public class AlphabeticalAppsList {
|
||||||
return mFilteredApps.size();
|
return mFilteredApps.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of rows of applications (not including predictions)
|
||||||
|
*/
|
||||||
|
public int getNumAppRows() {
|
||||||
|
return mNumAppRowsInAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether there are is a filter set.
|
* Returns whether there are is a filter set.
|
||||||
*/
|
*/
|
||||||
|
@ -419,23 +429,23 @@ public class AlphabeticalAppsList {
|
||||||
// Create a new spacer for the prediction bar
|
// Create a new spacer for the prediction bar
|
||||||
AdapterItem sectionItem = AdapterItem.asPredictionBarSpacer(position++);
|
AdapterItem sectionItem = AdapterItem.asPredictionBarSpacer(position++);
|
||||||
mAdapterItems.add(sectionItem);
|
mAdapterItems.add(sectionItem);
|
||||||
|
// Add a fastscroller section for the prediction bar
|
||||||
|
lastFastScrollerSectionInfo = new FastScrollSectionInfo("");
|
||||||
|
lastFastScrollerSectionInfo.fastScrollToItem = sectionItem;
|
||||||
|
mFastScrollerSections.add(lastFastScrollerSectionInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
|
// Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
|
||||||
// ordered set of sections
|
// ordered set of sections
|
||||||
List<AppInfo> apps = getFiltersAppInfos();
|
for (AppInfo info : getFiltersAppInfos()) {
|
||||||
int numApps = apps.size();
|
|
||||||
for (int i = 0; i < numApps; i++) {
|
|
||||||
AppInfo info = apps.get(i);
|
|
||||||
String sectionName = getAndUpdateCachedSectionName(info.title);
|
String sectionName = getAndUpdateCachedSectionName(info.title);
|
||||||
|
|
||||||
// Create a new section if the section names do not match
|
// Create a new section if the section names do not match
|
||||||
if (lastSectionInfo == null || !sectionName.equals(lastSectionName)) {
|
if (lastSectionInfo == null || !sectionName.equals(lastSectionName)) {
|
||||||
lastSectionName = sectionName;
|
lastSectionName = sectionName;
|
||||||
lastSectionInfo = new SectionInfo();
|
lastSectionInfo = new SectionInfo();
|
||||||
lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName,
|
lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName);
|
||||||
(float) appIndex / numApps);
|
|
||||||
mSections.add(lastSectionInfo);
|
mSections.add(lastSectionInfo);
|
||||||
mFastScrollerSections.add(lastFastScrollerSectionInfo);
|
mFastScrollerSections.add(lastFastScrollerSectionInfo);
|
||||||
|
|
||||||
|
@ -451,7 +461,7 @@ public class AlphabeticalAppsList {
|
||||||
lastSectionInfo.numApps++, info, appIndex++);
|
lastSectionInfo.numApps++, info, appIndex++);
|
||||||
if (lastSectionInfo.firstAppItem == null) {
|
if (lastSectionInfo.firstAppItem == null) {
|
||||||
lastSectionInfo.firstAppItem = appItem;
|
lastSectionInfo.firstAppItem = appItem;
|
||||||
lastFastScrollerSectionInfo.appItem = appItem;
|
lastFastScrollerSectionInfo.fastScrollToItem = appItem;
|
||||||
}
|
}
|
||||||
mAdapterItems.add(appItem);
|
mAdapterItems.add(appItem);
|
||||||
mFilteredApps.add(info);
|
mFilteredApps.add(info);
|
||||||
|
@ -460,6 +470,45 @@ public class AlphabeticalAppsList {
|
||||||
// Merge multiple sections together as requested by the merge strategy for this device
|
// Merge multiple sections together as requested by the merge strategy for this device
|
||||||
mergeSections();
|
mergeSections();
|
||||||
|
|
||||||
|
if (mNumAppsPerRow != 0) {
|
||||||
|
// Update the number of rows in the adapter after we do all the merging (otherwise, we
|
||||||
|
// would have to shift the values again)
|
||||||
|
int numAppsInSection = 0;
|
||||||
|
int numAppsInRow = 0;
|
||||||
|
int rowIndex = -1;
|
||||||
|
for (AdapterItem item : mAdapterItems) {
|
||||||
|
item.rowIndex = 0;
|
||||||
|
if (item.viewType == AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE) {
|
||||||
|
numAppsInSection = 0;
|
||||||
|
} else if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) {
|
||||||
|
if (numAppsInSection % mNumAppsPerRow == 0) {
|
||||||
|
numAppsInRow = 0;
|
||||||
|
rowIndex++;
|
||||||
|
}
|
||||||
|
item.rowIndex = rowIndex;
|
||||||
|
item.rowAppIndex = numAppsInRow;
|
||||||
|
numAppsInSection++;
|
||||||
|
numAppsInRow++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mNumAppRowsInAdapter = rowIndex + 1;
|
||||||
|
|
||||||
|
// Pre-calculate all the fast scroller fractions based on the number of rows, if we have
|
||||||
|
// predicted apps, then we should account for that as a row in the touchFraction
|
||||||
|
float rowFraction = 1f / (mNumAppRowsInAdapter + (mPredictedApps.isEmpty() ? 0 : 1));
|
||||||
|
float initialOffset = mPredictedApps.isEmpty() ? 0 : rowFraction;
|
||||||
|
for (FastScrollSectionInfo info : mFastScrollerSections) {
|
||||||
|
AdapterItem item = info.fastScrollToItem;
|
||||||
|
if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE) {
|
||||||
|
info.touchFraction = 0f;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float subRowFraction = item.rowAppIndex * (rowFraction / mNumAppsPerRow);
|
||||||
|
info.touchFraction = initialOffset + item.rowIndex * rowFraction + subRowFraction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Refresh the recycler view
|
// Refresh the recycler view
|
||||||
if (mAdapter != null) {
|
if (mAdapter != null) {
|
||||||
mAdapter.notifyDataSetChanged();
|
mAdapter.notifyDataSetChanged();
|
||||||
|
@ -511,6 +560,7 @@ public class AlphabeticalAppsList {
|
||||||
// Remove the next section break
|
// Remove the next section break
|
||||||
mAdapterItems.remove(nextSection.sectionBreakItem);
|
mAdapterItems.remove(nextSection.sectionBreakItem);
|
||||||
int pos = mAdapterItems.indexOf(section.firstAppItem);
|
int pos = mAdapterItems.indexOf(section.firstAppItem);
|
||||||
|
|
||||||
// Point the section for these new apps to the merged section
|
// Point the section for these new apps to the merged section
|
||||||
int nextPos = pos + section.numApps;
|
int nextPos = pos + section.numApps;
|
||||||
for (int j = nextPos; j < (nextPos + nextSection.numApps); j++) {
|
for (int j = nextPos; j < (nextPos + nextSection.numApps); j++) {
|
||||||
|
|
|
@ -345,9 +345,11 @@ public class WidgetsContainerView extends BaseContainerView
|
||||||
InsetDrawable background = new InsetDrawable(
|
InsetDrawable background = new InsetDrawable(
|
||||||
getResources().getDrawable(R.drawable.quantum_panel_shape_dark), padding.left, 0,
|
getResources().getDrawable(R.drawable.quantum_panel_shape_dark), padding.left, 0,
|
||||||
padding.right, 0);
|
padding.right, 0);
|
||||||
|
Rect bgPadding = new Rect();
|
||||||
|
background.getPadding(bgPadding);
|
||||||
mView.setBackground(background);
|
mView.setBackground(background);
|
||||||
getRevealView().setBackground(background.getConstantState().newDrawable());
|
getRevealView().setBackground(background.getConstantState().newDrawable());
|
||||||
mView.updateBackgroundPadding(padding);
|
mView.updateBackgroundPadding(bgPadding);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,14 +17,14 @@
|
||||||
package com.android.launcher3.widget;
|
package com.android.launcher3.widget;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Color;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import com.android.launcher3.BaseRecyclerView;
|
import com.android.launcher3.BaseRecyclerView;
|
||||||
import com.android.launcher3.Utilities;
|
import com.android.launcher3.R;
|
||||||
import com.android.launcher3.model.WidgetsModel;
|
|
||||||
import com.android.launcher3.model.PackageItemInfo;
|
import com.android.launcher3.model.PackageItemInfo;
|
||||||
|
import com.android.launcher3.model.WidgetsModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The widgets recycler view.
|
* The widgets recycler view.
|
||||||
|
@ -33,6 +33,7 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
|
||||||
|
|
||||||
private static final String TAG = "WidgetsRecyclerView";
|
private static final String TAG = "WidgetsRecyclerView";
|
||||||
private WidgetsModel mWidgets;
|
private WidgetsModel mWidgets;
|
||||||
|
private ScrollPositionState mScrollPosState = new ScrollPositionState();
|
||||||
|
|
||||||
public WidgetsRecyclerView(Context context) {
|
public WidgetsRecyclerView(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
|
@ -58,6 +59,14 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
|
||||||
addOnItemTouchListener(this);
|
addOnItemTouchListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getFastScrollerTrackColor(int defaultTrackColor) {
|
||||||
|
return Color.WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFastScrollerThumbInactiveColor(int defaultInactiveThumbColor) {
|
||||||
|
return getResources().getColor(R.color.widgets_view_fastscroll_thumb_inactive_color);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the widget model in this view, used to determine the fast scroll position.
|
* Sets the widget model in this view, used to determine the fast scroll position.
|
||||||
*/
|
*/
|
||||||
|
@ -70,15 +79,21 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String scrollToPositionAtProgress(float touchFraction) {
|
public String scrollToPositionAtProgress(float touchFraction) {
|
||||||
float pos = mWidgets.getPackageSize() * touchFraction;
|
int rowCount = mWidgets.getPackageSize();
|
||||||
|
if (rowCount == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
int posInt = (int) pos;
|
// Stop the scroller if it is scrolling
|
||||||
|
stopScroll();
|
||||||
|
|
||||||
|
getCurScrollState(mScrollPosState);
|
||||||
|
float pos = rowCount * touchFraction;
|
||||||
|
int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, 0);
|
||||||
LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
|
LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
|
||||||
getCurScrollState(scrollPosState);
|
layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
|
||||||
layoutManager.scrollToPositionWithOffset((int) pos,
|
|
||||||
(int) (scrollPosState.rowHeight * ((float) posInt - pos)));
|
|
||||||
|
|
||||||
posInt = (int) ((touchFraction == 1)? pos -1 : pos);
|
int posInt = (int) ((touchFraction == 1)? pos -1 : pos);
|
||||||
PackageItemInfo p = mWidgets.getPackageItemInfo(posInt);
|
PackageItemInfo p = mWidgets.getPackageItemInfo(posInt);
|
||||||
return p.titleSectionName;
|
return p.titleSectionName;
|
||||||
}
|
}
|
||||||
|
@ -87,43 +102,23 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
|
||||||
* Updates the bounds for the scrollbar.
|
* Updates the bounds for the scrollbar.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void updateVerticalScrollbarBounds() {
|
public void onUpdateScrollbar() {
|
||||||
int rowCount = mWidgets.getPackageSize();
|
int rowCount = mWidgets.getPackageSize();
|
||||||
verticalScrollbarBounds.setEmpty();
|
|
||||||
|
|
||||||
// Skip early if, there are no items.
|
// Skip early if, there are no items.
|
||||||
if (rowCount == 0) {
|
if (rowCount == 0) {
|
||||||
|
mScrollbar.setScrollbarThumbOffset(-1, -1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip early if, there no child laid out in the container.
|
// Skip early if, there no child laid out in the container.
|
||||||
getCurScrollState(scrollPosState);
|
getCurScrollState(mScrollPosState);
|
||||||
if (scrollPosState.rowIndex < 0) {
|
if (mScrollPosState.rowIndex < 0) {
|
||||||
|
mScrollbar.setScrollbarThumbOffset(-1, -1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int actualHeight = getHeight() - getPaddingTop() - getPaddingBottom();
|
synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, 0);
|
||||||
int totalScrollHeight = rowCount * scrollPosState.rowHeight;
|
|
||||||
// Skip early if the height of all the rows are actually less than the container height.
|
|
||||||
if (totalScrollHeight < actualHeight) {
|
|
||||||
verticalScrollbarBounds.setEmpty();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int scrollbarHeight = (int) (actualHeight / ((float) totalScrollHeight / actualHeight));
|
|
||||||
int availableY = totalScrollHeight - actualHeight;
|
|
||||||
int availableScrollY = actualHeight - scrollbarHeight;
|
|
||||||
int y = (scrollPosState.rowIndex * scrollPosState.rowHeight)
|
|
||||||
- scrollPosState.rowTopOffset;
|
|
||||||
y = getPaddingTop() +
|
|
||||||
(int) (((float) (getPaddingTop() + y) / availableY) * availableScrollY);
|
|
||||||
|
|
||||||
// Calculate the position and size of the scroll bar.
|
|
||||||
int x = getWidth() - getScrollbarWidth() - mBackgroundPadding.right;
|
|
||||||
if (Utilities.isRtl(getResources())) {
|
|
||||||
x = mBackgroundPadding.left;
|
|
||||||
}
|
|
||||||
verticalScrollbarBounds.set(x, y, x + getScrollbarWidth(), y + scrollbarHeight);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue