Launcher3原始碼分析(Workspace)
阿新 • • 發佈:2019-02-01
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.0 f;
//長按桌面進入預覽模式時的縮放比例
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;
}
}