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 {
|
||||
public float getBackgroundAlpha();
|
||||
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 {
|
||||
public void setWidth(int);
|
||||
public int getWidth();
|
||||
|
@ -25,9 +35,20 @@
|
|||
public int getY();
|
||||
}
|
||||
|
||||
-keep class com.android.launcher3.Workspace {
|
||||
public float getBackgroundAlpha();
|
||||
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.FastBitmapDrawable {
|
||||
public int getBrightness();
|
||||
public void setBrightness(int);
|
||||
}
|
||||
|
||||
-keep class com.android.launcher3.MemoryDumpActivity {
|
||||
|
@ -39,16 +60,7 @@
|
|||
public void setAnimationProgress(float);
|
||||
}
|
||||
|
||||
-keep class com.android.launcher3.FastBitmapDrawable {
|
||||
public int getBrightness();
|
||||
public void setBrightness(int);
|
||||
}
|
||||
|
||||
-keep class com.android.launcher3.BaseRecyclerView {
|
||||
public void setFastScrollerAlpha(float);
|
||||
public float getFastScrollerAlpha();
|
||||
}
|
||||
|
||||
-keep class com.android.launcher3.ButtonDropTarget {
|
||||
public int getTextColor();
|
||||
}
|
||||
-keep class com.android.launcher3.Workspace {
|
||||
public float getBackgroundAlpha();
|
||||
public void setBackgroundAlpha(float);
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/all_apps_scrollbar_thumb_color" />
|
||||
<solid android:color="@color/container_fastscroll_thumb_active_color" />
|
||||
<size
|
||||
android:width="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"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/all_apps_scrollbar_thumb_color" />
|
||||
<solid android:color="@color/container_fastscroll_thumb_active_color" />
|
||||
<size
|
||||
android:width="64dp"
|
||||
android:height="64dp" />
|
|
@ -38,8 +38,8 @@
|
|||
android:id="@+id/prediction_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/all_apps_prediction_bar_top_bottom_padding"
|
||||
android:paddingBottom="@dimen/all_apps_prediction_bar_top_bottom_padding"
|
||||
android:paddingTop="@dimen/all_apps_prediction_bar_top_padding"
|
||||
android:paddingBottom="@dimen/all_apps_prediction_bar_bottom_padding"
|
||||
android:orientation="horizontal"
|
||||
android:focusable="true"
|
||||
android:descendantFocusability="afterDescendants"
|
||||
|
|
|
@ -63,8 +63,8 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/all_apps_search_bar_height"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginRight="6dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginRight="4dp"
|
||||
android:contentDescription="@string/all_apps_search_bar_hint"
|
||||
android:paddingBottom="13dp"
|
||||
android:paddingTop="13dp"
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<resources>
|
||||
<!-- All Apps -->
|
||||
<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 -->
|
||||
<dimen name="toolbar_button_vertical_padding">8dip</dimen>
|
||||
|
|
|
@ -39,11 +39,15 @@
|
|||
<color name="outline_color">#FFFFFFFF</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 -->
|
||||
<color name="all_apps_scrollbar_thumb_color">#009688</color>
|
||||
<color name="all_apps_grid_section_text_color">#009688</color>
|
||||
|
||||
<!-- 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_item_text_color">#C4C4C4</color>
|
||||
<color name="widgets_cell_color">#263238</color>
|
||||
|
|
|
@ -49,12 +49,20 @@
|
|||
<dimen name="toolbar_button_vertical_padding">4dip</dimen>
|
||||
<dimen name="toolbar_button_horizontal_padding">12dip</dimen>
|
||||
|
||||
<!-- All Apps -->
|
||||
<!-- Container -->
|
||||
<!-- Note: This needs to match the fixed insets for the search box. -->
|
||||
<dimen name="container_bounds_inset">8dp</dimen>
|
||||
<!-- Notes: container_bounds_inset - quantum_panel_outer_padding -->
|
||||
<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_section_y_offset">8dp</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_icon_top_bottom_padding">8dp</dimen>
|
||||
<dimen name="all_apps_icon_width_gap">24dp</dimen>
|
||||
<dimen name="all_apps_prediction_bar_top_bottom_padding">16dp</dimen>
|
||||
|
||||
<dimen name="all_apps_fast_scroll_bar_width">4dp</dimen>
|
||||
<dimen name="all_apps_fast_scroll_scrubber_touch_inset">-24dp</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>
|
||||
<!-- 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_prediction_bar_bottom_padding">16dp</dimen>
|
||||
<dimen name="all_apps_list_top_bottom_padding">8dp</dimen>
|
||||
|
||||
<!-- Widget tray -->
|
||||
<dimen name="widget_container_inset">8dp</dimen>
|
||||
|
|
|
@ -16,24 +16,15 @@
|
|||
|
||||
package com.android.launcher3;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
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;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import com.android.launcher3.util.Thunk;
|
||||
|
||||
|
||||
/**
|
||||
* A base {@link RecyclerView}, which does the following:
|
||||
* <ul>
|
||||
|
@ -41,7 +32,7 @@ import com.android.launcher3.util.Thunk;
|
|||
* <li> Enable fast scroller.
|
||||
* </ul>
|
||||
*/
|
||||
public class BaseRecyclerView extends RecyclerView
|
||||
public abstract class BaseRecyclerView extends RecyclerView
|
||||
implements RecyclerView.OnItemTouchListener {
|
||||
|
||||
private static final int SCROLL_DELTA_THRESHOLD_DP = 4;
|
||||
|
@ -50,14 +41,8 @@ public class BaseRecyclerView extends RecyclerView
|
|||
@Thunk int mDy = 0;
|
||||
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
|
||||
* that we can calculate what the scroll bar looks like, and where to jump to from the fast
|
||||
* scroller.
|
||||
|
@ -70,27 +55,12 @@ public class BaseRecyclerView extends RecyclerView
|
|||
// The height of a given row (they are currently all the same height)
|
||||
public int rowHeight;
|
||||
}
|
||||
// Should be maintained inside overriden method #updateVerticalScrollbarBounds
|
||||
public ScrollPositionState scrollPosState = new ScrollPositionState();
|
||||
public Rect verticalScrollbarBounds = new Rect();
|
||||
|
||||
private boolean mDraggingFastScroller;
|
||||
|
||||
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;
|
||||
protected BaseRecyclerViewFastScrollBar mScrollbar;
|
||||
|
||||
private int mDownX;
|
||||
private int mDownY;
|
||||
private int mLastY;
|
||||
private int mScrollbarWidth;
|
||||
private int mScrollbarInset;
|
||||
protected Rect mBackgroundPadding = new Rect();
|
||||
|
||||
public BaseRecyclerView(Context context) {
|
||||
|
@ -104,25 +74,10 @@ public class BaseRecyclerView extends RecyclerView
|
|||
public BaseRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP;
|
||||
mScrollbar = new BaseRecyclerViewFastScrollBar(this, getResources());
|
||||
|
||||
ScrollListener listener = new ScrollListener();
|
||||
setOnScrollListener(listener);
|
||||
|
||||
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 {
|
||||
|
@ -133,6 +88,10 @@ public class BaseRecyclerView extends RecyclerView
|
|||
@Override
|
||||
public void onScrolled(RecyclerView recyclerView, int dx, int 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).
|
||||
*/
|
||||
private boolean handleTouchEvent(MotionEvent ev) {
|
||||
ViewConfiguration config = ViewConfiguration.get(getContext());
|
||||
|
||||
int action = ev.getAction();
|
||||
int x = (int) ev.getX();
|
||||
int y = (int) ev.getY();
|
||||
|
@ -174,41 +131,19 @@ public class BaseRecyclerView extends RecyclerView
|
|||
if (shouldStopScroll(ev)) {
|
||||
stopScroll();
|
||||
}
|
||||
mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
|
||||
break;
|
||||
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;
|
||||
|
||||
// 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);
|
||||
}
|
||||
mLastY = y;
|
||||
mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
mDraggingFastScroller = false;
|
||||
animateFastScrollerVisibility(false);
|
||||
onFastScrollCompleted();
|
||||
mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
|
||||
break;
|
||||
}
|
||||
return mDraggingFastScroller;
|
||||
return mScrollbar.isDragging();
|
||||
}
|
||||
|
||||
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
||||
|
@ -234,159 +169,117 @@ public class BaseRecyclerView extends RecyclerView
|
|||
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
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
super.dispatchDraw(canvas);
|
||||
drawVerticalScrubber(canvas);
|
||||
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());
|
||||
onUpdateScrollbar();
|
||||
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) {
|
||||
if (mFastScrollAlpha > 0f && mFastScrollSectionName != null && !mFastScrollSectionName.isEmpty()) {
|
||||
// Draw the fast scroller popup
|
||||
int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
|
||||
canvas.translate(mFastScrollerBounds.left, mFastScrollerBounds.top);
|
||||
mFastScrollerBg.setAlpha((int) (mFastScrollAlpha * 255));
|
||||
mFastScrollerBg.draw(canvas);
|
||||
mFastScrollTextPaint.setAlpha((int) (mFastScrollAlpha * 255));
|
||||
mFastScrollTextPaint.getTextBounds(mFastScrollSectionName, 0,
|
||||
mFastScrollSectionName.length(), mFastScrollTextBounds);
|
||||
float textWidth = mFastScrollTextPaint.measureText(mFastScrollSectionName);
|
||||
canvas.drawText(mFastScrollSectionName,
|
||||
(mFastScrollerBounds.width() - textWidth) / 2,
|
||||
mFastScrollerBounds.height() -
|
||||
(mFastScrollerBounds.height() - mFastScrollTextBounds.height()) / 2,
|
||||
mFastScrollTextPaint);
|
||||
canvas.restoreToCount(restoreCount);
|
||||
protected void synchronizeScrollBarThumbOffsetToViewScroll(ScrollPositionState scrollPosState,
|
||||
int rowCount, int yOffset) {
|
||||
int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight,
|
||||
yOffset);
|
||||
int availableScrollBarHeight = getAvailableScrollBarHeight();
|
||||
|
||||
// Only show the scrollbar if there is height to be scrolled
|
||||
if (availableScrollHeight <= 0) {
|
||||
mScrollbar.setScrollbarThumbOffset(-1, -1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the scroll bar width.
|
||||
*/
|
||||
public int getScrollbarWidth() {
|
||||
return mScrollbarWidth;
|
||||
}
|
||||
// Calculate the current scroll position, the scrollY of the recycler view accounts for the
|
||||
// view padding, while the scrollBarY is drawn right up to the background padding (ignoring
|
||||
// padding)
|
||||
int scrollY = getPaddingTop() + yOffset +
|
||||
(scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset;
|
||||
int scrollBarY = mBackgroundPadding.top +
|
||||
(int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
|
||||
|
||||
/**
|
||||
* Sets the fast scroller alpha.
|
||||
*/
|
||||
public void setFastScrollerAlpha(float alpha) {
|
||||
mFastScrollAlpha = alpha;
|
||||
invalidateFastScroller(mFastScrollerBounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fast scroller alpha.
|
||||
*/
|
||||
public float getFastScrollerAlpha() {
|
||||
return mFastScrollAlpha;
|
||||
// Calculate the position and size of the scroll bar
|
||||
int scrollBarX;
|
||||
if (Utilities.isRtl(getResources())) {
|
||||
scrollBarX = mBackgroundPadding.left;
|
||||
} else {
|
||||
scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getWidth();
|
||||
}
|
||||
mScrollbar.setScrollbarThumbOffset(scrollBarX, scrollBarY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the touch (from 0..1) to the adapter position that should be visible.
|
||||
* <p>Override in each subclass of this base class.
|
||||
*/
|
||||
public String scrollToPositionAtProgress(float touchFraction) {
|
||||
return null;
|
||||
}
|
||||
public abstract String scrollToPositionAtProgress(float touchFraction);
|
||||
|
||||
/**
|
||||
* Updates the bounds for the scrollbar.
|
||||
* <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) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
public void onFastScrollCompleted() {}
|
||||
}
|
|
@ -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;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
|
@ -24,6 +25,7 @@ import android.content.res.Resources.Theme;
|
|||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
|
@ -34,6 +36,8 @@ import android.view.KeyEvent;
|
|||
import android.view.MotionEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewParent;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.widget.TextView;
|
||||
import com.android.launcher3.IconCache.IconLoadRequest;
|
||||
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
|
||||
* too aggressive.
|
||||
*/
|
||||
public class BubbleTextView extends TextView {
|
||||
public class BubbleTextView extends TextView
|
||||
implements BaseRecyclerViewFastScrollBar.FastScrollFocusableView {
|
||||
|
||||
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_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 Drawable mIcon;
|
||||
private final Drawable mBackground;
|
||||
|
@ -79,6 +91,12 @@ public class BubbleTextView extends TextView {
|
|||
private boolean mIgnorePressedStateChange;
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -335,7 +360,18 @@ public class BubbleTextView extends TextView {
|
|||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
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);
|
||||
|
||||
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.
|
||||
*/
|
||||
|
|
|
@ -1125,27 +1125,6 @@ public class Launcher extends Activity
|
|||
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 {
|
||||
/**
|
||||
* Called when the search overlay is shown.
|
||||
|
|
|
@ -143,13 +143,6 @@ public class LauncherAppState {
|
|||
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) {
|
||||
sLauncherProvider = new WeakReference<LauncherProvider>(provider);
|
||||
}
|
||||
|
|
|
@ -70,10 +70,12 @@ public interface LauncherCallbacks {
|
|||
/*
|
||||
* Extension points for replacing the search experience
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean forceDisableVoiceButtonProxy();
|
||||
public boolean providesSearch();
|
||||
public boolean startSearch(String initialQuery, boolean selectInitialQuery,
|
||||
Bundle appSearchData, Rect sourceBounds);
|
||||
@Deprecated
|
||||
public void startVoice();
|
||||
public boolean hasCustomContentToLeft();
|
||||
public void populateCustomContentContainer();
|
||||
|
|
|
@ -637,6 +637,25 @@ public final class Utilities {
|
|||
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)
|
||||
public static boolean isRtl(Resources res) {
|
||||
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.TargetApi;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
|
@ -155,6 +154,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
|||
private int mSectionNamesMargin;
|
||||
private int mNumAppsPerRow;
|
||||
private int mNumPredictedAppsPerRow;
|
||||
private int mRecyclerViewTopBottomPadding;
|
||||
// This coordinate is relative to this container view
|
||||
private final Point mBoundsCheckLastTouchDownPos = new Point(-1, -1);
|
||||
// This coordinate is relative to its parent
|
||||
|
@ -189,7 +189,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
|||
mPredictionBarHeight = (int) (grid.allAppsIconSizePx + grid.iconDrawablePaddingOriginalPx +
|
||||
Utilities.calculateTextHeight(grid.allAppsIconTextSizePx) +
|
||||
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);
|
||||
mApps = new AlphabeticalAppsList(context);
|
||||
mApps.setAdapterChangedCallback(this);
|
||||
|
@ -199,6 +200,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
|||
mApps.setAdapter(mAdapter);
|
||||
mLayoutManager = mAdapter.getLayoutManager();
|
||||
mItemDecoration = mAdapter.getItemDecoration();
|
||||
mRecyclerViewTopBottomPadding =
|
||||
res.getDimensionPixelSize(R.dimen.all_apps_list_top_bottom_padding);
|
||||
|
||||
mSearchQueryBuilder = new SpannableStringBuilder();
|
||||
Selection.setSelection(mSearchQueryBuilder, 0);
|
||||
|
@ -414,7 +417,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
|||
new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f),
|
||||
MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE);
|
||||
|
||||
mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
|
||||
mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow);
|
||||
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
|
||||
mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm);
|
||||
}
|
||||
|
@ -431,14 +434,16 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
|||
protected void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding) {
|
||||
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(
|
||||
getResources().getDrawable(R.drawable.quantum_panel_shape), padding.left, 0,
|
||||
padding.right, 0);
|
||||
Rect bgPadding = new Rect();
|
||||
background.getPadding(bgPadding);
|
||||
mContainerView.setBackground(background);
|
||||
mRevealView.setBackground(background.getConstantState().newDrawable());
|
||||
mAppsRecyclerView.updateBackgroundPadding(padding);
|
||||
mAdapter.updateBackgroundPadding(padding);
|
||||
mAppsRecyclerView.updateBackgroundPadding(bgPadding);
|
||||
mAdapter.updateBackgroundPadding(bgPadding);
|
||||
|
||||
// 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
|
||||
|
@ -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
|
||||
// names)
|
||||
int startInset = Math.max(mSectionNamesMargin, mAppsRecyclerView.getScrollbarWidth());
|
||||
int startInset = Math.max(mSectionNamesMargin, mAppsRecyclerView.getMaxScrollbarWidth());
|
||||
int topBottomPadding = mRecyclerViewTopBottomPadding;
|
||||
if (isRtl) {
|
||||
mAppsRecyclerView.setPadding(padding.left + mAppsRecyclerView.getScrollbarWidth(), 0,
|
||||
padding.right + startInset, 0);
|
||||
mAppsRecyclerView.setPadding(padding.left + mAppsRecyclerView.getMaxScrollbarWidth(),
|
||||
topBottomPadding, padding.right + startInset, topBottomPadding);
|
||||
} else {
|
||||
mAppsRecyclerView.setPadding(padding.left + startInset, 0,
|
||||
padding.right + mAppsRecyclerView.getScrollbarWidth(), 0);
|
||||
mAppsRecyclerView.setPadding(padding.left + startInset, topBottomPadding,
|
||||
padding.right + mAppsRecyclerView.getMaxScrollbarWidth(), topBottomPadding);
|
||||
}
|
||||
|
||||
// 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
|
||||
mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar);
|
||||
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
|
||||
lp.leftMargin = padding.left + mAppsRecyclerView.getScrollbarWidth();
|
||||
lp.rightMargin = padding.right + mAppsRecyclerView.getScrollbarWidth();
|
||||
lp.leftMargin = padding.left + mAppsRecyclerView.getMaxScrollbarWidth();
|
||||
lp.rightMargin = padding.right + mAppsRecyclerView.getMaxScrollbarWidth();
|
||||
mPredictionBarView.requestLayout();
|
||||
}
|
||||
|
||||
|
|
|
@ -337,7 +337,7 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
|
|||
mPredictedAppsDividerPaint.setColor(0x1E000000);
|
||||
mPredictedAppsDividerPaint.setAntiAlias(true);
|
||||
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.view.View;
|
||||
import com.android.launcher3.BaseRecyclerView;
|
||||
import com.android.launcher3.BaseRecyclerViewFastScrollBar;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.Stats;
|
||||
import com.android.launcher3.Utilities;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -35,13 +35,25 @@ import java.util.List;
|
|||
public class AllAppsRecyclerView extends BaseRecyclerView
|
||||
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 int mNumAppsPerRow;
|
||||
private int mNumPredictedAppsPerRow;
|
||||
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 ScrollPositionState mScrollPosState = new ScrollPositionState();
|
||||
|
||||
public AllAppsRecyclerView(Context context) {
|
||||
this(context, null);
|
||||
|
@ -59,6 +71,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
|||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
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.
|
||||
*/
|
||||
public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) {
|
||||
public void setNumAppsPerRow(int numAppsPerRow) {
|
||||
mNumAppsPerRow = numAppsPerRow;
|
||||
mNumPredictedAppsPerRow = numPredictedAppsPerRow;
|
||||
|
||||
DeviceProfile grid = mLauncher.getDeviceProfile();
|
||||
RecyclerView.RecycledViewPool pool = getRecycledViewPool();
|
||||
|
@ -103,11 +115,12 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
|||
*/
|
||||
public int getScrollPosition() {
|
||||
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
|
||||
getCurScrollState(scrollPosState, items);
|
||||
if (scrollPosState.rowIndex != -1) {
|
||||
getCurScrollState(mScrollPosState, items);
|
||||
if (mScrollPosState.rowIndex != -1) {
|
||||
int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
|
||||
return getPaddingTop() + (scrollPosState.rowIndex * scrollPosState.rowHeight) +
|
||||
predictionBarHeight - scrollPosState.rowTopOffset;
|
||||
return getPaddingTop() + predictionBarHeight +
|
||||
(mScrollPosState.rowIndex * mScrollPosState.rowHeight) -
|
||||
mScrollPosState.rowTopOffset;
|
||||
}
|
||||
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.
|
||||
*/
|
||||
@Override
|
||||
public String scrollToPositionAtProgress(float touchFraction) {
|
||||
// Ensure that we have any sections
|
||||
List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
|
||||
mApps.getFastScrollerSections();
|
||||
if (fastScrollSections.isEmpty()) {
|
||||
int rowCount = mApps.getNumAppRows();
|
||||
if (rowCount == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Stop the scroller if it is scrolling
|
||||
LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
|
||||
stopScroll();
|
||||
|
||||
// If there is a prediction bar, then capture the appropriate area for the prediction bar
|
||||
float predictionBarFraction = 0f;
|
||||
if (!mApps.getPredictedApps().isEmpty()) {
|
||||
predictionBarFraction = (float) mNumPredictedAppsPerRow / mApps.getSize();
|
||||
if (touchFraction <= predictionBarFraction) {
|
||||
// Scroll to the top of the view, where the prediction bar is
|
||||
layoutManager.scrollToPositionWithOffset(0, 0);
|
||||
return "";
|
||||
// Find the fastscroll section that maps to this touch fraction
|
||||
List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
|
||||
mApps.getFastScrollerSections();
|
||||
AlphabeticalAppsList.FastScrollSectionInfo lastInfo = fastScrollSections.get(0);
|
||||
if (mScrollBarMode == FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW) {
|
||||
for (int i = 1; i < fastScrollSections.size(); i++) {
|
||||
AlphabeticalAppsList.FastScrollSectionInfo info = fastScrollSections.get(i);
|
||||
if (info.touchFraction > touchFraction) {
|
||||
break;
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
// Map the touch position back to the scroll of the recycler view
|
||||
getCurScrollState(mScrollPosState, mApps.getAdapterItems());
|
||||
int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
|
||||
int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight,
|
||||
predictionBarHeight);
|
||||
LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
|
||||
if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
|
||||
layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
|
||||
}
|
||||
|
||||
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) {
|
||||
smoothSnapToPosition(mPrevFastScrollFocusedPosition, mScrollPosState);
|
||||
} else if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
|
||||
final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition);
|
||||
if (vh != null &&
|
||||
vh.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
|
||||
mLastFastScrollFocusedView =
|
||||
(BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView;
|
||||
mLastFastScrollFocusedView.setFastScrollFocused(true, true);
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected fast scroll mode");
|
||||
}
|
||||
}
|
||||
|
||||
// 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++) {
|
||||
AlphabeticalAppsList.FastScrollSectionInfo scrollSection = fastScrollSections.get(i);
|
||||
if (lastScrollSection.appRangeFraction <= touchFraction &&
|
||||
touchFraction < scrollSection.appRangeFraction) {
|
||||
break;
|
||||
}
|
||||
lastScrollSection = scrollSection;
|
||||
}
|
||||
|
||||
// Scroll to the view at the position, anchored at the top of the screen. We call the scroll
|
||||
// method on the LayoutManager directly since it is not exposed by RecyclerView.
|
||||
if (mLastFastscrollPosition != lastScrollSection.appItem.position) {
|
||||
mLastFastscrollPosition = lastScrollSection.appItem.position;
|
||||
layoutManager.scrollToPositionWithOffset(lastScrollSection.appItem.position, 0);
|
||||
}
|
||||
|
||||
return lastScrollSection.sectionName;
|
||||
return lastInfo.sectionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the row index for a app index in the list.
|
||||
*/
|
||||
private int findRowForAppIndex(int index) {
|
||||
List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
|
||||
int appIndex = 0;
|
||||
int rowCount = 0;
|
||||
for (AlphabeticalAppsList.SectionInfo info : sections) {
|
||||
int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow);
|
||||
if (appIndex + info.numApps > index) {
|
||||
return rowCount + ((index - appIndex) / mNumAppsPerRow);
|
||||
}
|
||||
appIndex += info.numApps;
|
||||
rowCount += numRowsInSection;
|
||||
@Override
|
||||
public void onFastScrollCompleted() {
|
||||
super.onFastScrollCompleted();
|
||||
// Reset and clean up the last focused view
|
||||
if (mLastFastScrollFocusedView != null) {
|
||||
mLastFastScrollFocusedView.setFastScrollFocused(false, true);
|
||||
mLastFastScrollFocusedView = null;
|
||||
}
|
||||
return appIndex;
|
||||
mPrevFastScrollFocusedPosition = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of rows in the list.
|
||||
*/
|
||||
private int getNumRows() {
|
||||
List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
|
||||
int rowCount = 0;
|
||||
for (AlphabeticalAppsList.SectionInfo info : sections) {
|
||||
int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow);
|
||||
rowCount += numRowsInSection;
|
||||
}
|
||||
return rowCount;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the bounds for the scrollbar.
|
||||
*/
|
||||
@Override
|
||||
public void updateVerticalScrollbarBounds() {
|
||||
public void onUpdateScrollbar() {
|
||||
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
|
||||
|
||||
// Skip early if there are no items or we haven't been measured
|
||||
if (items.isEmpty() || mNumAppsPerRow == 0) {
|
||||
verticalScrollbarBounds.setEmpty();
|
||||
mScrollbar.setScrollbarThumbOffset(-1, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the index and height of the first visible row (all rows have the same height)
|
||||
int x, y;
|
||||
int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
|
||||
int rowCount = getNumRows();
|
||||
getCurScrollState(scrollPosState, items);
|
||||
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;
|
||||
}
|
||||
int rowCount = mApps.getNumAppRows();
|
||||
getCurScrollState(mScrollPosState, items);
|
||||
if (mScrollPosState.rowIndex < 0) {
|
||||
mScrollbar.setScrollbarThumbOffset(-1, -1);
|
||||
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,
|
||||
List<AlphabeticalAppsList.AdapterItem> items) {
|
||||
|
@ -288,7 +317,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
|||
if (position != NO_POSITION) {
|
||||
AlphabeticalAppsList.AdapterItem item = items.get(position);
|
||||
if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) {
|
||||
stateOut.rowIndex = findRowForAppIndex(item.appIndex);
|
||||
stateOut.rowIndex = item.rowIndex;
|
||||
stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
|
||||
stateOut.rowHeight = child.getHeight();
|
||||
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 {
|
||||
// The section name
|
||||
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
|
||||
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.appRangeFraction = appRangeFraction;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,6 +81,8 @@ public class AlphabeticalAppsList {
|
|||
public int position;
|
||||
// The type of this item
|
||||
public int viewType;
|
||||
// The row that this item shows up on
|
||||
public int rowIndex;
|
||||
|
||||
/** Section & App properties */
|
||||
// The section for this item
|
||||
|
@ -94,6 +94,8 @@ public class AlphabeticalAppsList {
|
|||
public String sectionName = null;
|
||||
// The index of this app in the section
|
||||
public int sectionAppIndex = -1;
|
||||
// The index of this app in the row
|
||||
public int rowAppIndex;
|
||||
// The associated AppInfo for the app
|
||||
public AppInfo appInfo = null;
|
||||
// The index of this app not including sections
|
||||
|
@ -172,6 +174,7 @@ public class AlphabeticalAppsList {
|
|||
private AdapterChangedCallback mAdapterChangedCallback;
|
||||
private int mNumAppsPerRow;
|
||||
private int mNumPredictedAppsPerRow;
|
||||
private int mNumAppRowsInAdapter;
|
||||
|
||||
public AlphabeticalAppsList(Context context) {
|
||||
mLauncher = (Launcher) context;
|
||||
|
@ -240,6 +243,13 @@ public class AlphabeticalAppsList {
|
|||
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.
|
||||
*/
|
||||
|
@ -419,23 +429,23 @@ public class AlphabeticalAppsList {
|
|||
// Create a new spacer for the prediction bar
|
||||
AdapterItem sectionItem = AdapterItem.asPredictionBarSpacer(position++);
|
||||
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
|
||||
// ordered set of sections
|
||||
List<AppInfo> apps = getFiltersAppInfos();
|
||||
int numApps = apps.size();
|
||||
for (int i = 0; i < numApps; i++) {
|
||||
AppInfo info = apps.get(i);
|
||||
for (AppInfo info : getFiltersAppInfos()) {
|
||||
String sectionName = getAndUpdateCachedSectionName(info.title);
|
||||
|
||||
// Create a new section if the section names do not match
|
||||
if (lastSectionInfo == null || !sectionName.equals(lastSectionName)) {
|
||||
lastSectionName = sectionName;
|
||||
lastSectionInfo = new SectionInfo();
|
||||
lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName,
|
||||
(float) appIndex / numApps);
|
||||
lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName);
|
||||
mSections.add(lastSectionInfo);
|
||||
mFastScrollerSections.add(lastFastScrollerSectionInfo);
|
||||
|
||||
|
@ -451,7 +461,7 @@ public class AlphabeticalAppsList {
|
|||
lastSectionInfo.numApps++, info, appIndex++);
|
||||
if (lastSectionInfo.firstAppItem == null) {
|
||||
lastSectionInfo.firstAppItem = appItem;
|
||||
lastFastScrollerSectionInfo.appItem = appItem;
|
||||
lastFastScrollerSectionInfo.fastScrollToItem = appItem;
|
||||
}
|
||||
mAdapterItems.add(appItem);
|
||||
mFilteredApps.add(info);
|
||||
|
@ -460,6 +470,45 @@ public class AlphabeticalAppsList {
|
|||
// Merge multiple sections together as requested by the merge strategy for this device
|
||||
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
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
|
@ -511,6 +560,7 @@ public class AlphabeticalAppsList {
|
|||
// Remove the next section break
|
||||
mAdapterItems.remove(nextSection.sectionBreakItem);
|
||||
int pos = mAdapterItems.indexOf(section.firstAppItem);
|
||||
|
||||
// Point the section for these new apps to the merged section
|
||||
int nextPos = pos + section.numApps;
|
||||
for (int j = nextPos; j < (nextPos + nextSection.numApps); j++) {
|
||||
|
|
|
@ -345,9 +345,11 @@ public class WidgetsContainerView extends BaseContainerView
|
|||
InsetDrawable background = new InsetDrawable(
|
||||
getResources().getDrawable(R.drawable.quantum_panel_shape_dark), padding.left, 0,
|
||||
padding.right, 0);
|
||||
Rect bgPadding = new Rect();
|
||||
background.getPadding(bgPadding);
|
||||
mView.setBackground(background);
|
||||
getRevealView().setBackground(background.getConstantState().newDrawable());
|
||||
mView.updateBackgroundPadding(padding);
|
||||
mView.updateBackgroundPadding(bgPadding);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,14 +17,14 @@
|
|||
package com.android.launcher3.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Color;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import com.android.launcher3.BaseRecyclerView;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.model.WidgetsModel;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.model.PackageItemInfo;
|
||||
import com.android.launcher3.model.WidgetsModel;
|
||||
|
||||
/**
|
||||
* The widgets recycler view.
|
||||
|
@ -33,6 +33,7 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
|
|||
|
||||
private static final String TAG = "WidgetsRecyclerView";
|
||||
private WidgetsModel mWidgets;
|
||||
private ScrollPositionState mScrollPosState = new ScrollPositionState();
|
||||
|
||||
public WidgetsRecyclerView(Context context) {
|
||||
this(context, null);
|
||||
|
@ -58,6 +59,14 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
|
|||
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.
|
||||
*/
|
||||
|
@ -70,15 +79,21 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
|
|||
*/
|
||||
@Override
|
||||
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());
|
||||
getCurScrollState(scrollPosState);
|
||||
layoutManager.scrollToPositionWithOffset((int) pos,
|
||||
(int) (scrollPosState.rowHeight * ((float) posInt - pos)));
|
||||
layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
|
||||
|
||||
posInt = (int) ((touchFraction == 1)? pos -1 : pos);
|
||||
int posInt = (int) ((touchFraction == 1)? pos -1 : pos);
|
||||
PackageItemInfo p = mWidgets.getPackageItemInfo(posInt);
|
||||
return p.titleSectionName;
|
||||
}
|
||||
|
@ -87,43 +102,23 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
|
|||
* Updates the bounds for the scrollbar.
|
||||
*/
|
||||
@Override
|
||||
public void updateVerticalScrollbarBounds() {
|
||||
public void onUpdateScrollbar() {
|
||||
int rowCount = mWidgets.getPackageSize();
|
||||
verticalScrollbarBounds.setEmpty();
|
||||
|
||||
// Skip early if, there are no items.
|
||||
if (rowCount == 0) {
|
||||
mScrollbar.setScrollbarThumbOffset(-1, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip early if, there no child laid out in the container.
|
||||
getCurScrollState(scrollPosState);
|
||||
if (scrollPosState.rowIndex < 0) {
|
||||
getCurScrollState(mScrollPosState);
|
||||
if (mScrollPosState.rowIndex < 0) {
|
||||
mScrollbar.setScrollbarThumbOffset(-1, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
int actualHeight = getHeight() - getPaddingTop() - getPaddingBottom();
|
||||
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);
|
||||
synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue