1.简介
默许的launcher便是launcher3这个app了,手机发动今后主动发动的app,便是咱们常说的桌面。点击home键会回来桌面app,假如手机上装有多个桌面app,那么点击home键会提示让你挑选一个。
2.launcher.xml
布局简单剖析下
<com.android.launcher3.LauncherRootView
android:id="@+id/launcher"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.android.launcher3.dragndrop.DragLayer
android:id="@+id/drag_layer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:importantForAccessibility="no">
<com.android.launcher3.views.AccessibilityActionsView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/home_screen"
/>
<!-- The workspace contains 5 screens of cells -->
<!-- 左右滑动的控件 DO NOT CHANGE THE ID -->
<com.android.launcher3.Workspace
android:id="@+id/workspace"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:theme="@style/HomeScreenElementTheme"
launcher:pageIndicator="@+id/page_indicator" />
<!-- home页底部那几个方便方式,DO NOT CHANGE THE ID -->
<include
android:id="@+id/hotseat"
layout="@layout/hotseat" />
<!-- 对应workspace的指示器,只要一页的话不显现。Keep these behind the workspace so that they are not visible when
we go into AllApps -->
<com.android.launcher3.pageindicators.WorkspacePageIndicator
android:id="@+id/page_indicator"
android:layout_width="match_parent"
android:layout_height="@dimen/workspace_page_indicator_height"
android:layout_gravity="bottom|center_horizontal"
android:theme="@style/HomeScreenElementTheme" />
<!-- 这个是长按app方便方式的时分,屏幕顶部呈现的操作按钮,撤销,卸载等按钮-->
<include
android:id="@+id/drop_target_bar"
layout="@layout/drop_target_bar" />
<com.android.launcher3.views.ScrimView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/scrim_view"
android:background="@android:color/transparent" />
<!--这个是手势上划今后看到的页面,便是一个搜索框,下边是一切已装置的app-->
<include
android:id="@+id/apps_view"
layout="@layout/all_apps"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!--用来显现recents内容的,详细布局在quickStep目录下重写了-->
<include
android:id="@+id/overview_panel"
layout="@layout/overview_panel" />
</com.android.launcher3.dragndrop.DragLayer>
</com.android.launcher3.LauncherRootView>
下图1 是id/workspace ,图2 是id/page_indicator ,图3是id/hotseat
下图是布局里的id://apps_views
2.0.include layout
>hotseat.xml
<com.android.launcher3.Hotseat
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:id="@+id/hotseat"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/HomeScreenElementTheme"
android:importantForAccessibility="no"
android:preferKeepClear="true"
launcher:containerType="hotseat" />
public class Hotseat extends CellLayout implements Insettable {
hotseat的onLayout里打印了下
System.out.println("log========="+l+"/"+t+","+r+"/"+b);
//log=========0/2568,1440/2960
>all_apps.xml
<com.android.launcher3.allapps.LauncherAllAppsContainerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/apps_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="true"
android:clipToPadding="false"
android:focusable="false"
android:saveEnabled="false" />
>drop_target_bar.xml
<com.android.launcher3.DropTargetBar xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/dynamic_grid_drop_target_size"
android:layout_gravity="center_horizontal|top"
android:focusable="false"
android:alpha="0"
android:theme="@style/HomeScreenElementTheme"
android:visibility="invisible">
<!-- Delete target -->
<com.android.launcher3.DeleteDropTarget
android:id="@+id/delete_target_text"
style="@style/DropTargetButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="@string/remove_drop_target_label" />
<!-- Uninstall target -->
<com.android.launcher3.SecondaryDropTarget
android:id="@+id/uninstall_target_text"
style="@style/DropTargetButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="@string/uninstall_drop_target_label" />
</com.android.launcher3.DropTargetBar>
2.1 DeviceProfile.java
从布局看,Workspace控件是铺满全屏的,可实际作用,很明显,距离底部有一段距离。WorkspacePageIndicator也是,布局上看,它便是底部居中的,可实际上的方位,明显和底部有一段距离。
查看了下这两控件的onlayout办法,并未进行处理,所以应该是其他地方处理的。
DeviceProfile是经过Builder来创立的,如下
public static class Builder {
public DeviceProfile build() {
//...
return new DeviceProfile(mContext, mInv, mInfo, mWindowBounds, mDotRendererCache,
mIsMultiWindowMode, mTransposeLayoutWithOrientation, mIsMultiDisplay,
mIsGestureMode, mViewScaleProvider);
}
DeviceProfile里有如下的变量,看姓名就很像咱们要的
public final Rect workspacePadding = new Rect();
数据的改动是经过下边这个办法,这个办法在结构办法里会调用2次,先在updateAvailableDimensions(res)办法里,之后等cellLayoutPaddingPx这个核算出来今后,又从头调用了一次。
private void updateWorkspacePadding() {
>workspace里运用
能够看到Workspace控件设置了padding,indicator控件设置了margin
public void setInsets(Rect insets) {
DeviceProfile grid = mLauncher.getDeviceProfile();
mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
//拿到padding并给控件设置padding
Rect padding = grid.workspacePadding;
setPadding(padding.left, padding.top, padding.right, padding.bottom);
mInsets.set(insets);
if (mWorkspaceFadeInAdjacentScreens) {
// In landscape mode the page spacing is set to the default.
setPageSpacing(grid.edgeMarginPx);
} else {
// In portrait, we want the pages spaced such that there is no
// overhang of the previous / next page into the current page viewport.
// We assume symmetrical padding in portrait mode.
int maxInsets = Math.max(insets.left, insets.right);
int maxPadding = Math.max(grid.edgeMarginPx, padding.left + 1);
setPageSpacing(Math.max(maxInsets, maxPadding));
}
updateCellLayoutPadding();
updateWorkspaceWidgetsSizes();
setPageIndicatorInset();
}
//这个是给indicator设置margin
private void setPageIndicatorInset() {
DeviceProfile grid = mLauncher.getDeviceProfile();
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPageIndicator.getLayoutParams();
// Set insets for page indicator
Rect padding = grid.workspacePadding;
if (grid.isVerticalBarLayout()) {
lp.leftMargin = padding.left + grid.workspaceCellPaddingXPx;
lp.rightMargin = padding.right + grid.workspaceCellPaddingXPx;
lp.bottomMargin = padding.bottom;
} else {
lp.leftMargin = lp.rightMargin = 0;
lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
lp.bottomMargin = grid.hotseatBarSizePx;
}
//Workspace的布局里有指明indicator的id,所以这儿拿到了对应的view
mPageIndicator.setLayoutParams(lp);
}
2.2.InvariantDeviceProfile.java
private void initGrid(Context context, Info displayInfo, DisplayOption displayOption,
@DeviceType int deviceType) {
//...
for (WindowBounds bounds : displayInfo.supportedBounds) {
localSupportedProfiles.add(new DeviceProfile.Builder(context, this, displayInfo)
.setIsMultiDisplay(deviceType == TYPE_MULTI_DISPLAY)
.setWindowBounds(bounds)
.setDotRendererCache(dotRendererCache)
.build());
这儿的bounds有3种,依据结果猜测应该是竖屏加两种横屏,模拟器日志如下
//bounds and inset
Rect(0, 0 - 1440, 2960)==Rect(0, 84 - 0, 168)
Rect(0, 0 - 2960, 1440)==Rect(0, 84 - 168, 0)
Rect(0, 0 - 2960, 1440)==Rect(168, 84 - 0, 0)
3.View
3.1.CellLayout
public class CellLayout extends ViewGroup {
//...
public @interface ContainerType{}
public static final int WORKSPACE = 0;
public static final int HOTSEAT = 1;
public static final int FOLDER = 2;
public CellLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
//容器的类型,有3种,默许是workspace
mContainerType = a.getInteger(R.styleable.CellLayout_containerType, WORKSPACE);
a.recycle();
// 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.
setWillNotDraw(false);
setClipToPadding(false);
mActivity = ActivityContext.lookupContext(context);
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
resetCellSizeInternal(deviceProfile);
//每页切割的行数和列数
mCountX = deviceProfile.inv.numColumns;
mCountY = deviceProfile.inv.numRows;
mOccupied = new GridOccupancy(mCountX, mCountY);
mTmpOccupied = new GridOccupancy(mCountX, mCountY);
mFolderLeaveBehind.mDelegateCellX = -1;
mFolderLeaveBehind.mDelegateCellY = -1;
setAlwaysDrawnWithCacheEnabled(false);
Resources res = getResources();
mBackground = getContext().getDrawable(R.drawable.bg_celllayout);
mBackground.setCallback(this);
mBackground.setAlpha(0);
//便是长按图标的时分,图标底部会呈现一个椭圆的边框,便是那个颜色
mGridColor = Themes.getAttrColor(getContext(), R.attr.workspaceAccentColor);
mGridVisualizationRoundingRadius =
res.getDimensionPixelSize(R.dimen.grid_visualization_rounding_radius);
mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * deviceProfile.iconSizePx);
// Initialize the data structures used for the drag visualization.
mEaseOutInterpolator = Interpolators.DEACCEL_2_5; // Quint ease out
mDragCell[0] = mDragCell[1] = -1;
mDragCellSpan[0] = mDragCellSpan[1] = -1;
for (int i = 0; i < mDragOutlines.length; i++) {
mDragOutlines[i] = new CellLayoutLayoutParams(0, 0, 0, 0, -1);
}
mDragOutlinePaint.setColor(Themes.getAttrColor(context, R.attr.workspaceTextColor));
//...
mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType);
mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
mBorderSpace);
//默许增加了一个容器
addView(mShortcutsAndWidgets);
}
3.2.Hotseat.java
public class Hotseat extends CellLayout implements Insettable {
//...
public Hotseat(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
//这个应该是曾经用的,曾经的qsb是在hotseat里,现在的是在顶部显现的,所以这个能够不重视,宽高都是0
mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
addView(mQsb);
}
public void resetLayout(boolean hasVerticalHotseat) {
removeAllViewsInLayout();
mHasVerticalHotseat = hasVerticalHotseat;
DeviceProfile dp = mActivity.getDeviceProfile();
resetCellSize(dp);
//设置几行几列
if (hasVerticalHotseat) {
setGridSize(1, dp.numShownHotseatIcons);//横屏是左右一列
} else {
setGridSize(dp.numShownHotseatIcons, 1);//竖屏是底部一行
}
}
public void setInsets(Rect insets) {
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
DeviceProfile grid = mActivity.getDeviceProfile();
if (grid.isVerticalBarLayout()) {
//横屏,高度铺满,宽度限制,依据seascape决定显现在左面仍是右边
mQsb.setVisibility(View.GONE);
lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
if (grid.isSeascape()) {
lp.gravity = Gravity.LEFT;
lp.width = grid.hotseatBarSizePx + insets.left;
} else {
lp.gravity = Gravity.RIGHT;
lp.width = grid.hotseatBarSizePx + insets.right;
}
} else {
//竖屏,底部显现,宽度铺满,高度限制
mQsb.setVisibility(View.VISIBLE);
lp.gravity = Gravity.BOTTOM;
lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
lp.height = grid.hotseatBarSizePx;
}
Rect padding = grid.getHotseatLayoutPadding(getContext());
setPadding(padding.left, padding.top, padding.right, padding.bottom);
setLayoutParams(lp);
InsettableFrameLayout.dispatchInsets(this, insets);
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
//接触事情交给workspace处理了
int yThreshold = getMeasuredHeight() - getPaddingBottom();
if (mWorkspace != null && ev.getY() <= yThreshold) {
mSendTouchToWorkspace = mWorkspace.onInterceptTouchEvent(ev);
return mSendTouchToWorkspace;
}
return false;
}
3.3.WorkspacePageIndicator.java
与workspace翻页对应的一个自定义指示器,不论横屏仍是竖屏,都是在底部的,当然了,竖屏的时分是在hotseat上边的。
这儿首要阐明的是,workspace里有个办法【setPageIndicatorInset()】,设置了这个indicator的margin。
public class WorkspacePageIndicator extends View implements Insettable, PageIndicator {
//
public WorkspacePageIndicator(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
Resources res = context.getResources();
mLinePaint = new Paint();
mLinePaint.setAlpha(0);
mLauncher = Launcher.getLauncher(context);
mLineHeight = res.getDimensionPixelSize(R.dimen.workspace_page_indicator_line_height);
boolean darkText = Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText);
mActiveAlpha = darkText ? BLACK_ALPHA : WHITE_ALPHA;
mLinePaint.setColor(darkText ? Color.BLACK : Color.WHITE);
}
protected void onDraw(Canvas canvas) {
if (mTotalScroll == 0 || mNumPagesFloat == 0) {
return;
}
//依据当前地点的页面,画条钱
canvas.drawRoundRect(lineLeft, getHeight() / 2 - mLineHeight / 2, lineRight,
getHeight() / 2 + mLineHeight / 2, mLineHeight, mLineHeight, mLinePaint);
}
//主动躲藏
public void setShouldAutoHide(boolean shouldAutoHide) {
mShouldAutoHide = shouldAutoHide;
if (shouldAutoHide && mLinePaint.getAlpha() > 0) {
hideAfterDelay();
} else if (!shouldAutoHide) {
mDelayedLineFadeHandler.removeCallbacksAndMessages(null);
}
}
3.4.Workspace.java
里面的child是CellLayout
/**
* The workspace is a wide area with a wallpaper and a finite number of pages.
* 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.
* @param <T> Class that extends View and PageIndicator
*/
public class Workspace<T extends View & PageIndicator> extends PagedView<T>
implements DropTarget, DragSource, View.OnTouchListener,
DragController.DragListener, Insettable, StateHandler<LauncherState>,
WorkspaceLayoutManager, LauncherBindableItemsContainer, LauncherOverlayCallbacks {
//...
public Workspace(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mLauncher = Launcher.getLauncher(context);
mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this);
mWallpaperManager = WallpaperManager.getInstance(context);
mAllAppsIconSize = mLauncher.getDeviceProfile().allAppsIconSizePx;
mWallpaperOffset = new WallpaperOffsetInterpolator(this);
setHapticFeedbackEnabled(false);
initWorkspace();
// Disable multitouch across the workspace/all apps/customize tray
setMotionEventSplittingEnabled(true);
//接触事情的处理
setOnTouchListener(new WorkspaceTouchListener(mLauncher, this));
mStatsLogManager = StatsLogManager.newInstance(context);
}
>setInsets
public void setInsets(Rect insets) {
DeviceProfile grid = mLauncher.getDeviceProfile();
mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
Rect padding = grid.workspacePadding;
//设置padding
setPadding(padding.left, padding.top, padding.right, padding.bottom);
mInsets.set(insets);
if (mWorkspaceFadeInAdjacentScreens) {
// In landscape mode the page spacing is set to the default.
setPageSpacing(grid.edgeMarginPx);
} else {
// In portrait, we want the pages spaced such that there is no
// overhang of the previous / next page into the current page viewport.
// We assume symmetrical padding in portrait mode.
int maxInsets = Math.max(insets.left, insets.right);
int maxPadding = Math.max(grid.edgeMarginPx, padding.left + 1);
setPageSpacing(Math.max(maxInsets, maxPadding));
}
//celllayout便是workspace里的child了,设置下padding
updateCellLayoutPadding();
//设置里面的widget的巨细,又是套在ShortcutAndWidgetContainer容器里的child
updateWorkspaceWidgetsSizes();
//给indicator设置margin,确保竖屏的话indicator在hotseat上边,横屏的话在右边或者左面
setPageIndicatorInset();
}
>drag start/end
public void onDragStart(DragObject dragObject, DragOptions options) {
if (mDragInfo != null && mDragInfo.cell != null) {
CellLayout layout = (CellLayout) (mDragInfo.cell instanceof LauncherAppWidgetHostView
? dragObject.dragView.getContentViewParent().getParent()
: mDragInfo.cell.getParent().getParent());
layout.markCellsAsUnoccupiedForView(mDragInfo.cell);
}
updateChildrenLayersEnabled();
//判别下是否需求增加新的页面
boolean addNewPage = !(options.isAccessibleDrag && dragObject.dragSource != this);
if (addNewPage) {
mDeferRemoveExtraEmptyScreen = false;
//增加一个空白页面
addExtraEmptyScreenOnDrag(dragObject);
if (dragObject.dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
&& dragObject.dragSource != this) {
int currentPage = getDestinationPage();
for (int pageIndex = currentPage; pageIndex < getPageCount(); pageIndex++) {
CellLayout page = (CellLayout) getPageAt(pageIndex);
if (page.hasReorderSolution(dragObject.dragInfo)) {
setCurrentPage(pageIndex);
break;
}
}
}
}
// Always enter the spring loaded mode
mLauncher.getStateManager().goToState(SPRING_LOADED);
}
public void onDragEnd() {
updateChildrenLayersEnabled();
StateManager<LauncherState> stateManager = mLauncher.getStateManager();
stateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
@Override
public void onStateTransitionComplete(LauncherState finalState) {
if (finalState == NORMAL) {
if (!mDeferRemoveExtraEmptyScreen) {
removeExtraEmptyScreen(true /* stripEmptyScreens */);
}
stateManager.removeStateListener(this);
}
}
});
mDragInfo = null;
mDragSourceInternal = null;
}
>onViewAdded
public void onViewAdded(View child) {
//能够看到,只能增加CellLayout
if (!(child instanceof CellLayout)) {
throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
}
CellLayout cl = ((CellLayout) child);
cl.setOnInterceptTouchListener(this);//child是否阻拦接触事情,由父类来处理
cl.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
super.onViewAdded(child);
}
>bindAndInitFirstWorkspaceScreen
一个是launcher类里调用,一个是下边removeAll的时分调用
public void bindAndInitFirstWorkspaceScreen() {
if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
return;
}
// 增加第一个页面
CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
// Always add a first page pinned widget on the first screen.
if (mFirstPagePinnedItem == null) {
//这个便是那个顶部的search bar
mFirstPagePinnedItem = LayoutInflater.from(getContext())
.inflate(R.layout.search_container_workspace, firstPage, false);
}
int cellHSpan = mLauncher.getDeviceProfile().inv.numSearchContainerColumns;
CellLayoutLayoutParams lp = new CellLayoutLayoutParams(0, 0, cellHSpan, 1, FIRST_SCREEN_ID);
lp.canReorder = false;
//把search bar 增加到第一个cellLayout页面里去
if (!firstPage.addViewToCellLayout(
mFirstPagePinnedItem, 0, R.id.search_container_workspace, lp, true)) {
Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
mFirstPagePinnedItem = null;
}
}
+search_container_workspace.xml
<com.android.launcher3.qsb.QsbContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="@id/search_container_workspace"
android:padding="0dp" >
<fragment
android:name="com.android.launcher3.qsb.QsbContainerView$QsbFragment"
android:layout_width="match_parent"
android:tag="qsb_view"
android:layout_height="match_parent"/>
</com.android.launcher3.qsb.QsbContainerView>
>insertNewWorkspaceScreen
public CellLayout insertNewWorkspaceScreen(int screenId, int insertIndex) {
CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
R.layout.workspace_screen, this, false /* attachToRoot */);
mWorkspaceScreens.put(screenId, newScreen);
mScreenOrder.add(insertIndex, screenId);
addView(newScreen, insertIndex);//增加到workspace里
mStateTransitionAnimation.applyChildState(
mLauncher.getStateManager().getState(), newScreen, insertIndex);
updatePageScrollValues();
updateCellLayoutPadding();
return newScreen;
}
外部调用的是下边的办法
public void insertNewWorkspaceScreenBeforeEmptyScreen(int screenId) {
// 有空白页的话插在空白页之前,没有的话插在容器结尾
int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
if (insertIndex < 0) {
insertIndex = mScreenOrder.size();
}
insertNewWorkspaceScreen(screenId, insertIndex);
}
public void insertNewWorkspaceScreen(int screenId) {
//默许插入在结尾
insertNewWorkspaceScreen(screenId, getChildCount());
}
>removeAllWorkspaceScreens
在launcher里会调用这个办法,首要是清空workspace内容,并初始化一个默许的页面
public void removeAllWorkspaceScreens() {
disableLayoutTransitions();
// 移除主页那个search bar
if (mFirstPagePinnedItem != null) {
((ViewGroup) mFirstPagePinnedItem.getParent()).removeView(mFirstPagePinnedItem);
}
// 清空view以及集合里保存的引证
removeFolderListeners();
removeAllViews();
mScreenOrder.clear();
mWorkspaceScreens.clear();
// Remove any deferred refresh callbacks
mLauncher.mHandler.removeCallbacksAndMessages(DeferredWidgetRefresh.class);
// 从头初始化第一个默许的页面
bindAndInitFirstWorkspaceScreen();
// Re-enable the layout transitions
enableLayoutTransitions();
}
+workspace_screen.xml
<com.android.launcher3.CellLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hapticFeedbackEnabled="false"
launcher:containerType="workspace" />
>workspace元素介绍
如下图,有3种,
- 第一种是LauncherAppWidgetHostView,也便是常说的(某个app的)小部件
- 第二种是文件夹FolderIcon,其实拖拽的时分也是当个图标处理的
- 第三种便是app的方便图标了DoubleShadowBubbleTextView
>addExtraEmptyScreenOnDrag
处理下拖拽的时分,需不需求增加新的空白页。
变量阐明:
-
dragSourceChildCount—-开始拖动今后,页面上元素的个数,假如是小部件的话,开始拖动的时分这个小部件就主动从页面移除了,所以拖动小部件的时分这个count会少一,举个列子,假如页面上就只要一个小部件,你拖动今后回来的count便是0了。
private void addExtraEmptyScreenOnDrag(DragObject dragObject) { boolean lastChildOnScreen = false; boolean childOnFinalScreen = false; if (mDragSourceInternal != null) { //看上边阐明 int dragSourceChildCount = mDragSourceInternal.getChildCount(); //这玩意判别的是折叠屏吗? If the icon was dragged from Hotseat, there is no page pair if (isTwoPanelEnabled() && !(mDragSourceInternal.getParent() instanceof Hotseat)) { int pagePairScreenId = getScreenPair(dragObject.dragInfo.screenId); CellLayout pagePair = mWorkspaceScreens.get(pagePairScreenId); dragSourceChildCount += pagePair.getShortcutsAndWidgets().getChildCount(); } //拖动的是小部件的话,这个count数少一个,这儿需求加回来 if (dragObject.dragView.getContentView() instanceof LauncherAppWidgetHostView) { dragSourceChildCount++; } //阐明咱们拖动的是页面上的唯一一个元素 if (dragSourceChildCount == 1) { lastChildOnScreen = true; } CellLayout cl = (CellLayout) mDragSourceInternal.getParent(); //这个拖动的图标地点的页面是workspace的最后一个页面 if (getLeftmostVisiblePageForIndex(indexOfChild(cl)) == getLeftmostVisiblePageForIndex(getPageCount() - 1)) { childOnFinalScreen = true; } } // 拖动的是最后一个页面的唯一一个元素,那么不必创立新的空白页面 if (lastChildOnScreen && childOnFinalScreen) { return; } forEachExtraEmptyPageId(extraEmptyPageId -> { //没有空白页的话,创立一个 if (!mWorkspaceScreens.containsKey(extraEmptyPageId)) { insertNewWorkspaceScreen(extraEmptyPageId); } }); }
private void forEachExtraEmptyPageId(Consumer<Integer> callback) {
callback.accept(EXTRA_EMPTY_SCREEN_ID);//空屏的id,固定的
if (isTwoPanelEnabled()) {//双屏的话,再加一个空的
callback.accept(EXTRA_EMPTY_SCREEN_SECOND_ID);
}
}
>removeExtraEmptyScreen
public void removeExtraEmptyScreen(boolean stripEmptyScreens) {
removeExtraEmptyScreenDelayed(0, stripEmptyScreens, null);
}
public void removeExtraEmptyScreenDelayed(
int delay, boolean stripEmptyScreens, Runnable onComplete) {
if (mLauncher.isWorkspaceLoading()) {
// Don't strip empty screens if the workspace is still loading
return;
}
if (delay > 0) {
postDelayed(
() -> removeExtraEmptyScreenDelayed(0, stripEmptyScreens, onComplete), delay);
return;
}
// First we convert the last page to an extra page if the last page is empty
// and we don't already have an extra page.
convertFinalScreenToEmptyScreenIfNecessary();
// Then we remove the extra page(s) if they are not the only pages left in Workspace.
if (hasExtraEmptyScreens()) {
forEachExtraEmptyPageId(extraEmptyPageId -> {
removeView(mWorkspaceScreens.get(extraEmptyPageId));
mWorkspaceScreens.remove(extraEmptyPageId);
mScreenOrder.removeValue(extraEmptyPageId);
});
setCurrentPage(getNextPage());
// Update the page indicator to reflect the removed page.
showPageIndicatorAtCurrentScroll();
}
if (stripEmptyScreens) {
// This will remove all empty pages from the Workspace. If there are no more pages left,
// it will add extra page(s) so that users can put items on at least one page.
stripEmptyScreens();
}
if (onComplete != null) {
onComplete.run();
}
}
3.5.WorkspaceTouchListener
用来处理workspace的空白区域的点击事情的,首要便是弹一个如下弹框选项
//处理workspace空白区域的touch事情,并显现一个选项弹框
public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListener
implements OnTouchListener {
//...
private int mLongPressState = STATE\_CANCELLED;
private final GestureDetector mGestureDetector;//手势监听,这儿就处理下长按事情
public WorkspaceTouchListener(Launcher launcher, Workspace\<?> workspace) {
//..
mGestureDetector = new GestureDetector(workspace.getContext(), this);
}
@Override
public boolean onTouch(View view, MotionEvent ev) {
mGestureDetector.onTouchEvent(ev);//这儿就简单的监听了长按事情
int action = ev.getActionMasked();
if (action == ACTION_DOWN) {
// 是否能够处理长按操作
boolean handleLongPress = canHandleLongPress();
if (handleLongPress) {
// Check if the event is not near the edges
DeviceProfile dp = mLauncher.getDeviceProfile();
DragLayer dl = mLauncher.getDragLayer();
Rect insets = dp.getInsets();
mTempRect.set(insets.left, insets.top, dl.getWidth() - insets.right,
dl.getHeight() - insets.bottom);
mTempRect.inset(dp.edgeMarginPx, dp.edgeMarginPx);
//判别下点击是否在可操作规模
handleLongPress = mTempRect.contains((int) ev.getX(), (int) ev.getY());
}
if (handleLongPress) {
mLongPressState = STATE_REQUESTED;//修正状态,后边用到
mTouchDownPoint.set(ev.getX(), ev.getY());
// Mouse right button's ACTION_DOWN should immediately show menu
if (TouchUtil.isMouseRightClickDownOrMove(ev)) {
maybeShowMenu();//鼠标右键的话这儿就直接弹框了
return true;
}
}
mWorkspace.onTouchEvent(ev);
// Return true to keep receiving touch events
return true;
}
if (mLongPressState == STATE_PENDING_PARENT_INFORM) {
// Inform the workspace to cancel touch handling
ev.setAction(ACTION_CANCEL);
mWorkspace.onTouchEvent(ev);
ev.setAction(action);
mLongPressState = STATE_COMPLETED;
}
boolean isInAllAppsBottomSheet = mLauncher.isInState(ALL_APPS)
&& mLauncher.getDeviceProfile().isTablet;
final boolean result;
if (mLongPressState == STATE_COMPLETED) {
// We have handled the touch, so workspace does not need to know anything anymore.
result = true;
} else if (mLongPressState == STATE_REQUESTED) {
mWorkspace.onTouchEvent(ev);
//正在拖拽或者移动的话,撤销长按事情
if (mWorkspace.isHandlingTouch()) {
cancelLongPress();
} else if (action == ACTION_MOVE && PointF.length(
mTouchDownPoint.x - ev.getX(), mTouchDownPoint.y - ev.getY()) > mTouchSlop) {
cancelLongPress();
}
result = true;
} else {
// We don't want to handle touch unless we're in AllApps bottom sheet, let workspace
// handle it as usual.
result = isInAllAppsBottomSheet;
}
if (action == ACTION_UP || action == ACTION_POINTER_UP) {
if (!mWorkspace.isHandlingTouch()) {
final CellLayout currentPage =
(CellLayout) mWorkspace.getChildAt(mWorkspace.getCurrentPage());
if (currentPage != null) {
//手指抬起的话,把方位发送给壁纸处理
mWorkspace.onWallpaperTap(ev);
}
}
}
if (action == ACTION_UP || action == ACTION_CANCEL) {
cancelLongPress();
}
if (action == ACTION_UP && isInAllAppsBottomSheet) {
mLauncher.getStateManager().goToState(NORMAL);
}
return result;
}
//是否支撑长按事情
private boolean canHandleLongPress() {
return AbstractFloatingView\.getTopOpenView(mLauncher) == null
&& mLauncher.isInState(NORMAL);
}
private void cancelLongPress() {
mLongPressState = STATE\_CANCELLED;
}
@Override
public void onLongPress(MotionEvent event) {
maybeShowMenu(); //手势的长按事情回调
}
private void maybeShowMenu() {
if (mLongPressState == STATE\_REQUESTED) {//这个state前边有剖析,在可点击规模即可
if (canHandleLongPress()) {//再次判别是否能够长按
mLongPressState = STATE\_PENDING\_PARENT\_INFORM;
//这儿便是显现弹框的操作了
mLauncher.showDefaultOptions(mTouchDownPoint.x, mTouchDownPoint.y);
} else {
cancelLongPress();
}
}
}
}
3.6.OptionsPopupView
public void showDefaultOptions(float x, float y) {
OptionsPopupView.show(this, getPopupTarget(x, y), OptionsPopupView.getOptions(this),
false);
}
public static OptionsPopupView show(ActivityContext launcher, RectF targetRect,
List<OptionItem> items, boolean shouldAddArrow, int width) {
//这玩意便是个线性布局
OptionsPopupView popup = (OptionsPopupView) launcher.getLayoutInflater()
.inflate(R.layout.longpress_options_menu, launcher.getDragLayer(), false);
popup.mTargetRect = targetRect;
popup.setShouldAddArrow(shouldAddArrow);
for (OptionItem item : items) {
DeepShortcutView view =
(DeepShortcutView) popup.inflateAndAdd(R.layout.system_shortcut, popup);
if (width > 0) {
view.getLayoutParams().width = width;
}
view.getIconView().setBackgroundDrawable(item.icon);
view.getBubbleText().setText(item.label);
view.setOnClickListener(popup);
view.setOnLongClickListener(popup);
popup.mItemMap.put(view, item);
}
popup.show();
return popup;
}
3.7.ShortcutAndWidgetContainer
public ShortcutAndWidgetContainer(Context context, @ContainerType int containerType) {
super(context);
mActivity = ActivityContext.lookupContext(context);
mWallpaperManager = WallpaperManager.getInstance(context);
mContainerType = containerType;
}
public void setCellDimensions(int cellWidth, int cellHeight, int countX, int countY,
Point borderSpace) {
mCellWidth = cellWidth;
mCellHeight = cellHeight;
mCountX = countX;
mCountY = countY;
mBorderSpace = borderSpace;
}
//依据传进来的索引,对比布局参数,判别点击的是哪个
public View getChildAt(int cellX, int cellY) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
if ((lp.cellX <= cellX) && (cellX < lp.cellX + lp.cellHSpan)
&& (lp.cellY <= cellY) && (cellY < lp.cellY + lp.cellVSpan)) {
return child;
}
}
return null;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthSpecSize, heightSpecSize);
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
measureChild(child);
}
}
}
>measureChild
首要便是经过lp的setup办法,核算child的x,y方位以及宽高
public void measureChild(View child) {
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
final DeviceProfile dp = mActivity.getDeviceProfile();
if (child instanceof NavigableAppWidgetHostView) {
((NavigableAppWidgetHostView) child).getWidgetInset(dp, mTempRect);
final PointF appWidgetScale = dp.getAppWidgetScale((ItemInfo) child.getTag());
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
appWidgetScale.x, appWidgetScale.y, mBorderSpace, mTempRect);
} else {
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
mBorderSpace, null);
//...
child.setPadding(cellPaddingX, cellPaddingY, cellPaddingX, 0);
}
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childheightMeasureSpec);
}
>onLayout
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
layoutChild(child);
}
}
}
public void layoutChild(View child) {
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
if (child instanceof NavigableAppWidgetHostView) {
NavigableAppWidgetHostView nahv = (NavigableAppWidgetHostView) child;
//...
}
int childLeft = lp.x;
int childTop = lp.y;
//measure办法的时分已经经过lp的setup办法核算了方位以及宽高,这儿直接运用即可
child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
//...
}
3.8.CellLayoutLayoutParams
celllayout相似网格那种,所以这儿的数据都是网格的索引
public CellLayoutLayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan,
int screenId) {
super(CellLayoutLayoutParams.MATCH_PARENT, CellLayoutLayoutParams.MATCH_PARENT);
this.cellX = cellX;//水平方向索引
this.cellY = cellY;//笔直方向索引
this.cellHSpan = cellHSpan;//水平方向跨度,便是占几个格子
this.cellVSpan = cellVSpan;//笔直方向跨度
this.screenId = screenId;
}
中心办法setup,依据网格的索引,每个网格的宽高,以及自己横向和纵向占几个网格,就能够核算出方位宽高了。
public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
int rowCount, float cellScaleX, float cellScaleY, Point borderSpace,
@Nullable Rect inset) {
if (isLockedToGrid) {
final int myCellHSpan = cellHSpan;
final int myCellVSpan = cellVSpan;
int myCellX = useTmpCoords ? tmpCellX : cellX;
int myCellY = useTmpCoords ? tmpCellY : cellY;
if (invertHorizontally) {
myCellX = colCount - myCellX - cellHSpan;
}
int hBorderSpacing = (myCellHSpan - 1) * borderSpace.x;
int vBorderSpacing = (myCellVSpan - 1) * borderSpace.y;
float myCellWidth = ((myCellHSpan * cellWidth) + hBorderSpacing) / cellScaleX;
float myCellHeight = ((myCellVSpan * cellHeight) + vBorderSpacing) / cellScaleY;
width = Math.round(myCellWidth) - leftMargin - rightMargin;
height = Math.round(myCellHeight) - topMargin - bottomMargin;
x = leftMargin + (myCellX * cellWidth) + (myCellX * borderSpace.x);
y = topMargin + (myCellY * cellHeight) + (myCellY * borderSpace.y);
if (inset != null) {
x -= inset.left;
y -= inset.top;
width += inset.left + inset.right;
height += inset.top + inset.bottom;
}
}
}
3.9.FolderIcon
这个是显现在workspace上的,Foler是点击这个打开的布局
>folder_icon.xml
<com.android.launcher3.folder.FolderIcon
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:focusable="true" >
<com.android.launcher3.views.DoubleShadowBubbleTextView
style="@style/BaseIcon.Workspace"
android:id="@+id/folder_icon_name"
android:focusable="false"
android:layout_gravity="top"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.android.launcher3.folder.FolderIcon>
>init
结构办法里会初始化一些工具类,
public FolderIcon(Context context) {
super(context);
init();
}
private void init() {
mLongPressHelper = new CheckLongPressHelper(this);//长按事情帮助类
mPreviewLayoutRule = new ClippedFolderIconLayoutRule();
mPreviewItemManager = new PreviewItemManager(this);//预览作用这个来操控
mDotParams = new DotRenderer.DrawParams();
}
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN
&& shouldIgnoreTouchDown(event.getX(), event.getY())) {
return false;
}
// Call the superclass onTouchEvent first, because sometimes it changes the state to
// isPressed() on an ACTION_UP
super.onTouchEvent(event);
//接触事情交给helper类来处理长按事情
mLongPressHelper.onTouchEvent(event);
// Keep receiving the rest of the events
return true;
}
看一下helper类里长按事情的处理逻辑
private void triggerLongPress() {
if ((mView.getParent() != null)
&& mView.hasWindowFocus()
&& (!mView.isPressed() || mListener != null)
&& !mHasPerformedLongPress) {
boolean handled;
if (mListener != null) {
handled = mListener.onLongClick(mView);
} else {
//咱们没有设置listener,所以最后交给FolderIcon自己处理了
handled = mView.performLongClick();
}
if (handled) {
mView.setPressed(false);
mHasPerformedLongPress = true;
}
clearCallbacks();
}
}
这儿讲一下,长按事情是在workspace增加元素的时分一致增加的,用的下边这个
default View.OnLongClickListener getWorkspaceChildOnLongClickListener() {
return ItemLongClickListener.INSTANCE_WORKSPACE;
}
>获取对象
public static <T extends Context & ActivityContext> FolderIcon inflateFolderAndIcon(int resId,
T activityContext, ViewGroup group, FolderInfo folderInfo) {
//创立folder,也便是foldericon打开的控件
Folder folder = Folder.fromXml(activityContext);
//创立foldericon并设置数据
FolderIcon icon = inflateIcon(resId, activityContext, group, folderInfo);
folder.setFolderIcon(icon);
folder.bind(folderInfo);
icon.setFolder(folder);
return icon;
}
public static FolderIcon inflateIcon(int resId, ActivityContext activity, ViewGroup group,
FolderInfo folderInfo) {
DeviceProfile grid = activity.getDeviceProfile();
FolderIcon icon = (FolderIcon) LayoutInflater.from(group.getContext())
.inflate(resId, group, false);
icon.mFolderName = icon.findViewById(R.id.folder_icon_name);//文件夹姓名
//...
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
//设置topMargin,这样就显现在图标下边了。
lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
icon.setTag(folderInfo);
//点击事情
icon.setOnClickListener(ItemClickHandler.INSTANCE);
//...
icon.setDotInfo(folderDotInfo);
//预览图数据设置
icon.mPreviewVerifier = new FolderGridOrganizer(activity.getDeviceProfile().inv);
icon.mPreviewVerifier.setFolderInfo(folderInfo);
icon.updatePreviewItems(false);
folderInfo.addListener(icon);
return icon;
}
>点击事情
ItemClickHandler.INSTANCE
private static void onClick(View v) {
//依据tag拿到点击的view所属的类型,对应处理
Object tag = v.getTag();
if (tag instanceof WorkspaceItemInfo) {
onClickAppShortcut(v, (WorkspaceItemInfo) tag, launcher);
} else if (tag instanceof FolderInfo) {
if (v instanceof FolderIcon) {
onClickFolderIcon(v);
}
} else if (tag instanceof AppInfo) {
startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);
} else if (tag instanceof LauncherAppWidgetInfo) {
if (v instanceof PendingAppWidgetHostView) {
onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
}
} else if (tag instanceof ItemClickProxy) {
((ItemClickProxy) tag).onItemClicked(v);
}
}
最终是拿到folder今后调用animate办法显现
folder.animateOpen();
>dispatchDraw
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (!mBackgroundIsVisible) return;
mPreviewItemManager.recomputePreviewDrawingParams();
if (!mBackground.drawingDelegated()) {
mBackground.drawBackground(canvas);
}
if (mCurrentPreviewItems.isEmpty() && !mAnimating) return;
//经过管理类画背景,便是folder里缩小的app图标
mPreviewItemManager.draw(canvas);
if (!mBackground.drawingDelegated()) {
mBackground.drawBackgroundStroke(canvas);
}
drawDot(canvas);
}
3.10.Folder
这玩意是个线性布局,点击FolerIcon今后显现的浮窗
public abstract class AbstractFloatingView extends LinearLayout implements TouchController {
public class Folder extends AbstractFloatingView implements ClipPathView, DragSource,
View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener {
//...
@IntDef({STATE_CLOSED, STATE_ANIMATING, STATE_OPEN})
public @interface FolderState {}
>user_folder_icon_normalized.xml
FolderPagedView也是个自定义view,相似workspace,里面也是增加celllayout作为一页容器
<com.android.launcher3.folder.Folder
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<com.android.launcher3.folder.FolderPagedView
android:id="@+id/folder_content"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent"
launcher:pageIndicator="@+id/folder_page_indicator" />
<!--水平方向,文件夹姓名以及indicator-->
<LinearLayout
android:id="@+id/folder_footer"
android:layout_width="match_parent"
android:layout_height="@dimen/folder_footer_height_default"
android:clipChildren="false"
android:orientation="horizontal">
<com.android.launcher3.folder.FolderNameEditText
android:id="@+id/folder_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
style="@style/TextHeadline"
android:layout_weight="1"/>
<com.android.launcher3.pageindicators.PageIndicatorDots
android:id="@+id/folder_page_indicator"
android:layout_gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="1dp"
/>
</LinearLayout>
</com.android.launcher3.folder.Folder>
>onFinishInflate
获取背景图片,以及给child设置对应的属性
protected void onFinishInflate() {
super.onFinishInflate();
mBackground = (GradientDrawable) ResourcesCompat.getDrawable(getResources(),
R.drawable.round_rect_folder, getContext().getTheme());
mContent = findViewById(R.id.folder_content);
mContent.setFolder(this);
mPageIndicator = findViewById(R.id.folder_page_indicator);
mFolderName = findViewById(R.id.folder_name);
//...
mFooter = findViewById(R.id.folder_footer);
mFooterHeight = dp.folderFooterHeightPx;
}
>animateOpen
点击FolderIcon今后会调用下边的办法显现Folder
public void animateOpen() {
animateOpen(mInfo.contents, 0);
}
private void animateOpen(List<WorkspaceItemInfo> items, int pageNo) {
Folder openFolder = getOpen(mActivityContext);
if (openFolder != null && openFolder != this) {
//先查下,假如有在显现的folder,封闭它
openFolder.close(true);
}
mContent.bindItems(items);
centerAboutIcon();//核算下方位,巨细,设置下背景图的巨细
mItemsInvalidated = true;
updateTextViewFocus();
mIsOpen = true;
BaseDragLayer dragLayer = mActivityContext.getDragLayer();
if (getParent() == null) {
dragLayer.addView(this);//增加到容器里
mDragController.addDropTarget(this);
} else {
}
mContent.completePendingPageChanges();
mContent.setCurrentPage(pageNo);
mDeleteFolderOnDropCompleted = false;
//...anim
// Footer animation
if (mContent.getPageCount() > 1 && !mInfo.hasOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION)) {
//...
} else {
mFolderName.setTranslationX(0);
}
//...
}
>close
首要这个东西是增加到dragLayer层的,所以最开始的touch事情处理便是从layer层开始判别的。
BaseDragLayer.java
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if (action == ACTION_UP || action == ACTION_CANCEL) {
if (mTouchCompleteListener != null) {
mTouchCompleteListener.onTouchComplete();
}
mTouchCompleteListener = null;
} else if (action == MotionEvent.ACTION_DOWN) {
mActivity.finishAutoCancelActionMode();
}
return findActiveController(ev);//这一行..
}
//持续
protected boolean findActiveController(MotionEvent ev) {
mActiveController = null;
if (canFindActiveController()) {
mActiveController = findControllerToHandleTouch(ev);//这行..
}
return mActiveController != null;
}
//Folder便是承继的AbstractFloatingView,所以显现的时分,最上层查到的topView便是咱们的Folder了
private TouchController findControllerToHandleTouch(MotionEvent ev) {
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
if (topView != null
&& (isEventInLauncher(ev) || topView.canInterceptEventsInSystemGestureRegion())
&& topView.onControllerInterceptTouchEvent(ev)) {//这行..
return topView;
}
for (TouchController controller : mControllers) {
if (controller.onControllerInterceptTouchEvent(ev)) {
return controller;
}
}
return null;
}
//回到Folder.java
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
BaseDragLayer dl = (BaseDragLayer) getParent();
if (isEditingName()) {
if (!dl.isEventOverView(mFolderName, ev)) {
mFolderName.dispatchBackKey();
return true;
}
return false;
} else if (!dl.isEventOverView(this, ev)
&& mLauncherDelegate.interceptOutsideTouch(ev, dl, this)) {//这行..
return true;
}
}
return false;
}
//go on,咱们是点击空白区域躲藏Folder,所以很明显,走的else
boolean interceptOutsideTouch(MotionEvent ev, BaseDragLayer dl, Folder folder) {
if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
// Do not close the container if in drag and drop.
if (!dl.isEventOverView(mLauncher.getDropTargetBar(), ev)) {
return true;
}
} else {
// 这行。
folder.close(true);
return true;
}
return false;
}
// Folder.java
protected void handleClose(boolean animate) {
mIsOpen = false;
//...
if (animate) {
animateClosed();//带动画的,走这儿
} else {
closeComplete(false);
post(this::announceAccessibilityChanges);
}
}
//最终会走到这儿,从头显现FolderIcon, 并从dragLayer里移除Folder
private void closeComplete(boolean wasAnimated) {
BaseDragLayer parent = (BaseDragLayer) getParent();
if (parent != null) {
parent.removeView(this);// 移除
}
mDragController.removeDropTarget(this);
clearFocus();
if (mFolderIcon != null) {
mFolderIcon.setVisibility(View.VISIBLE);
mFolderIcon.setIconVisible(true);//从头显现
mFolderIcon.mFolderName.setTextVisibility(true);
}
FolderPagedView
public class FolderPagedView extends PagedView<PageIndicatorDots> implements ClipPathView {
4.学习记载
4.1.ItemLongClickListener.java
hotseat,workspace,allapps页面的icon的长按事情,都会调用canStartDrag这个办法判别的,所以在这个办法里处理就行。
public static final OnLongClickListener INSTANCE_WORKSPACE =
ItemLongClickListener::onWorkspaceItemLongClick;
public static final OnLongClickListener INSTANCE_ALL_APPS =
ItemLongClickListener::onAllAppsItemLongClick;
>onWorkspaceItemLongClick
private static boolean onWorkspaceItemLongClick(View v) {
Launcher launcher = Launcher.getLauncher(v.getContext());
//先判别是否支撑拖拽
if (!canStartDrag(launcher)) return false;
if (!launcher.isInState(NORMAL) && !launcher.isInState(OVERVIEW)) return false;
if (!(v.getTag() instanceof ItemInfo)) return false;
launcher.setWaitingForResult(null);
beginDrag(v, launcher, (ItemInfo) v.getTag(), launcher.getDefaultWorkspaceDragOptions());
return true;
}
>onAllAppsItemLongClick
private static boolean onAllAppsItemLongClick(View view) {
//..
Launcher launcher = Launcher.getLauncher(v.getContext());
//先判别是否支撑拖拽
if (!canStartDrag(launcher)) return false;
// When we have exited all apps or are in transition, disregard long clicks
if (!launcher.isInState(ALL_APPS) && !launcher.isInState(OVERVIEW)) return false;
if (launcher.getWorkspace().isSwitchingState()) return false;
// Start the drag
final DragController dragController = launcher.getDragController();
dragController.addDragListener(new DragController.DragListener() {
@Override
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
v.setVisibility(INVISIBLE);
}
@Override
public void onDragEnd() {
v.setVisibility(VISIBLE);
dragController.removeDragListener(this);
}
});
launcher.getWorkspace().beginDragShared(v, launcher.getAppsView(), new DragOptions());
return false;
}
>canStartDrag
下边的办法回来false,一切的icon都长按都没有反应了,天然也不会拖动了。
//禁止元素拖拽的话,这儿回来false即可
public static boolean canStartDrag(Launcher launcher) {
if (launcher == null) {
return false;
}
if (launcher.isWorkspaceLocked()) return false;
if (launcher.getDragController().isDragging()) return false;
return true;
}
4.2.WorkspaceLayoutManager.java
一个抽象类,完成了把view增加到container的逻辑,workspace,hotseat里的元素增加用的这个
default void addInScreen(View child, int container, int screenId, int x, int y,
int spanX, int spanY) {
if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
}
final CellLayout layout;
// 容器是hotseat类型
if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
|| container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
layout = getHotseat();
//hotseat里的只要图标,文本躲藏
if (child instanceof FolderIcon) {
((FolderIcon) child).setTextVisible(false);
}
} else {
// 其他的也便是workspace里的了,这个图标文字都显现的
if (child instanceof FolderIcon) {
((FolderIcon) child).setTextVisible(true);
}
layout = getScreenWithId(screenId);
}
ViewGroup.LayoutParams genericLp = child.getLayoutParams();
CellLayoutLayoutParams lp;
if (genericLp == null || !(genericLp instanceof CellLayoutLayoutParams)) {
lp = new CellLayoutLayoutParams(x, y, spanX, spanY, screenId);
} else {
lp = (CellLayoutLayoutParams) genericLp;
lp.cellX = x;
lp.cellY = y;
lp.cellHSpan = spanX;
lp.cellVSpan = spanY;
}
if (spanX < 0 && spanY < 0) {
lp.isLockedToGrid = false;
}
ItemInfo info = (ItemInfo) child.getTag();
int childId = info.getViewId();
boolean markCellsAsOccupied = !(child instanceof Folder);
//把child增加到容器里
if (!layout.addViewToCellLayout(child, -1, childId, lp, markCellsAsOccupied)) {
}
child.setHapticFeedbackEnabled(false);
//看下child的长按事情,都是这个
child.setOnLongClickListener(getWorkspaceChildOnLongClickListener());
if (child instanceof DropTarget) {
//这个是拖拽到顶部丢掉的child
onAddDropTarget((DropTarget) child);
}
}
// child的长按事情用的这个
default View.OnLongClickListener getWorkspaceChildOnLongClickListener() {
return ItemLongClickListener.INSTANCE_WORKSPACE;
}
5.总结
- 桌面的布局结构
- DevicePorfile,launcher3里各种配置相关的数据基本都在这儿,比方常用的hotseat高度,workspace每页能够分成几行几列
- cellLayout,一个能够按照网格切割显现child的容器,hotseat承继的它,workspace里增加的是它
- workspace里每页的元素,首要有3种类型,app的图标,app的小部件,文件夹
- 3.5小节里是空白区域的长按事情,4.1小节的是元素的长按事情