Launcher3源码分析(Workspace)

时间:2021-10-21 09:19:21

Workspace主要功能:完成多个屏幕的以及壁纸的显示,多个屏幕之间的切换和壁纸的添加。

    /**
* Used to inflate the Workspace from XML.
*/

public Workspace(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
//获取绘制轮廓的辅助类对象
mOutlineHelper = HolographicOutlineHelper.obtain(context);

mLauncher = (Launcher) context;
mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this);
final Resources res = getResources();
DeviceProfile grid = mLauncher.getDeviceProfile();
mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
mFadeInAdjacentScreens = false;
//获取Wallpaper管理器
mWallpaperManager = WallpaperManager.getInstance(context);
//获取自定义属性
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.Workspace, defStyle, 0);
//在allapp 应用程序菜单里拖动app时workspace的缩放比例
mSpringLoadedShrinkFactor =
res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
//长按桌面进入预览模式时的缩放比例
mOverviewModeShrinkFactor = grid.getOverviewModeScale(mIsRtl);
//开机时的屏幕
mOriginalDefaultPage = mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
a.recycle();
//监听view层次的变化
setOnHierarchyChangeListener(this);
//打开触摸反馈
setHapticFeedbackEnabled(false);
//初始化workspace
initWorkspace();

// Disable multitouch across the workspace/all apps/customize tray
setMotionEventSplittingEnabled(true);
}

初始化workspace

/**
* Initializes various states for this workspace.
*/

protected void initWorkspace() {
//默认页
mCurrentPage = mDefaultPage;
LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = mLauncher.getDeviceProfile();
//保存应用图片的缓存
mIconCache = app.getIconCache();
setWillNotDraw(false);
setClipChildren(false);
setClipToPadding(false);
//设置子view绘图缓存开始
setChildrenDrawnWithCacheEnabled(true);

setMinScale(mOverviewModeShrinkFactor);
setupLayoutTransition();
//wallpaper偏移
mWallpaperOffset = new WallpaperOffsetInterpolator();
//获取屏幕大小
Display display = mLauncher.getWindowManager().getDefaultDisplay();
display.getSize(mDisplaySize);

mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);

// Set the wallpaper dimensions when Launcher starts up
setWallpaperDimension();

setEdgeGlowColor(getResources().getColor(R.color.workspace_edge_effect_color));
}

workspace实现了DragSource和Dragtarget,说明它即是一个拖动的容器也是一个拖动的源,分析开始拖动方法:

public void startDrag(CellLayout.CellInfo cellInfo){
startDrag(cellInfo,false);
}
public void startDrag(CellLayout.CellInfo cellInfo,boolean accessible){
View child = cellInfo.cell;

// Make sure the drag was started by a long press as opposed to a long click.
if (!child.isInTouchMode()) {
return;
}

mDragInfo = cellInfo;
//原位置的item设置为不可见
child.setVisibility(INVISIBLE);
CellLayout layout = (CellLayout) child.getParent().getParent();
layout.prepareChildForDrag(child);

beginDragShared(child, this, accessible);
}

public void beginDragShared(View child, DragSource source,boolean accessible){
beginDragShared(child, new Point(), source, accessible);
}

public void beginDragShared(View child, Point relativeTouchPos,DragSource source, boolean accessible){
child.clearFocus();
child.setPressed(false);
//当item拖动时跟随着的背景图
mDragOutline = createDragOutline(child,DRAG_BITMAP_PADDING);
//开始拖动
mLauncher.onDragStarted(child);
// The drag bitmap follows the touch point around on the screen
AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);

final Bitmap b = createDragBitmap(child,padding);

float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
int dragLayerX = Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
int dragLayerY = Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2 - padding.get() / 2);

DeviceProfile grid = mLauncher.getDeviceProfile();
Point dragVisualizeOffset = null;
Rect dragRect = null;
if (child instanceof BubbleTextView) {
BubbleTextView icon = (BubbleTextView) child;
int iconSize = grid.iconSizePx;
int top = child.getPaddingTop();
int left = (bmpWidth - iconSize) / 2;
int right = left + iconSize;
int bottom = top + iconSize;
if (icon.isLayoutHorizontal()) {
// If the layout is horizontal, then if we are just picking up the icon, then just
// use the child position since the icon is top-left aligned. Otherwise, offset
// the drag layer position horizontally so that the icon is under the current
// touch position.
if (icon.getIcon().getBounds().contains(relativeTouchPos.x, relativeTouchPos.y)) {
dragLayerX = Math.round(mTempXY[0]);
} else {
dragLayerX = Math.round(mTempXY[0] + relativeTouchPos.x - (bmpWidth / 2));
}
}
dragLayerY += top;
// Note: The drag region is used to calculate drag layer offsets, but the
// dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2);
dragRect = new Rect(left, top, right, bottom);
} else if (child instanceof FolderIcon) {
int previewSize = grid.folderIconSizePx;
dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
}

// Clear the pressed state if necessary
if (child instanceof BubbleTextView) {
BubbleTextView icon = (BubbleTextView) child;
icon.clearPressedBackground();
}

if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
String msg = "Drag started with a view that has no tag set. This "
+ "will cause a crash (issue 11627249) down the line. "
+ "View: " + child + " tag: " + child.getTag();
throw new IllegalStateException(msg);
}

//调用DragController的startDrag方法
DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, accessible);
dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());

if (child.getParent() instanceof ShortcutAndWidgetContainer) {
mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
}

b.recycle();

}

workspace主要负责左右滑动,滑动功能主要分两步:1、在onInterceptTouchEvent中进行拦截。2、在onTouchEvent中进行滑动

public boolean onTouch(View v, MotionEvent event){
return (worspaceInModalSate() || !isFinishedSwitchingState()) || (!workspaceInModalState() && indexofChild(V) != mCurrentPage);
}

public boolean onInterceptTounchEvent(MotionEvent ev){
switch(ev.getAction() & MptionEvent.ACTION_MASK){
case MotionEvcent.ACTION_DOWN:
mXDown = ev.getX();
mYDown = ev.getY();
//记录按下的时间
mTouchDownTime = system.currentTimeMillis();
break;
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_up:
if(mTouvhState == TOUCH_STATE_RESET){
final CellLayout currentPage == (CellLayout)getChildAt(mCurrentPage);
if(currentPage != null){
onWallpaperTap(ev);
}
}
//调用父类的onInterceptTouchEvent(ev);主要功能是在onTouchEvent()方法之前处理touch事件。包括:down、up、move事件。
return super.onInterceptTouchEvent(ev);
}
}

重写了父类的computeScroll();主要功能是计算拖动的位移量、更新背景、设置要显示的屏幕。

public void computeScroll(){
super.computeScroll();
mWallpaperOffset.syncWithScroll();
}

public void syncWithScroll(){
float offset = wallpaperOffsetForCurrentScroll();
mWallpaperOffset.setFinalX(offset);
updateOffset(true);
}
//处理壁纸移动的类
class WallpaperOffsetInterpolator implements Choreographer.FrameCallback {
float mFinalOffset = 0.0f;
float mCurrentOffset = 0.5f; // to force an initial update
boolean mWaitingForUpdate;
Choreographer mChoreographer;
Interpolator mInterpolator;
boolean mAnimating;
long mAnimationStartTime;
float mAnimationStartOffset;
private final int ANIMATION_DURATION = 250;
// Don't use all the wallpaper for parallax until you have at least this many pages
private final int MIN_PARALLAX_PAGE_SPAN = 3;
int mNumScreens;

public WallpaperOffsetInterpolator() {
mChoreographer = Choreographer.getInstance();
mInterpolator = new DecelerateInterpolator(1.5f);
}

@Override
public void doFrame(long frameTimeNanos) {
//调用了updateOffset方法,这个方法即是处理壁纸的位移的。
updateOffset(false);
}

private void updateOffset(boolean force) {
if (mWaitingForUpdate || force) {
mWaitingForUpdate = false;
if (computeScrollOffset() && mWindowToken != null) {
try {
mWallpaperManager.setWallpaperOffsets(mWindowToken,
mWallpaperOffset.getCurrX(), 0.5f);
setWallpaperOffsetSteps();
} catch (IllegalArgumentException e) {
Log.e(TAG, "Error updating wallpaper offset: " + e);
}
}
}
}

public boolean computeScrollOffset() {
final float oldOffset = mCurrentOffset;
if (mAnimating) {
long durationSinceAnimation = System.currentTimeMillis() - 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 (Math.abs(mCurrentOffset - mFinalOffset) > 0.0000001f) {
scheduleUpdate();
}
if (Math.abs(oldOffset - mCurrentOffset) > 0.0000001f) {
return true;
}
return false;
}

private float wallpaperOffsetForCurrentScroll() {
// TODO: do different behavior if it's a live wallpaper?
// Don't use up all the wallpaper parallax until you have at least
// MIN_PARALLAX_PAGE_SPAN pages
int numScrollingPages = getNumScreensExcludingEmptyAndCustom();
int parallaxPageSpan;
if (mWallpaperIsLiveWallpaper) {
parallaxPageSpan = numScrollingPages - 1;
} else {
parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1);
}
mNumPagesForWallpaperParallax = parallaxPageSpan;

if (getChildCount() <= 1) {
if (mIsRtl) {
return 1 - 1.0f/mNumPagesForWallpaperParallax;
}
return 0;
}

// Exclude the leftmost page
int emptyExtraPages = numEmptyScreensToIgnore();
int firstIndex = numCustomPages();
// Exclude the last extra empty screen (if we have > MIN_PARALLAX_PAGE_SPAN pages)
int lastIndex = getChildCount() - 1 - emptyExtraPages;
if (mIsRtl) {
int temp = firstIndex;
firstIndex = lastIndex;
lastIndex = temp;
}

int firstPageScrollX = getScrollForPage(firstIndex);
int scrollRange = getScrollForPage(lastIndex) - firstPageScrollX;
if (scrollRange == 0) {
return 0;
} else {
// 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 =
getScrollX() - firstPageScrollX - getLayoutTransitionOffsetForPage(0);
float offset = Math.min(1, adjustedScroll / (float) scrollRange);
offset = Math.max(0, offset);

// On RTL devices, push the wallpaper offset to the right if we don't have enough
// pages (ie if numScrollingPages < MIN_PARALLAX_PAGE_SPAN)
if (!mWallpaperIsLiveWallpaper && numScrollingPages < MIN_PARALLAX_PAGE_SPAN
&& mIsRtl) {
return offset * (parallaxPageSpan - numScrollingPages + 1) / parallaxPageSpan;
}
return offset * (numScrollingPages - 1) / parallaxPageSpan;
}
}

private int numEmptyScreensToIgnore() {
int numScrollingPages = getChildCount() - numCustomPages();
if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && hasExtraEmptyScreen()) {
return 1;
} else {
return 0;
}
}

private int getNumScreensExcludingEmptyAndCustom() {
int numScrollingPages = getChildCount() - numEmptyScreensToIgnore() - numCustomPages();
return numScrollingPages;
}

public void syncWithScroll() {
//获取壁纸偏移量
float offset = wallpaperOffsetForCurrentScroll();
//设置壁纸偏移量
mWallpaperOffset.setFinalX(offset);
//更新壁纸偏移量
updateOffset(true);
}

public float getCurrX() {
return mCurrentOffset;
}

public float getFinalX() {
return mFinalOffset;
}

private void animateToFinal() {
mAnimating = true;
mAnimationStartOffset = mCurrentOffset;
mAnimationStartTime = System.currentTimeMillis();
}

private void setWallpaperOffsetSteps() {
// Set wallpaper offset steps (1 / (number of screens - 1))
float xOffset = 1.0f / mNumPagesForWallpaperParallax;
if (xOffset != mLastSetWallpaperOffsetSteps) {
mWallpaperManager.setWallpaperOffsetSteps(xOffset, 1.0f);
mLastSetWallpaperOffsetSteps = xOffset;
}
}

public void setFinalX(float x) {
scheduleUpdate();
mFinalOffset = Math.max(0f, Math.min(x, 1.0f));
if (getNumScreensExcludingEmptyAndCustom() != mNumScreens) {
if (mNumScreens > 0) {
// Don't animate if we're going from 0 screens
animateToFinal();
}
mNumScreens = getNumScreensExcludingEmptyAndCustom();
}
}

private void scheduleUpdate() {
if (!mWaitingForUpdate) {
mChoreographer.postFrameCallback(this);
mWaitingForUpdate = true;
}
}
//把壁纸最终偏移量设置为当前偏移量
public void jumpToFinal() {
mCurrentOffset = mFinalOffset;
}
}

onLayout()交给父类PageView处理

protected void onLayout(boolean changed, int left,int top,int right,int bottom){
if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
mWallpaperOffset.syncWithScroll();
mWallpaperOffset.jumpToFinal();
}
super.onLayout(changed, left, top, right, bottom);
}

查找新屏幕的索引,如果存在空屏幕,则在那之前插入它

public void long insertNewWorkspaceScreenBeforeEmptyScreen(long screenId){

}
public long insertNewWorkspaceScreen(long screenId, int insertIndex){
if(mWorkspaceScreens.containsKey(screenId)){
throw new RuntimeException("Screen id " + screenId + "already exists!");
}
//inflate xml
CellLayout newScreen = (CellLayout)mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen,this,false);
//设置屏幕的长按、点击事件监听
newScreen.setOnLongClickListener(mLongClickListener);
newScreen.setOnClickListener(mLauncher);
//屏幕声音效果
newScreen.setSoundEffectEnabled(false);
mWorkspaceScreens.put(screenId,newScreen);
mScreenOrder.add(insertIndex,screenId);
addView(newScreen,insertIndex);
//屏幕允许拖曳?
LauncherAccessibilityDelegate delegate = LauncherAppSate.getInstance().getAccessibleDrag();
if(delegate != null && dlelegate.isInAccessibleDrag()){
newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILTY_DRAG);
return screenId;
}
}