408a54f2e7
Add a new screen ID for the second extra empty page and add that new screen as well when the existing extra empty page is added so that users can put items on both sides of Workspace. Test: manual Bug: 196376162 Change-Id: I0b4f2e818407a10d8a7c032788a7bd7a61267779
307 lines
12 KiB
Java
307 lines
12 KiB
Java
package com.android.launcher3.util;
|
|
|
|
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
|
|
|
import android.app.WallpaperManager;
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.os.Handler;
|
|
import android.os.IBinder;
|
|
import android.os.Message;
|
|
import android.os.SystemClock;
|
|
import android.util.Log;
|
|
import android.view.animation.Interpolator;
|
|
|
|
import com.android.launcher3.Utilities;
|
|
import com.android.launcher3.Workspace;
|
|
import com.android.launcher3.anim.Interpolators;
|
|
|
|
/**
|
|
* Utility class to handle wallpaper scrolling along with workspace.
|
|
*/
|
|
public class WallpaperOffsetInterpolator extends BroadcastReceiver {
|
|
|
|
private static final int[] sTempInt = new int[2];
|
|
private static final String TAG = "WPOffsetInterpolator";
|
|
private static final int ANIMATION_DURATION = 250;
|
|
|
|
// Don't use all the wallpaper for parallax until you have at least this many pages
|
|
private static final int MIN_PARALLAX_PAGE_SPAN = 4;
|
|
|
|
private final Workspace mWorkspace;
|
|
private final boolean mIsRtl;
|
|
private final Handler mHandler;
|
|
|
|
private boolean mRegistered = false;
|
|
private IBinder mWindowToken;
|
|
private boolean mWallpaperIsLiveWallpaper;
|
|
|
|
private boolean mLockedToDefaultPage;
|
|
private int mNumScreens;
|
|
|
|
public WallpaperOffsetInterpolator(Workspace workspace) {
|
|
mWorkspace = workspace;
|
|
mIsRtl = Utilities.isRtl(workspace.getResources());
|
|
mHandler = new OffsetHandler(workspace.getContext());
|
|
}
|
|
|
|
/**
|
|
* Locks the wallpaper offset to the offset in the default state of Launcher.
|
|
*/
|
|
public void setLockToDefaultPage(boolean lockToDefaultPage) {
|
|
mLockedToDefaultPage = lockToDefaultPage;
|
|
}
|
|
|
|
public boolean isLockedToDefaultPage() {
|
|
return mLockedToDefaultPage;
|
|
}
|
|
|
|
/**
|
|
* Computes the wallpaper offset as an int ratio (out[0] / out[1])
|
|
*
|
|
* TODO: do different behavior if it's a live wallpaper?
|
|
*/
|
|
private void wallpaperOffsetForScroll(int scroll, int numScrollableScreens, final int[] out) {
|
|
out[1] = 1;
|
|
|
|
// To match the default wallpaper behavior in the system, we default to either the left
|
|
// or right edge on initialization
|
|
if (mLockedToDefaultPage || numScrollableScreens <= 1) {
|
|
out[0] = mIsRtl ? 1 : 0;
|
|
return;
|
|
}
|
|
|
|
// Distribute the wallpaper parallax over a minimum of MIN_PARALLAX_PAGE_SPAN workspace
|
|
// screens, not including the custom screen, and empty screens (if > MIN_PARALLAX_PAGE_SPAN)
|
|
int numScreensForWallpaperParallax = mWallpaperIsLiveWallpaper ? numScrollableScreens :
|
|
Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollableScreens);
|
|
|
|
// Offset by the custom screen
|
|
|
|
// Don't confuse screens & pages in this function. In a phone UI, we often use screens &
|
|
// pages interchangeably. However, in a n-panels UI, where n > 1, the screen in this class
|
|
// means the scrollable screen. Each screen can consist of at most n panels.
|
|
// Each panel has at most 1 page. Take 5 pages in 2 panels UI as an example, the Workspace
|
|
// looks as follow:
|
|
//
|
|
// S: scrollable screen, P: page, <E>: empty
|
|
// S0 S1 S2
|
|
// _______ _______ ________
|
|
// |P0|P1| |P2|P3| |P4|<E>|
|
|
// ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯¯
|
|
int endIndex = getNumPagesExcludingEmpty() - 1;
|
|
final int leftPageIndex = mIsRtl ? endIndex : 0;
|
|
final int rightPageIndex = mIsRtl ? 0 : endIndex;
|
|
|
|
// Calculate the scroll range
|
|
int leftPageScrollX = mWorkspace.getScrollForPage(leftPageIndex);
|
|
int rightPageScrollX = mWorkspace.getScrollForPage(rightPageIndex);
|
|
int scrollRange = rightPageScrollX - leftPageScrollX;
|
|
if (scrollRange <= 0) {
|
|
out[0] = 0;
|
|
return;
|
|
}
|
|
|
|
// Sometimes the left parameter of the pages is animated during a layout transition;
|
|
// this parameter offsets it to keep the wallpaper from animating as well
|
|
int adjustedScroll = scroll - leftPageScrollX -
|
|
mWorkspace.getLayoutTransitionOffsetForPage(0);
|
|
adjustedScroll = Utilities.boundToRange(adjustedScroll, 0, scrollRange);
|
|
out[1] = (numScreensForWallpaperParallax - 1) * scrollRange;
|
|
|
|
// The offset is now distributed 0..1 between the left and right pages that we care about,
|
|
// so we just map that between the pages that we are using for parallax
|
|
int rtlOffset = 0;
|
|
if (mIsRtl) {
|
|
// In RTL, the pages are right aligned, so adjust the offset from the end
|
|
rtlOffset = out[1] - (numScrollableScreens - 1) * scrollRange;
|
|
}
|
|
out[0] = rtlOffset + adjustedScroll * (numScrollableScreens - 1);
|
|
}
|
|
|
|
public float wallpaperOffsetForScroll(int scroll) {
|
|
wallpaperOffsetForScroll(scroll, getNumScrollableScreensExcludingEmpty(), sTempInt);
|
|
return ((float) sTempInt[0]) / sTempInt[1];
|
|
}
|
|
|
|
/**
|
|
* Returns the number of screens that can be scrolled.
|
|
*
|
|
* <p>In an usual phone UI, the number of scrollable screens is equal to the number of
|
|
* CellLayouts because each screen has exactly 1 CellLayout.
|
|
*
|
|
* <p>In a n-panels UI, a screen shows n panels. Each panel has at most 1 CellLayout. Take
|
|
* 2-panels UI as an example: let's say there are 5 CellLayouts in the Workspace. the number of
|
|
* scrollable screens will be 3 = ⌈5 / 2⌉.
|
|
*/
|
|
private int getNumScrollableScreensExcludingEmpty() {
|
|
float numOfPages = getNumPagesExcludingEmpty();
|
|
return (int) Math.ceil(numOfPages / mWorkspace.getPanelCount());
|
|
}
|
|
|
|
/**
|
|
* Returns the number of non-empty pages in the Workspace.
|
|
*
|
|
* <p>If a user starts dragging on the rightmost (or leftmost in RTL), an empty CellLayout is
|
|
* added to the Workspace. This empty CellLayout add as a hover-over target for adding a new
|
|
* page. To avoid janky motion effect, we ignore this empty CellLayout.
|
|
*/
|
|
private int getNumPagesExcludingEmpty() {
|
|
int numOfPages = mWorkspace.getChildCount();
|
|
if (numOfPages >= MIN_PARALLAX_PAGE_SPAN && mWorkspace.hasExtraEmptyScreens()) {
|
|
return numOfPages - mWorkspace.getPanelCount();
|
|
} else {
|
|
return numOfPages;
|
|
}
|
|
}
|
|
|
|
public void syncWithScroll() {
|
|
int numScreens = getNumScrollableScreensExcludingEmpty();
|
|
wallpaperOffsetForScroll(mWorkspace.getScrollX(), numScreens, sTempInt);
|
|
Message msg = Message.obtain(mHandler, MSG_UPDATE_OFFSET, sTempInt[0], sTempInt[1],
|
|
mWindowToken);
|
|
if (numScreens != mNumScreens) {
|
|
if (mNumScreens > 0) {
|
|
// Don't animate if we're going from 0 screens
|
|
msg.what = MSG_START_ANIMATION;
|
|
}
|
|
mNumScreens = numScreens;
|
|
updateOffset();
|
|
}
|
|
msg.sendToTarget();
|
|
}
|
|
|
|
/** Returns the number of pages used for the wallpaper parallax. */
|
|
public int getNumPagesForWallpaperParallax() {
|
|
if (mWallpaperIsLiveWallpaper) {
|
|
return mNumScreens;
|
|
} else {
|
|
return Math.max(MIN_PARALLAX_PAGE_SPAN, mNumScreens);
|
|
}
|
|
}
|
|
|
|
private void updateOffset() {
|
|
Message.obtain(mHandler, MSG_SET_NUM_PARALLAX, getNumPagesForWallpaperParallax(), 0,
|
|
mWindowToken).sendToTarget();
|
|
}
|
|
|
|
public void jumpToFinal() {
|
|
Message.obtain(mHandler, MSG_JUMP_TO_FINAL, mWindowToken).sendToTarget();
|
|
}
|
|
|
|
public void setWindowToken(IBinder token) {
|
|
mWindowToken = token;
|
|
if (mWindowToken == null && mRegistered) {
|
|
mWorkspace.getContext().unregisterReceiver(this);
|
|
mRegistered = false;
|
|
} else if (mWindowToken != null && !mRegistered) {
|
|
mWorkspace.getContext()
|
|
.registerReceiver(this, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
|
|
onReceive(mWorkspace.getContext(), null);
|
|
mRegistered = true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
mWallpaperIsLiveWallpaper =
|
|
WallpaperManager.getInstance(mWorkspace.getContext()).getWallpaperInfo() != null;
|
|
updateOffset();
|
|
}
|
|
|
|
private static final int MSG_START_ANIMATION = 1;
|
|
private static final int MSG_UPDATE_OFFSET = 2;
|
|
private static final int MSG_APPLY_OFFSET = 3;
|
|
private static final int MSG_SET_NUM_PARALLAX = 4;
|
|
private static final int MSG_JUMP_TO_FINAL = 5;
|
|
|
|
private static class OffsetHandler extends Handler {
|
|
|
|
private final Interpolator mInterpolator;
|
|
private final WallpaperManager mWM;
|
|
|
|
private float mCurrentOffset = 0.5f; // to force an initial update
|
|
private boolean mAnimating;
|
|
private long mAnimationStartTime;
|
|
private float mAnimationStartOffset;
|
|
|
|
private float mFinalOffset;
|
|
private float mOffsetX;
|
|
|
|
public OffsetHandler(Context context) {
|
|
super(UI_HELPER_EXECUTOR.getLooper());
|
|
mInterpolator = Interpolators.DEACCEL_1_5;
|
|
mWM = WallpaperManager.getInstance(context);
|
|
}
|
|
|
|
@Override
|
|
public void handleMessage(Message msg) {
|
|
final IBinder token = (IBinder) msg.obj;
|
|
if (token == null) {
|
|
return;
|
|
}
|
|
|
|
switch (msg.what) {
|
|
case MSG_START_ANIMATION: {
|
|
mAnimating = true;
|
|
mAnimationStartOffset = mCurrentOffset;
|
|
mAnimationStartTime = msg.getWhen();
|
|
// Follow through
|
|
}
|
|
case MSG_UPDATE_OFFSET:
|
|
mFinalOffset = ((float) msg.arg1) / msg.arg2;
|
|
// Follow through
|
|
case MSG_APPLY_OFFSET: {
|
|
float oldOffset = mCurrentOffset;
|
|
if (mAnimating) {
|
|
long durationSinceAnimation = SystemClock.uptimeMillis()
|
|
- mAnimationStartTime;
|
|
float t0 = durationSinceAnimation / (float) ANIMATION_DURATION;
|
|
float t1 = mInterpolator.getInterpolation(t0);
|
|
mCurrentOffset = mAnimationStartOffset +
|
|
(mFinalOffset - mAnimationStartOffset) * t1;
|
|
mAnimating = durationSinceAnimation < ANIMATION_DURATION;
|
|
} else {
|
|
mCurrentOffset = mFinalOffset;
|
|
}
|
|
|
|
if (Float.compare(mCurrentOffset, oldOffset) != 0) {
|
|
setOffsetSafely(token);
|
|
// Force the wallpaper offset steps to be set again, because another app
|
|
// might have changed them
|
|
mWM.setWallpaperOffsetSteps(mOffsetX, 1.0f);
|
|
}
|
|
if (mAnimating) {
|
|
// If we are animating, keep updating the offset
|
|
Message.obtain(this, MSG_APPLY_OFFSET, token).sendToTarget();
|
|
}
|
|
return;
|
|
}
|
|
case MSG_SET_NUM_PARALLAX: {
|
|
// Set wallpaper offset steps (1 / (number of screens - 1))
|
|
mOffsetX = 1.0f / (msg.arg1 - 1);
|
|
mWM.setWallpaperOffsetSteps(mOffsetX, 1.0f);
|
|
return;
|
|
}
|
|
case MSG_JUMP_TO_FINAL: {
|
|
if (Float.compare(mCurrentOffset, mFinalOffset) != 0) {
|
|
mCurrentOffset = mFinalOffset;
|
|
setOffsetSafely(token);
|
|
}
|
|
mAnimating = false;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void setOffsetSafely(IBinder token) {
|
|
try {
|
|
mWM.setWallpaperOffsets(token, mCurrentOffset, 0.5f);
|
|
} catch (IllegalArgumentException e) {
|
|
Log.e(TAG, "Error updating wallpaper offset: " + e);
|
|
}
|
|
}
|
|
}
|
|
} |