diff options
Diffstat (limited to 'src/com')
22 files changed, 675 insertions, 760 deletions
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java index b63ef788a..8d418f984 100644 --- a/src/com/android/launcher3/BaseRecyclerView.java +++ b/src/com/android/launcher3/BaseRecyclerView.java @@ -16,15 +16,28 @@ package com.android.launcher3; +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 will NOT intercept a touch sequence unless the scrolling - * velocity is below a predefined threshold. + * A base {@link RecyclerView}, which does the following: + * <ul> + * <li> NOT intercept a touch unless the scrolling velocity is below a predefined threshold. + * <li> Enable fast scroller. + * </ul> */ public class BaseRecyclerView extends RecyclerView implements RecyclerView.OnItemTouchListener { @@ -35,6 +48,53 @@ 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() + * 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. + */ + public static class ScrollPositionState { + // The index of the first visible row + public int rowIndex; + // The offset of the first visible row + public int rowTopOffset; + // 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; + + private int mDownX; + private int mDownY; + private int mLastX; + private int mLastY; + private int mScrollbarWidth; + private int mScrollbarMinHeight; + private int mScrollbarInset; + private Rect mBackgroundPadding = new Rect(); + + + public BaseRecyclerView(Context context) { this(context, null); } @@ -49,6 +109,24 @@ public class BaseRecyclerView extends RecyclerView 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); + mScrollbarMinHeight = + res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_bar_min_height); + mScrollbarInset = + res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_scrubber_touch_inset); + setFastScrollerAlpha(mFastScrollAlpha); + setOverScrollMode(View.OVER_SCROLL_NEVER); } private class ScrollListener extends OnScrollListener { @@ -68,17 +146,74 @@ public class BaseRecyclerView extends RecyclerView addOnItemTouchListener(this); } + /** + * We intercept the touch handling only to support fast scrolling when initiated from the + * scroll bar. Otherwise, we fall back to the default RecyclerView touch handling. + */ @Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) { - if (shouldStopScroll(ev)) { - stopScroll(); - } - return false; + return handleTouchEvent(ev); } @Override public void onTouchEvent(RecyclerView rv, MotionEvent ev) { - // Do nothing. + handleTouchEvent(ev); + } + + /** + * Handles the touch event and determines whether to show the fast scroller (or updates it if + * 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(); + switch (action) { + case MotionEvent.ACTION_DOWN: + // Keep track of the down positions + mDownX = mLastX = x; + mDownY = mLastY = y; + if (shouldStopScroll(ev)) { + stopScroll(); + } + 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) { + mLastX = x; + 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); + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mDraggingFastScroller = false; + animateFastScrollerVisibility(false); + break; + } + return mDraggingFastScroller; } public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { @@ -99,4 +234,127 @@ public class BaseRecyclerView extends RecyclerView } return false; } + + @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()); + mScrollbar.draw(canvas); + canvas.restoreToCount(restoreCount); + } + + /** + * Draws the fast scroller popup. + */ + 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); + } + } + + /** + * Returns the scroll bar width. + */ + public int getScrollbarWidth() { + return mScrollbarWidth; + } + + /** + * Sets the fast scroller alpha. + */ + public void setFastScrollerAlpha(float alpha) { + mFastScrollAlpha = alpha; + invalidateFastScroller(mFastScrollerBounds); + } + + /** + * 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; + } + + /** + * Updates the bounds for the scrollbar. + * <p>Override in each subclass of this base class. + */ + public void updateVerticalScrollbarBounds() {}; + + /** + * Animates the visibility of the fast scroller popup. + */ + private void animateFastScrollerVisibility(boolean visible) { + ObjectAnimator anim = ObjectAnimator.ofFloat(this, "fastScrollerAlpha", visible ? 1f : 0f); + anim.setDuration(visible ? 200 : 150); + anim.start(); + } + + /** + * 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 + getScrollBarSize(); + } else { + x = getWidth() - getPaddingRight() - getScrollBarSize() - 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(); + } + } }
\ No newline at end of file diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 2b1cfe0e4..61567ac00 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -168,7 +168,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { private int[] mDirectionVector = new int[2]; int[] mPreviousReorderDirection = new int[2]; private static final int INVALID_DIRECTION = -100; - private DropTarget.DragEnforcer mDragEnforcer; private final Rect mTempRect = new Rect(); @@ -188,7 +187,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { public CellLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mDragEnforcer = new DropTarget.DragEnforcer(context); // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show // the user where a dragged item will land when dropped. @@ -2637,7 +2635,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { * or it may have begun on another layout. */ void onDragEnter() { - mDragEnforcer.onDragEnter(); mDragging = true; } @@ -2645,7 +2642,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { * Called when drag has left this CellLayout or has been completed (successfully or not) */ void onDragExit() { - mDragEnforcer.onDragExit(); // This can actually be called when we aren't in a drag, e.g. when adding a new // item to this layout via the customize drawer. // Guard against that case. diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java index 423a9a3d5..41e053eed 100644 --- a/src/com/android/launcher3/DragLayer.java +++ b/src/com/android/launcher3/DragLayer.java @@ -918,7 +918,7 @@ public class DragLayer extends InsettableFrameLayout { void showPageHints() { mShowPageHints = true; Workspace workspace = mLauncher.getWorkspace(); - getDescendantRectRelativeToSelf(workspace.getChildAt(workspace.getChildCount() - 1), + getDescendantRectRelativeToSelf(workspace.getChildAt(workspace.numCustomPages()), mScrollChildPosition); invalidate(); } diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java index a3828c1d0..c8fac5466 100644 --- a/src/com/android/launcher3/DropTarget.java +++ b/src/com/android/launcher3/DropTarget.java @@ -16,10 +16,8 @@ package com.android.launcher3; -import android.content.Context; import android.graphics.PointF; import android.graphics.Rect; -import android.util.Log; /** * Interface defining an object that can receive a drag. @@ -93,43 +91,6 @@ public interface DropTarget { } } - public static class DragEnforcer implements DragController.DragListener { - int dragParity = 0; - - public DragEnforcer(Context context) { - Launcher launcher = (Launcher) context; - launcher.getDragController().addDragListener(this); - } - - void onDragEnter() { - dragParity++; - if (dragParity != 1) { - Log.e(TAG, "onDragEnter: Drag contract violated: " + dragParity); - } - } - - void onDragExit() { - dragParity--; - if (dragParity != 0) { - Log.e(TAG, "onDragExit: Drag contract violated: " + dragParity); - } - } - - @Override - public void onDragStart(DragSource source, Object info, int dragAction) { - if (dragParity != 0) { - Log.e(TAG, "onDragEnter: Drag contract violated: " + dragParity); - } - } - - @Override - public void onDragEnd() { - if (dragParity != 0) { - Log.e(TAG, "onDragExit: Drag contract violated: " + dragParity); - } - } - } - /** * Used to temporarily disable certain drop targets * diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java index 0c91a7113..6dfca9ef3 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -49,10 +49,8 @@ import com.android.launcher3.util.Thunk; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Locale; -import java.util.Map.Entry; import java.util.Stack; /** @@ -75,8 +73,8 @@ public class IconCache { @Thunk static class CacheEntry { public Bitmap icon; - public CharSequence title; - public CharSequence contentDescription; + public CharSequence title = ""; + public CharSequence contentDescription = ""; public boolean isLowResIcon; } @@ -367,13 +365,6 @@ public class IconCache { } /** - * Empty out the cache. - */ - public synchronized void flush() { - mCache.clear(); - } - - /** * Fetches high-res icon for the provided ItemInfo and updates the caller when done. * @return a request ID that can be used to cancel the request. */ @@ -584,7 +575,7 @@ public class IconCache { CacheEntry entry = mCache.get(cacheKey); if (entry == null || (entry.isLowResIcon && !useLowResIcon)) { entry = new CacheEntry(); - mCache.put(cacheKey, entry); + boolean entryUpdated = true; // Check the DB first. if (!getEntryFromDB(cn, user, entry, useLowResIcon)) { @@ -609,8 +600,14 @@ public class IconCache { } catch (NameNotFoundException e) { if (DEBUG) Log.d(TAG, "Application not installed " + packageName); + entryUpdated = false; } } + + // Only add a filled-out entry to the cache + if (entryUpdated) { + mCache.put(cacheKey, entry); + } } return entry; } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 5dd64e0e2..5dac3b3da 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -88,6 +88,7 @@ import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; +import android.view.animation.OvershootInterpolator; import android.view.inputmethod.InputMethodManager; import android.widget.Advanceable; import android.widget.FrameLayout; @@ -111,11 +112,8 @@ import com.android.launcher3.widget.PendingAddWidgetInfo; import com.android.launcher3.widget.WidgetHostViewLoader; import com.android.launcher3.widget.WidgetsContainerView; -import java.io.DataInputStream; -import java.io.DataOutputStream; import java.io.File; import java.io.FileDescriptor; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; @@ -163,14 +161,14 @@ public class Launcher extends Activity private static final int WORKSPACE_BACKGROUND_TRANSPARENT = 1; private static final int WORKSPACE_BACKGROUND_BLACK = 2; + private static final float BOUNCE_ANIMATION_TENSION = 1.3f; + /** * IntentStarter uses request codes starting with this. This must be greater than all activity * request codes used internally. */ protected static final int REQUEST_LAST = 100; - static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate"; - static final int SCREEN_COUNT = 5; // To turn on these properties, type @@ -309,8 +307,6 @@ public class Launcher extends Activity private boolean mHasFocus = false; private boolean mAttached = false; - @Thunk static LocaleConfiguration sLocaleConfiguration = null; - private static LongArrayMap<FolderInfo> sFolders = new LongArrayMap<>(); private View.OnTouchListener mHapticFeedbackTouchListener; @@ -468,7 +464,6 @@ public class Launcher extends Activity Environment.getExternalStorageDirectory() + "/launcher"); } - checkForLocaleChange(); setContentView(R.layout.launcher); setupViews(); @@ -558,6 +553,35 @@ public class Launcher extends Activity } } }); + mLauncherCallbacks.setLauncherSearchCallback(new Launcher.LauncherSearchCallbacks() { + private boolean mImportanceStored = false; + private int mWorkspaceImportanceForAccessibility = + View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; + private int mHotseatImportanceForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; + + @Override + public void onSearchOverlayOpened() { + if (mImportanceStored) { + return; + } + // The underlying workspace and hotseat are temporarily suppressed by the search + // overlay. So they sholudn't be accessible. + mWorkspaceImportanceForAccessibility = mWorkspace.getImportantForAccessibility(); + mHotseatImportanceForAccessibility = mHotseat.getImportantForAccessibility(); + mWorkspace.setImportantForAccessibility( + View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + mHotseat.setImportantForAccessibility( + View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + mImportanceStored = true; + } + + @Override + public void onSearchOverlayClosed() { + mWorkspace.setImportantForAccessibility(mWorkspaceImportanceForAccessibility); + mHotseat.setImportantForAccessibility(mHotseatImportanceForAccessibility); + mImportanceStored = false; + } + }); return true; } @@ -606,108 +630,6 @@ public class Launcher extends Activity } } - @Thunk void checkForLocaleChange() { - if (sLocaleConfiguration == null) { - new AsyncTask<Void, Void, LocaleConfiguration>() { - @Override - protected LocaleConfiguration doInBackground(Void... unused) { - LocaleConfiguration localeConfiguration = new LocaleConfiguration(); - readConfiguration(Launcher.this, localeConfiguration); - return localeConfiguration; - } - - @Override - protected void onPostExecute(LocaleConfiguration result) { - sLocaleConfiguration = result; - checkForLocaleChange(); // recursive, but now with a locale configuration - } - }.execute(); - return; - } - - final Configuration configuration = getResources().getConfiguration(); - - final String previousLocale = sLocaleConfiguration.locale; - final String locale = configuration.locale.toString(); - - final int previousMcc = sLocaleConfiguration.mcc; - final int mcc = configuration.mcc; - - final int previousMnc = sLocaleConfiguration.mnc; - final int mnc = configuration.mnc; - - boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc; - - if (localeChanged) { - sLocaleConfiguration.locale = locale; - sLocaleConfiguration.mcc = mcc; - sLocaleConfiguration.mnc = mnc; - - mIconCache.flush(); - - final LocaleConfiguration localeConfiguration = sLocaleConfiguration; - new AsyncTask<Void, Void, Void>() { - public Void doInBackground(Void ... args) { - writeConfiguration(Launcher.this, localeConfiguration); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); - } - } - - @Thunk static class LocaleConfiguration { - public String locale; - public int mcc = -1; - public int mnc = -1; - } - - @Thunk static void readConfiguration(Context context, LocaleConfiguration configuration) { - DataInputStream in = null; - try { - in = new DataInputStream(context.openFileInput(LauncherFiles.LAUNCHER_PREFERENCES)); - configuration.locale = in.readUTF(); - configuration.mcc = in.readInt(); - configuration.mnc = in.readInt(); - } catch (FileNotFoundException e) { - // Ignore - } catch (IOException e) { - // Ignore - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - // Ignore - } - } - } - } - - @Thunk static void writeConfiguration(Context context, LocaleConfiguration configuration) { - DataOutputStream out = null; - try { - out = new DataOutputStream(context.openFileOutput( - LauncherFiles.LAUNCHER_PREFERENCES, MODE_PRIVATE)); - out.writeUTF(configuration.locale); - out.writeInt(configuration.mcc); - out.writeInt(configuration.mnc); - out.flush(); - } catch (FileNotFoundException e) { - // Ignore - } catch (IOException e) { - //noinspection ResultOfMethodCallIgnored - context.getFileStreamPath(LauncherFiles.LAUNCHER_PREFERENCES).delete(); - } finally { - if (out != null) { - try { - out.close(); - } catch (IOException e) { - // Ignore - } - } - } - } - public Stats getStats() { return mStats; } @@ -1055,8 +977,8 @@ public class Launcher extends Activity } // Background was set to gradient in onPause(), restore to transparent if in all apps. - setWorkspaceBackground(mState == State.WORKSPACE ? WORKSPACE_BACKGROUND_TRANSPARENT - : WORKSPACE_BACKGROUND_GRADIENT); + setWorkspaceBackground(mState == State.WORKSPACE ? WORKSPACE_BACKGROUND_GRADIENT + : WORKSPACE_BACKGROUND_TRANSPARENT); mPaused = false; if (mRestoring || mOnResumeNeedsLoad) { @@ -1207,6 +1129,18 @@ public class Launcher extends Activity public void dismissAllApps(); } + public interface LauncherSearchCallbacks { + /** + * Called when the search overlay is shown. + */ + public void onSearchOverlayOpened(); + + /** + * Called when the search overlay is dismissed. + */ + public void onSearchOverlayClosed(); + } + public interface LauncherOverlayCallbacks { /** * This method indicates whether a call to {@link #enterFullImmersion()} will succeed, @@ -4194,7 +4128,7 @@ public class Launcher extends Activity PropertyValuesHolder.ofFloat("scaleY", 1f)); bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION); bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY); - bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator()); + bounceAnim.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION)); return bounceAnim; } diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java index 6ff76665c..6a248a332 100644 --- a/src/com/android/launcher3/LauncherAnimUtils.java +++ b/src/com/android/launcher3/LauncherAnimUtils.java @@ -26,6 +26,9 @@ import android.os.Build; import android.view.View; import android.view.ViewAnimationUtils; import android.view.ViewTreeObserver; + +import com.android.launcher3.util.UiThreadCircularReveal; + import java.util.HashSet; import java.util.WeakHashMap; @@ -130,13 +133,11 @@ public class LauncherAnimUtils { } @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public static Animator createCircularReveal(View view, int centerX, + public static ValueAnimator createCircularReveal(View view, int centerX, int centerY, float startRadius, float endRadius) { - Animator anim = ViewAnimationUtils.createCircularReveal(view, centerX, + ValueAnimator anim = UiThreadCircularReveal.createCircularReveal(view, centerX, centerY, startRadius, endRadius); - if (anim instanceof ValueAnimator) { - new FirstFrameAnimatorHelper((ValueAnimator) anim, view); - } + new FirstFrameAnimatorHelper(anim, view); return anim; } } diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java index 0124d1f28..a5f36ba93 100644 --- a/src/com/android/launcher3/LauncherCallbacks.java +++ b/src/com/android/launcher3/LauncherCallbacks.java @@ -119,4 +119,11 @@ public interface LauncherCallbacks { */ public void setLauncherAppsCallback(Object callbacks); + /** + * Sets the callbacks to allow reacting the actions of search overlays of the launcher. + * + * @param callbacks A set of callbacks to the Launcher, is actually a LauncherSearchCallback, + * but for implementation purposes is passed around as an object. + */ + public void setLauncherSearchCallback(Object callbacks); } diff --git a/src/com/android/launcher3/LauncherExtension.java b/src/com/android/launcher3/LauncherExtension.java index 14ad6016c..09a105bcc 100644 --- a/src/com/android/launcher3/LauncherExtension.java +++ b/src/com/android/launcher3/LauncherExtension.java @@ -289,6 +289,11 @@ public class LauncherExtension extends Launcher { // Do nothing } + @Override + public void setLauncherSearchCallback(Object callbacks) { + // Do nothing + } + class LauncherExtensionOverlay implements LauncherOverlay { LauncherOverlayCallbacks mLauncherOverlayCallbacks; ViewGroup mOverlayView; diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java index 03ec4bf7a..c08cd0bf5 100644 --- a/src/com/android/launcher3/LauncherFiles.java +++ b/src/com/android/launcher3/LauncherFiles.java @@ -17,7 +17,6 @@ public class LauncherFiles { public static final String DEFAULT_WALLPAPER_THUMBNAIL = "default_thumb2.jpg"; public static final String DEFAULT_WALLPAPER_THUMBNAIL_OLD = "default_thumb.jpg"; public static final String LAUNCHER_DB = "launcher.db"; - public static final String LAUNCHER_PREFERENCES = "launcher.preferences"; public static final String SHARED_PREFERENCES_KEY = "com.android.launcher3.prefs"; public static final String WALLPAPER_CROP_PREFERENCES_KEY = "com.android.launcher3.WallpaperCropActivity"; @@ -31,7 +30,6 @@ public class LauncherFiles { DEFAULT_WALLPAPER_THUMBNAIL, DEFAULT_WALLPAPER_THUMBNAIL_OLD, LAUNCHER_DB, - LAUNCHER_PREFERENCES, SHARED_PREFERENCES_KEY + XML, WALLPAPER_CROP_PREFERENCES_KEY + XML, WALLPAPER_IMAGES_DB, @@ -43,5 +41,6 @@ public class LauncherFiles { public static final List<String> OBSOLETE_FILES = Collections.unmodifiableList(Arrays.asList( "launches.log", "stats.log", + "launcher.preferences", "com.android.launcher3.compat.PackageInstallerCompatV16.queue")); } diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java index f373fde2d..a006d141b 100644 --- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java +++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java @@ -26,12 +26,14 @@ import android.annotation.SuppressLint; import android.content.res.Resources; import android.util.Log; import android.view.View; -import android.view.ViewAnimationUtils; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; + import com.android.launcher3.allapps.AllAppsContainerView; +import com.android.launcher3.util.UiThreadCircularReveal; import com.android.launcher3.util.Thunk; import com.android.launcher3.widget.WidgetsContainerView; + import java.util.HashMap; /** @@ -320,7 +322,7 @@ public class LauncherStateTransitionAnimation { float startRadius = pCb.getMaterialRevealViewStartFinalRadius(); AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener( revealView, allAppsButtonView); - Animator reveal = ViewAnimationUtils.createCircularReveal(revealView, width / 2, + Animator reveal = UiThreadCircularReveal.createCircularReveal(revealView, width / 2, height / 2, startRadius, revealRadius); reveal.setDuration(revealDuration); reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); @@ -587,14 +589,14 @@ public class LauncherStateTransitionAnimation { TimeInterpolator decelerateInterpolator = material ? new LogDecelerateInterpolator(100, 0) : new DecelerateInterpolator(1f); - ObjectAnimator panelDriftY = LauncherAnimUtils.ofFloat(revealView, "translationY", + ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY", 0, revealViewToYDrift); panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY); panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); panelDriftY.setInterpolator(decelerateInterpolator); mStateAnimation.play(panelDriftY); - ObjectAnimator panelDriftX = LauncherAnimUtils.ofFloat(revealView, "translationX", + ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX", 0, revealViewToXDrift); panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY); panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); @@ -605,7 +607,7 @@ public class LauncherStateTransitionAnimation { final float revealViewToAlpha = !material ? 0f : pCb.getMaterialRevealViewFinalAlpha(revealView); if (revealViewToAlpha != 1f) { - ObjectAnimator panelAlpha = LauncherAnimUtils.ofFloat(revealView, "alpha", + ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha", 1f, revealViewToAlpha); panelAlpha.setDuration(material ? revealDuration : 150); panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY); @@ -617,7 +619,7 @@ public class LauncherStateTransitionAnimation { layerViews.put(contentView, BUILD_AND_SET_LAYER); // Create the individual animators - ObjectAnimator pageDrift = LauncherAnimUtils.ofFloat(contentView, "translationY", + ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 0, revealViewToYDrift); contentView.setTranslationY(0); pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY); @@ -626,7 +628,7 @@ public class LauncherStateTransitionAnimation { mStateAnimation.play(pageDrift); contentView.setAlpha(1f); - ObjectAnimator itemsAlpha = LauncherAnimUtils.ofFloat(contentView, "alpha", 1f, 0f); + ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f); itemsAlpha.setDuration(100); itemsAlpha.setInterpolator(decelerateInterpolator); mStateAnimation.play(itemsAlpha); @@ -636,9 +638,8 @@ public class LauncherStateTransitionAnimation { float finalRadius = pCb.getMaterialRevealViewStartFinalRadius(); AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener(revealView, allAppsButtonView); - Animator reveal = - LauncherAnimUtils.createCircularReveal(revealView, width / 2, - height / 2, revealRadius, finalRadius); + Animator reveal = UiThreadCircularReveal.createCircularReveal(revealView, width / 2, + height / 2, revealRadius, finalRadius); reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); reveal.setDuration(revealDuration); reveal.setStartDelay(itemsAlphaStagger); @@ -782,4 +783,4 @@ public class LauncherStateTransitionAnimation { mStateAnimation = null; } } -}
\ No newline at end of file +} diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index dda9a166c..9271e8b15 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -146,7 +146,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected OnLongClickListener mLongClickListener; protected int mTouchSlop; - private int mPagingTouchSlop; private int mMaximumVelocity; protected int mPageLayoutWidthGap; protected int mPageLayoutHeightGap; @@ -172,14 +171,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // If true, modify alpha of neighboring pages as user scrolls left/right protected boolean mFadeInAdjacentScreens = false; - // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding - // to switch to a new page - protected boolean mUsePagingTouchSlop = true; - - // If true, the subclass should directly update scrollX itself in its computeScroll method - // (SmoothPagedView does this) - protected boolean mDeferScrollUpdate = false; - protected boolean mIsPageMoving = false; private boolean mWasInOverscroll = false; @@ -264,7 +255,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc final ViewConfiguration configuration = ViewConfiguration.get(getContext()); mTouchSlop = configuration.getScaledPagingTouchSlop(); - mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); mDensity = getResources().getDisplayMetrics().density; @@ -633,6 +623,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (mCurrentPage != getNextPage()) { AccessibilityEvent ev = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED); + ev.setScrollable(true); + ev.setScrollX(getScrollX()); + ev.setScrollY(getScrollY()); + ev.setMaxScrollX(mMaxScrollX); + ev.setMaxScrollY(0); sendAccessibilityEventUnchecked(ev); } @@ -853,7 +848,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc int offsetY = getViewportOffsetY(); // Update the viewport offsets - mViewport.offset(offsetX, offsetY); + mViewport.offset(offsetX, offsetY); final int startIndex = mIsRtl ? childCount - 1 : 0; final int endIndex = mIsRtl ? -1 : childCount; @@ -1434,25 +1429,20 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (!isTouchPointInViewportWithBuffer((int) x, (int) y)) return; final int xDiff = (int) Math.abs(x - mLastMotionX); - final int yDiff = (int) Math.abs(y - mLastMotionY); final int touchSlop = Math.round(touchSlopScale * mTouchSlop); - boolean xPaged = xDiff > mPagingTouchSlop; boolean xMoved = xDiff > touchSlop; - boolean yMoved = yDiff > touchSlop; - if (xMoved || xPaged || yMoved) { - if (mUsePagingTouchSlop ? xPaged : xMoved) { - // Scroll if the user moved far enough along the X axis - mTouchState = TOUCH_STATE_SCROLLING; - mTotalMotionX += Math.abs(mLastMotionX - x); - mLastMotionX = x; - mLastMotionXRemainder = 0; - mTouchX = getViewportOffsetX() + getScrollX(); - mSmoothingTime = System.nanoTime() / NANOTIME_DIV; - onScrollInteractionBegin(); - pageBeginMoving(); - } + if (xMoved) { + // Scroll if the user moved far enough along the X axis + mTouchState = TOUCH_STATE_SCROLLING; + mTotalMotionX += Math.abs(mLastMotionX - x); + mLastMotionX = x; + mLastMotionXRemainder = 0; + mTouchX = getViewportOffsetX() + getScrollX(); + mSmoothingTime = System.nanoTime() / NANOTIME_DIV; + onScrollInteractionBegin(); + pageBeginMoving(); } } @@ -1697,12 +1687,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (Math.abs(deltaX) >= 1.0f) { mTouchX += deltaX; mSmoothingTime = System.nanoTime() / NANOTIME_DIV; - if (!mDeferScrollUpdate) { - scrollBy((int) deltaX, 0); - if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX); - } else { - invalidate(); - } + scrollBy((int) deltaX, 0); mLastMotionX = x; mLastMotionXRemainder = deltaX - (int) deltaX; } else { @@ -2098,7 +2083,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc snapToPage(whichPage, delta, duration); } - protected void snapToPage(int whichPage) { + public void snapToPage(int whichPage) { snapToPage(whichPage, getPageSnapDuration()); } @@ -2347,6 +2332,15 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (getCurrentPage() > 0) { info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); } + info.setClassName(getClass().getName()); + + // Accessibility-wise, PagedView doesn't support long click, so disabling it. + // Besides disabling the accessibility long-click, this also prevents this view from getting + // accessibility focus. + info.setLongClickable(false); + if (Utilities.isLmpOrAbove()) { + info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); + } } @Override @@ -2360,7 +2354,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); - event.setScrollable(true); + event.setScrollable(getPageCount() > 1); } @Override diff --git a/src/com/android/launcher3/SmoothPagedView.java b/src/com/android/launcher3/SmoothPagedView.java deleted file mode 100644 index 0f9b23cda..000000000 --- a/src/com/android/launcher3/SmoothPagedView.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2008 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.content.Context; -import android.util.AttributeSet; -import android.view.animation.Interpolator; - -public abstract class SmoothPagedView extends PagedView { - private static final float SMOOTHING_SPEED = 0.75f; - private static final float SMOOTHING_CONSTANT = (float) (0.016 / Math.log(SMOOTHING_SPEED)); - - private float mBaseLineFlingVelocity; - private float mFlingVelocityInfluence; - - static final int DEFAULT_MODE = 0; - static final int X_LARGE_MODE = 1; - - int mScrollMode; - - private Interpolator mScrollInterpolator; - - public static class OvershootInterpolator implements Interpolator { - private static final float DEFAULT_TENSION = 1.3f; - private float mTension; - - public OvershootInterpolator() { - mTension = DEFAULT_TENSION; - } - - public void setDistance(int distance) { - mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION; - } - - public void disableSettle() { - mTension = 0.f; - } - - public float getInterpolation(float t) { - t -= 1.0f; - return t * t * ((mTension + 1) * t + mTension) + 1.0f; - } - } - - /** - * Used to inflate the Workspace from XML. - * - * @param context The application's context. - * @param attrs The attributes set containing the Workspace's customization values. - */ - public SmoothPagedView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - /** - * Used to inflate the Workspace from XML. - * - * @param context The application's context. - * @param attrs The attributes set containing the Workspace's customization values. - * @param defStyle Unused. - */ - public SmoothPagedView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - mUsePagingTouchSlop = false; - - // This means that we'll take care of updating the scroll parameter ourselves (we do it - // in computeScroll), we only do this in the OVERSHOOT_MODE, ie. on phones - mDeferScrollUpdate = mScrollMode != X_LARGE_MODE; - } - - protected int getScrollMode() { - return X_LARGE_MODE; - } - - /** - * Initializes various states for this workspace. - */ - @Override - protected void init() { - super.init(); - - mScrollMode = getScrollMode(); - if (mScrollMode == DEFAULT_MODE) { - mBaseLineFlingVelocity = 2500.0f; - mFlingVelocityInfluence = 0.4f; - mScrollInterpolator = new OvershootInterpolator(); - setDefaultInterpolator(mScrollInterpolator); - } - } - - @Override - protected void snapToDestination() { - if (mScrollMode == X_LARGE_MODE) { - super.snapToDestination(); - } else { - snapToPageWithVelocity(getPageNearestToCenterOfScreen(), 0); - } - } - - @Override - protected void snapToPageWithVelocity(int whichPage, int velocity) { - if (mScrollMode == X_LARGE_MODE) { - super.snapToPageWithVelocity(whichPage, velocity); - } else { - snapToPageWithVelocity(whichPage, 0, true); - } - } - - private void snapToPageWithVelocity(int whichPage, int velocity, boolean settle) { - // if (!mScroller.isFinished()) return; - - whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1)); - - final int screenDelta = Math.max(1, Math.abs(whichPage - mCurrentPage)); - final int newX = getScrollForPage(whichPage); - final int delta = newX - mUnboundedScrollX; - int duration = (screenDelta + 1) * 100; - - if (!mScroller.isFinished()) { - mScroller.abortAnimation(); - } - - if (settle) { - ((OvershootInterpolator) mScrollInterpolator).setDistance(screenDelta); - } else { - ((OvershootInterpolator) mScrollInterpolator).disableSettle(); - } - - velocity = Math.abs(velocity); - if (velocity > 0) { - duration += (duration / (velocity / mBaseLineFlingVelocity)) * mFlingVelocityInfluence; - } else { - duration += 100; - } - - snapToPage(whichPage, delta, duration); - } - - @Override - public void snapToPage(int whichPage) { - if (mScrollMode == X_LARGE_MODE) { - super.snapToPage(whichPage); - } else { - snapToPageWithVelocity(whichPage, 0, false); - } - } - - @Override - public void computeScroll() { - if (mScrollMode == X_LARGE_MODE) { - super.computeScroll(); - } else { - boolean scrollComputed = computeScrollHelper(); - - if (!scrollComputed && mTouchState == TOUCH_STATE_SCROLLING) { - final float now = System.nanoTime() / NANOTIME_DIV; - final float e = (float) Math.exp((now - mSmoothingTime) / SMOOTHING_CONSTANT); - - final float dx = mTouchX - mUnboundedScrollX; - scrollTo(Math.round(mUnboundedScrollX + dx * e), getScrollY()); - mSmoothingTime = now; - - // Keep generating points as long as we're more than 1px away from the target - if (dx > 1.f || dx < -1.f) { - invalidate(); - } - } - } - } -} diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 6c4b7207b..256eba020 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -127,6 +127,11 @@ public final class Utilities { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; } + public static boolean isLmpMR1OrAbove() { + // TODO(adamcohen): update to Build.VERSION_CODES.LOLLIPOP_MR1 once building against 22; + return Build.VERSION.SDK_INT >= 22; + } + static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) { byte[] data = c.getBlob(iconIndex); try { @@ -588,7 +593,6 @@ public final class Utilities { } public static final Comparator<ItemInfo> RANK_COMPARATOR = new Comparator<ItemInfo>() { - @Override public int compare(ItemInfo lhs, ItemInfo rhs) { return lhs.rank - rhs.rank; diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index d2c37d209..6d5affb59 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -64,8 +64,8 @@ import com.android.launcher3.Launcher.LauncherOverlay; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.UninstallDropTarget.UninstallSource; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; -import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource; +import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.Thunk; @@ -83,12 +83,14 @@ import java.util.concurrent.atomic.AtomicInteger; * Each page contains a number of icons, folders or widgets the user can * interact with. A workspace is meant to be used with a fixed width only. */ -public class Workspace extends SmoothPagedView +public class Workspace extends PagedView implements DropTarget, DragSource, DragScroller, View.OnTouchListener, DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener, Insettable, UninstallSource, AccessibilityDragSource { private static final String TAG = "Launcher.Workspace"; + private static boolean ENFORCE_DRAG_EVENT_ORDER = false; + protected static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400; protected static final int FADE_EMPTY_SCREEN_DURATION = 150; @@ -215,7 +217,6 @@ public class Workspace extends SmoothPagedView private FolderIcon mDragOverFolderIcon = null; private boolean mCreateUserFolderOnDrop = false; private boolean mAddToExistingFolderOnDrop = false; - private DropTarget.DragEnforcer mDragEnforcer; private float mMaxDistanceForFolderCreation; private final Canvas mCanvas = new Canvas(); @@ -301,9 +302,6 @@ public class Workspace extends SmoothPagedView mOutlineHelper = HolographicOutlineHelper.obtain(context); - mDragEnforcer = new DropTarget.DragEnforcer(context); - // With workspace, data is available straight from the get-go - mLauncher = (Launcher) context; mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this); final Resources res = getResources(); @@ -327,7 +325,6 @@ public class Workspace extends SmoothPagedView // Disable multitouch across the workspace/all apps/customize tray setMotionEventSplittingEnabled(true); - setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); } @Override @@ -372,22 +369,23 @@ public class Workspace extends SmoothPagedView return r; } + @Override public void onDragStart(final DragSource source, Object info, int dragAction) { + if (ENFORCE_DRAG_EVENT_ORDER) { + enfoceDragParity("onDragStart", 0, 0); + } + mIsDragOccuring = true; updateChildrenLayersEnabled(false); mLauncher.lockScreenOrientation(); mLauncher.onInteractionBegin(); // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging InstallShortcutReceiver.enableInstallQueue(); - post(new Runnable() { - @Override - public void run() { - if (mIsDragOccuring && mAddNewPageOnDrag) { - mDeferRemoveExtraEmptyScreen = false; - addExtraEmptyScreenOnDrag(); - } - } - }); + + if (mAddNewPageOnDrag) { + mDeferRemoveExtraEmptyScreen = false; + addExtraEmptyScreenOnDrag(); + } } public void setAddNewPageOnDrag(boolean addPage) { @@ -398,7 +396,12 @@ public class Workspace extends SmoothPagedView mDeferRemoveExtraEmptyScreen = true; } + @Override public void onDragEnd() { + if (ENFORCE_DRAG_EVENT_ORDER) { + enfoceDragParity("onDragEnd", 0, 0); + } + if (!mDeferRemoveExtraEmptyScreen) { removeExtraEmptyScreen(true, mDragSourceInternal != null); } @@ -458,11 +461,6 @@ public class Workspace extends SmoothPagedView } @Override - protected int getScrollMode() { - return SmoothPagedView.X_LARGE_MODE; - } - - @Override public void onChildViewAdded(View parent, View child) { if (!(child instanceof CellLayout)) { throw new IllegalArgumentException("A Workspace can only have CellLayout children."); @@ -736,6 +734,7 @@ public class Workspace extends SmoothPagedView fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION, onComplete, stripEmptyScreens); } else { + snapToPage(getNextPage(), 0); fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION, onComplete, stripEmptyScreens); } @@ -2019,14 +2018,9 @@ public class Workspace extends SmoothPagedView for (int i = numCustomPages(); i < total; i++) { updateAccessibilityFlags((CellLayout) getPageAt(i), i); } - if (mState == State.NORMAL) { - setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - } else { - setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - } } else { int accessible = mState == State.NORMAL ? - IMPORTANT_FOR_ACCESSIBILITY_NO : + IMPORTANT_FOR_ACCESSIBILITY_AUTO : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; setImportantForAccessibility(accessible); } @@ -2045,7 +2039,7 @@ public class Workspace extends SmoothPagedView page.setAccessibilityDelegate(mPagesAccessibilityDelegate); } else { int accessible = mState == State.NORMAL ? - IMPORTANT_FOR_ACCESSIBILITY_NO : + IMPORTANT_FOR_ACCESSIBILITY_AUTO : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); page.getShortcutsAndWidgets().setImportantForAccessibility(accessible); @@ -2827,8 +2821,12 @@ public class Workspace extends SmoothPagedView location[1] = vY - y; } + @Override public void onDragEnter(DragObject d) { - mDragEnforcer.onDragEnter(); + if (ENFORCE_DRAG_EVENT_ORDER) { + enfoceDragParity("onDragEnter", 1, 1); + } + mCreateUserFolderOnDrop = false; mAddToExistingFolderOnDrop = false; @@ -2881,8 +2879,11 @@ public class Workspace extends SmoothPagedView return null; } + @Override public void onDragExit(DragObject d) { - mDragEnforcer.onDragExit(); + if (ENFORCE_DRAG_EVENT_ORDER) { + enfoceDragParity("onDragExit", -1, 0); + } // Here we store the final page that will be dropped to, if the workspace in fact // receives the drop @@ -2914,6 +2915,24 @@ public class Workspace extends SmoothPagedView mLauncher.getDragLayer().hidePageHints(); } + private void enfoceDragParity(String event, int update, int expectedValue) { + enfoceDragParity(this, event, update, expectedValue); + for (int i = 0; i < getChildCount(); i++) { + enfoceDragParity(getChildAt(i), event, update, expectedValue); + } + } + + private void enfoceDragParity(View v, String event, int update, int expectedValue) { + Object tag = v.getTag(R.id.drag_event_parity); + int value = tag == null ? 0 : (Integer) tag; + value += update; + v.setTag(R.id.drag_event_parity, value); + + if (value != expectedValue) { + Log.e(TAG, event + ": Drag contract violated: " + value); + } + } + void setCurrentDropLayout(CellLayout layout) { if (mDragTargetLayout != null) { mDragTargetLayout.revertTempState(); diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 60f9ab347..d81f97f24 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -447,7 +447,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc @Override protected void onFixedBoundsUpdated() { // Update the number of items in the grid - LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = mLauncher.getDeviceProfile(); if (grid.updateAppsViewNumCols(getContext().getResources(), mFixedBounds.width())) { mNumAppsPerRow = grid.allAppsNumCols; diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java index e95fa325a..cc5add3b2 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java @@ -15,75 +15,34 @@ */ package com.android.launcher3.allapps; -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.LinearLayoutManager; 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.BaseRecyclerView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; -import com.android.launcher3.R; import com.android.launcher3.Utilities; import java.util.List; /** - * A RecyclerView with custom fastscroll support. This is the main container for the all apps - * icons. + * A RecyclerView with custom fast scroll support for the all apps view. */ public class AllAppsRecyclerView extends BaseRecyclerView { - /** - * The current scroll state of the recycler view. We use this in updateVerticalScrollbarBounds() - * 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. - */ - private static class ScrollPositionState { - // The index of the first visible row - int rowIndex; - // The offset of the first visible row - int rowTopOffset; - // The height of a given row (they are currently all the same height) - int rowHeight; - } - - private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f; - private AlphabeticalAppsList mApps; private int mNumAppsPerRow; private int mNumPredictedAppsPerRow; - private Drawable mScrollbar; - private Drawable mFastScrollerBg; - private Rect mTmpFastScrollerInvalidateRect = new Rect(); - private Rect mFastScrollerBounds = new Rect(); - private Rect mVerticalScrollbarBounds = new Rect(); - private boolean mDraggingFastScroller; - private String mFastScrollSectionName; - private Paint mFastScrollTextPaint; - private Rect mFastScrollTextBounds = new Rect(); - private float mFastScrollAlpha; private int mPredictionBarHeight; - private int mDownX; - private int mDownY; - private int mLastX; - private int mLastY; - private int mScrollbarWidth; private int mScrollbarMinHeight; - private int mScrollbarInset; + private Rect mBackgroundPadding = new Rect(); - private ScrollPositionState mScrollPosState = new ScrollPositionState(); private Launcher mLauncher; @@ -102,25 +61,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView { public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr); - mLauncher = (Launcher) context; - 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); - mScrollbarMinHeight = - res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_bar_min_height); - mScrollbarInset = - res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_scrubber_touch_inset); - setFastScrollerAlpha(getFastScrollerAlpha()); - setOverScrollMode(View.OVER_SCROLL_NEVER); } /** @@ -158,28 +99,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView { } /** - * Sets the fast scroller alpha. - */ - public void setFastScrollerAlpha(float alpha) { - mFastScrollAlpha = alpha; - invalidateFastScroller(mFastScrollerBounds); - } - - /** - * Gets the fast scroller alpha. - */ - public float getFastScrollerAlpha() { - return mFastScrollAlpha; - } - - /** - * Returns the scroll bar width. - */ - public int getScrollbarWidth() { - return mScrollbarWidth; - } - - /** * Scrolls this recycler view to the top. */ public void scrollToTop() { @@ -191,11 +110,11 @@ public class AllAppsRecyclerView extends BaseRecyclerView { */ public int getScrollPosition() { List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems(); - getCurScrollState(mScrollPosState, items); - if (mScrollPosState.rowIndex != -1) { + getCurScrollState(scrollPosState, items); + if (scrollPosState.rowIndex != -1) { int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight; - return getPaddingTop() + (mScrollPosState.rowIndex * mScrollPosState.rowHeight) + - predictionBarHeight - mScrollPosState.rowTopOffset; + return getPaddingTop() + (scrollPosState.rowIndex * scrollPosState.rowHeight) + + predictionBarHeight - scrollPosState.rowTopOffset; } return 0; } @@ -206,150 +125,11 @@ public class AllAppsRecyclerView extends BaseRecyclerView { addOnItemTouchListener(this); } - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - drawVerticalScrubber(canvas); - drawFastScrollerPopup(canvas); - } - - /** - * We intercept the touch handling only to support fast scrolling when initiated from the - * scroll bar. Otherwise, we fall back to the default RecyclerView touch handling. - */ - @Override - public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) { - return handleTouchEvent(ev); - } - - @Override - public void onTouchEvent(RecyclerView rv, MotionEvent ev) { - handleTouchEvent(ev); - } - - /** - * Handles the touch event and determines whether to show the fast scroller (or updates it if - * 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(); - switch (action) { - case MotionEvent.ACTION_DOWN: - // Keep track of the down positions - mDownX = mLastX = x; - mDownY = mLastY = y; - if (shouldStopScroll(ev)) { - stopScroll(); - } - 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) { - mLastX = x; - 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); - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mDraggingFastScroller = false; - animateFastScrollerVisibility(false); - break; - } - return mDraggingFastScroller; - } - - /** - * Animates the visibility of the fast scroller popup. - */ - private void animateFastScrollerVisibility(boolean visible) { - ObjectAnimator anim = ObjectAnimator.ofFloat(this, "fastScrollerAlpha", visible ? 1f : 0f); - anim.setDuration(visible ? 200 : 150); - anim.start(); - } - - /** - * Returns whether a given point is near the scrollbar. - */ - private boolean isPointNearScrollbar(int x, int y) { - // Check if we are scrolling - updateVerticalScrollbarBounds(); - mVerticalScrollbarBounds.inset(mScrollbarInset, mScrollbarInset); - return mVerticalScrollbarBounds.contains(x, y); - } - - /** - * Draws the fast scroller popup. - */ - private void drawFastScrollerPopup(Canvas canvas) { - if (mFastScrollAlpha > 0f && !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); - } - } - - /** - * Draws the vertical scrollbar. - */ - private void drawVerticalScrubber(Canvas canvas) { - updateVerticalScrollbarBounds(); - - // Draw the scroll bar - int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); - canvas.translate(mVerticalScrollbarBounds.left, mVerticalScrollbarBounds.top); - mScrollbar.setBounds(0, 0, mScrollbarWidth, mVerticalScrollbarBounds.height()); - mScrollbar.draw(canvas); - canvas.restoreToCount(restoreCount); - } - - /** - * Invalidates the fast scroller popup. - */ - private void invalidateFastScroller(Rect bounds) { - invalidate(bounds.left, bounds.top, bounds.right, bounds.bottom); - } - /** * Maps the touch (from 0..1) to the adapter position that should be visible. */ - private String scrollToPositionAtProgress(float touchFraction) { + @Override + public String scrollToPositionAtProgress(float touchFraction) { // Ensure that we have any sections List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections = mApps.getFastScrollerSections(); @@ -393,27 +173,60 @@ public class AllAppsRecyclerView extends BaseRecyclerView { return lastScrollSection.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; + } + return appIndex; + } + + /** + * 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. */ - private void updateVerticalScrollbarBounds() { + @Override + public void updateVerticalScrollbarBounds() { List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems(); - // Skip early if there are no items + // Skip early if there are no items. if (items.isEmpty()) { - mVerticalScrollbarBounds.setEmpty(); + verticalScrollbarBounds.setEmpty(); return; } // Find the index and height of the first visible row (all rows have the same height) - int x; - int y; + int x, y; int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight; int rowCount = getNumRows(); - getCurScrollState(mScrollPosState, items); - if (mScrollPosState.rowIndex != -1) { + getCurScrollState(scrollPosState, items); + if (scrollPosState.rowIndex != -1) { int height = getHeight() - getPaddingTop() - getPaddingBottom(); - int totalScrollHeight = rowCount * mScrollPosState.rowHeight + predictionBarHeight; + int totalScrollHeight = rowCount * scrollPosState.rowHeight + predictionBarHeight; if (totalScrollHeight > height) { int scrollbarHeight = Math.max(mScrollbarMinHeight, (int) (height / ((float) totalScrollHeight / height))); @@ -422,78 +235,23 @@ public class AllAppsRecyclerView extends BaseRecyclerView { if (Utilities.isRtl(getResources())) { x = mBackgroundPadding.left; } else { - x = getWidth() - mBackgroundPadding.right - mScrollbarWidth; + 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 = (mScrollPosState.rowIndex * mScrollPosState.rowHeight) + predictionBarHeight - - mScrollPosState.rowTopOffset; + y = (scrollPosState.rowIndex * scrollPosState.rowHeight) + predictionBarHeight + - scrollPosState.rowTopOffset; y = getPaddingTop() + (int) (((float) (getPaddingTop() + y) / availableY) * availableScrollY); - mVerticalScrollbarBounds.set(x, y, x + mScrollbarWidth, y + scrollbarHeight); + verticalScrollbarBounds.set(x, y, x + getScrollbarWidth(), y + scrollbarHeight); return; } } - mVerticalScrollbarBounds.setEmpty(); - } - - /** - * 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 + getScrollBarSize(); - } else { - x = getWidth() - getPaddingRight() - getScrollBarSize() - 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(); - } - } - - /** - * 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; - } - return appIndex; - } - - /** - * 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; + verticalScrollbarBounds.setEmpty(); } /** diff --git a/src/com/android/launcher3/util/RevealOutlineProvider.java b/src/com/android/launcher3/util/RevealOutlineProvider.java new file mode 100644 index 000000000..0db3984f8 --- /dev/null +++ b/src/com/android/launcher3/util/RevealOutlineProvider.java @@ -0,0 +1,49 @@ +package com.android.launcher3.util; + +import android.annotation.TargetApi; +import android.graphics.Outline; +import android.graphics.Rect; +import android.os.Build; +import android.view.View; +import android.view.ViewOutlineProvider; + +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class RevealOutlineProvider extends ViewOutlineProvider { + + private int mCenterX; + private int mCenterY; + private float mRadius0; + private float mRadius1; + private int mCurrentRadius; + + private final Rect mOval; + + /** + * @param x reveal center x + * @param y reveal center y + * @param r0 initial radius + * @param r1 final radius + */ + public RevealOutlineProvider(int x, int y, float r0, float r1) { + mCenterX = x; + mCenterY = y; + mRadius0 = r0; + mRadius1 = r1; + + mOval = new Rect(); + } + + public void setProgress(float progress) { + mCurrentRadius = (int) ((1 - progress) * mRadius0 + progress * mRadius1); + + mOval.left = mCenterX - mCurrentRadius; + mOval.top = mCenterY - mCurrentRadius; + mOval.right = mCenterX + mCurrentRadius; + mOval.bottom = mCenterY + mCurrentRadius; + } + + @Override + public void getOutline(View v, Outline outline) { + outline.setRoundRect(mOval, mCurrentRadius); + } +} diff --git a/src/com/android/launcher3/util/UiThreadCircularReveal.java b/src/com/android/launcher3/util/UiThreadCircularReveal.java new file mode 100644 index 000000000..c7324fb1b --- /dev/null +++ b/src/com/android/launcher3/util/UiThreadCircularReveal.java @@ -0,0 +1,55 @@ +package com.android.launcher3.util; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.TargetApi; +import android.os.Build; +import android.view.View; +import android.view.ViewOutlineProvider; + +import com.android.launcher3.Utilities; + +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class UiThreadCircularReveal { + + public static ValueAnimator createCircularReveal(View v, int x, int y, float r0, float r1) { + ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); + + final View revealView = v; + final RevealOutlineProvider outlineProvider = new RevealOutlineProvider(x, y, r0, r1); + final ViewOutlineProvider originalProvider = revealView.getOutlineProvider(); + final float elevation = v.getElevation(); + + va.addListener(new AnimatorListenerAdapter() { + public void onAnimationStart(Animator animation) { + revealView.setOutlineProvider(outlineProvider); + revealView.setClipToOutline(true); + revealView.setTranslationZ(-elevation); + } + + public void onAnimationEnd(Animator animation) { + revealView.setOutlineProvider(originalProvider); + revealView.setClipToOutline(false); + revealView.setTranslationZ(0); + } + + }); + + va.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator arg0) { + float progress = arg0.getAnimatedFraction(); + outlineProvider.setProgress(progress); + if (Utilities.isLmpMR1OrAbove()) { + revealView.invalidateOutline(); + } else { + // On L, a bug requires calling a full view invalidate. + revealView.invalidate(); + } + } + }); + return va; + } +} diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java index 05e842e71..11c2107f2 100644 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ b/src/com/android/launcher3/widget/WidgetsContainerView.java @@ -35,7 +35,6 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragController; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget.DragObject; -import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.Folder; import com.android.launcher3.IconCache; import com.android.launcher3.ItemInfo; @@ -46,6 +45,7 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.Workspace; +import com.android.launcher3.model.WidgetsModel; /** * The widgets list view container. @@ -56,8 +56,6 @@ public class WidgetsContainerView extends BaseContainerView private static final String TAG = "WidgetsContainerView"; private static final boolean DEBUG = false; - private static final int SPRING_MODE_DELAY_MS = 150; - /* Coefficient multiplied to the screen height for preloading widgets. */ private static final int PRELOAD_SCREEN_HEIGHT_MULTIPLE = 1; @@ -67,7 +65,7 @@ public class WidgetsContainerView extends BaseContainerView private IconCache mIconCache; /* Recycler view related member variables */ - private RecyclerView mView; + private WidgetsRecyclerView mView; private WidgetsListAdapter mAdapter; /* Touch handling related member variables. */ @@ -102,7 +100,7 @@ public class WidgetsContainerView extends BaseContainerView @Override protected void onFinishInflate() { - mView = (RecyclerView) findViewById(R.id.widgets_list_view); + mView = (WidgetsRecyclerView) findViewById(R.id.widgets_list_view); mView.setAdapter(mAdapter); // This extends the layout space so that preloading happen for the {@link RecyclerView} @@ -186,18 +184,11 @@ public class WidgetsContainerView extends BaseContainerView Log.e(TAG, "Unexpected dragging view: " + v); } - // We delay entering spring-loaded mode slightly to make sure the UI - // thread is free of any work. - postDelayed(new Runnable() { - @Override - public void run() { - // We don't enter spring-loaded mode if the drag has been cancelled - if (mLauncher.getDragController().isDragging()) { - // Go into spring loaded mode (must happen before we startDrag()) - mLauncher.enterSpringLoadedDragMode(); - } - } - }, SPRING_MODE_DELAY_MS); + // We don't enter spring-loaded mode if the drag has been cancelled + if (mLauncher.getDragController().isDragging()) { + // Go into spring loaded mode (must happen before we startDrag()) + mLauncher.enterSpringLoadedDragMode(); + } return true; } @@ -360,6 +351,7 @@ public class WidgetsContainerView extends BaseContainerView * Initialize the widget data model. */ public void addWidgets(WidgetsModel model) { + mView.setWidgets(model); mAdapter.setWidgetsModel(model); mAdapter.notifyDataSetChanged(); } diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java index 7439a44f8..e82c0a631 100644 --- a/src/com/android/launcher3/widget/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java @@ -32,7 +32,6 @@ import android.widget.LinearLayout; import com.android.launcher3.BubbleTextView; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.IconCache; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetProviderInfo; diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java index 31ef5d6fc..bef255908 100644 --- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java +++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java @@ -17,14 +17,23 @@ package com.android.launcher3.widget; import android.content.Context; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; +import android.view.MotionEvent; + import com.android.launcher3.BaseRecyclerView; +import com.android.launcher3.model.WidgetsModel; /** * The widgets recycler view. */ public class WidgetsRecyclerView extends BaseRecyclerView { + private WidgetsModel mWidgets; + private Rect mBackgroundPadding = new Rect(); + public WidgetsRecyclerView(Context context) { this(context, null); } @@ -37,4 +46,67 @@ public class WidgetsRecyclerView extends BaseRecyclerView { super(context, attrs, defStyleAttr); } + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + addOnItemTouchListener(this); + } + + public void updateBackgroundPadding(Drawable background) { + background.getPadding(mBackgroundPadding); + } + + /** + * Sets the widget model in this view, used to determine the fast scroll position. + */ + public void setWidgets(WidgetsModel widgets) { + mWidgets = widgets; + } + + /** + * 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 + return ""; + } + + /** + * Updates the bounds for the scrollbar. + */ + @Override + public void updateVerticalScrollbarBounds() { + int rowCount = mWidgets.getPackageSize(); + + // Skip early if there are no items. + if (rowCount == 0) { + verticalScrollbarBounds.setEmpty(); + return; + } + + int x, y; + getCurScrollState(scrollPosState); + if (scrollPosState.rowIndex < 0) { + verticalScrollbarBounds.setEmpty(); + } + // TODO + } + + /** + * Returns the current scroll state. + */ + private void getCurScrollState(ScrollPositionState stateOut) { + stateOut.rowIndex = -1; + stateOut.rowTopOffset = -1; + stateOut.rowHeight = -1; + + int rowCount = mWidgets.getPackageSize(); + + // Return early if there are no items + if (rowCount == 0) { + return; + } + // TODO + } }
\ No newline at end of file |