博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
墨香带你学Launcher之(七)--小部件的加载、添加以及大小调节
阅读量:7077 次
发布时间:2019-06-28

本文共 24962 字,大约阅读时间需要 83 分钟。

上一章我们介绍了Launcher的拖拽过程,涉及到的范围比较广,包括图标的拖拽,桌面上CellLayout的拖拽,小部件的拖拽,以及跨不同部件的拖拽,设计思想非常巧妙,不过整个流程相对也比较好掌握,只要跟着上一章的流程自己多跟踪几遍基本就熟悉了。按照计划本章我们继续学习Launcher的Widget的加载、添加以及Widget的大小调节。

Widget的数据加载

其实我们在第二章介绍过Widget数据的加载,相对只是简单的做了介绍,下面我们稍微讲的详细点。

我们知道Widget的数据加载开始在LauncherModel中的updateWidgetsModel方法中,我们看下代码:

void updateWidgetsModel(boolean refresh) {        PackageManager packageManager = mApp.getContext().getPackageManager();        final ArrayList widgetsAndShortcuts = new ArrayList();        widgetsAndShortcuts.addAll(getWidgetProviders(mApp.getContext(), refresh));        Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);        widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));        mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);    }复制代码

上面代码我们可以看到是通过调用getWidgetProviders(mApp.getContext(), refresh)方法来获取所有Widget的,代码:

public static List
getWidgetProviders(Context context, boolean refresh) { ArrayList
results = new ArrayList
(); try { synchronized (sBgLock) { if (sBgWidgetProviders == null || refresh) { HashMap
tmpWidgetProviders = new HashMap<>(); AppWidgetManagerCompat wm = AppWidgetManagerCompat.getInstance(context); LauncherAppWidgetProviderInfo info; List
widgets = wm.getAllProviders(); for (AppWidgetProviderInfo pInfo : widgets) { info = LauncherAppWidgetProviderInfo.fromProviderInfo(context, pInfo); UserHandleCompat user = wm.getUser(info); tmpWidgetProviders.put(new ComponentKey(info.provider, user), info); } Collection
customWidgets = Launcher.getCustomAppWidgets().values(); for (CustomAppWidget widget : customWidgets) { info = new LauncherAppWidgetProviderInfo(context, widget); UserHandleCompat user = wm.getUser(info); tmpWidgetProviders.put(new ComponentKey(info.provider, user), info); } // Replace the global list at the very end, so that if there is an exception, // previously loaded provider list is used. sBgWidgetProviders = tmpWidgetProviders; } results.addAll(sBgWidgetProviders.values()); return results; } } catch (Exception e) { ... } }复制代码

我们看到首先是初始化AppWidgetManagerCompat,我们之前介绍过带有Compat的是兼容组件,我们看看是怎么兼容的,

我们下面代码:

public static AppWidgetManagerCompat getInstance(Context context) {        synchronized (sInstanceLock) {            if (sInstance == null) {                if (Utilities.ATLEAST_LOLLIPOP) {                    sInstance = new AppWidgetManagerCompatVL(context.getApplicationContext());                } else {                    sInstance = new AppWidgetManagerCompatV16(context.getApplicationContext());                }            }            return sInstance;        }    }复制代码

我们可以看到AppWidgetManagerCompat的初始化有两个,一个是当Api版本高于21(包含21)时,用AppWidgetManagerCompatVL,低于21时用AppWidgetManagerCompatV16,这两个有什么不同,我们下面分析。

下面我们看如何获取Widget列表对象:

List
widgets = wm.getAllProviders();复制代码

getAllProviders()方法是一个抽象方法,所以我们看哪里进行了复写,

可以看到还是上面两个兼容类复写了该方法,我们看这个两个类中做了什么处理,先看V16中的:

@Override    public List
getAllProviders() { return mAppWidgetManager.getInstalledProviders(); }复制代码

我们再看mAppWidgetManager这个是在哪里初始化,

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)    @Override    public boolean bindAppWidgetIdIfAllowed(int appWidgetId, AppWidgetProviderInfo info,            Bundle options) {        if (Utilities.ATLEAST_JB_MR1) {            return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider, options);        } else {            return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider);        }    }复制代码

里面有个if语句,我们可以看到当Api大于等于17时,调用第一个进行初始化,否则调用第二个方法进行初始化,这就是对不同手机版本做的兼容。在我们写App的时候如果遇到相似情况也可以这么处理。

我们再看一下VL中的getAllProviders()方法:

@Override    public List
getAllProviders() { ArrayList
providers = new ArrayList
(); for (UserHandle user : mUserManager.getUserProfiles()) { providers.addAll(mAppWidgetManager.getInstalledProvidersForProfile(user)); } return providers; }复制代码

和V16中的不一样了,这里面是通过for循环来获取的,其中有个UserHandle,那么在源码中给出的解释是设备中的每个用户,个人理解应该是每个应用,每个应用会有0-N个Widget,也就是从每个应用中获取每个应用的Widget列表。这样for循环就可以获取整个手机中所有应用的widget列表了。

再回到上面getWidgetProviders方法的代码中,我们接着看,接着for循环AppWidgetProviderInfo列表信息,重构LauncherAppWidgetProviderInfo对象,这里有点怪,为啥有了AppWidgetProviderInfo对象还要重构一个LauncherAppWidgetProviderInfo对象,我们知道在写插件的时候每个Widget都会有一个类继承AppWidgetProvider,这样才会有一个插件,因此我们知道AppWidgetProviderInfo对象肯定是AppWidgetProvider的对象,那么LauncherAppWidgetProviderInfo是什么,我们接着看能不能找到答案,LauncherAppWidgetProviderInfo的初始化时通过

LauncherAppWidgetProviderInfo.fromProviderInfo(context, pInfo);复制代码

方法进行初始化的,我们再看LauncherAppWidgetProviderInfo又继承AppWidgetProviderInfo,越来越怪,我们接着看fromProviderInfo(context, pInfo)方法:

public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context,            AppWidgetProviderInfo info) {        Parcel p = Parcel.obtain();        info.writeToParcel(p, 0);        p.setDataPosition(0);        LauncherAppWidgetProviderInfo lawpi = new LauncherAppWidgetProviderInfo(p);        p.recycle();        return lawpi;    }复制代码

我们看到最后是通过new LauncherAppWidgetProviderInfo来生成一个LauncherAppWidgetProviderInfo对象,那么这个对象构造函数中有什么:

public LauncherAppWidgetProviderInfo(Parcel in) {        super(in);        initSpans();    }复制代码

这个构造函数调用了initSpans方法,我们接着追寻:

private void initSpans() {        LauncherAppState app = LauncherAppState.getInstance();        InvariantDeviceProfile idp = app.getInvariantDeviceProfile();        // We only care out the cell size, which is independent of the the layout direction.        Rect paddingLand = idp.landscapeProfile.getWorkspacePadding(false /* isLayoutRtl */);        Rect paddingPort = idp.portraitProfile.getWorkspacePadding(false /* isLayoutRtl */);        // Always assume we're working with the smallest span to make sure we        // reserve enough space in both orientations.        float smallestCellWidth = DeviceProfile.calculateCellWidth(Math.min(                idp.landscapeProfile.widthPx - paddingLand.left - paddingLand.right,                idp.portraitProfile.widthPx - paddingPort.left - paddingPort.right),                idp.numColumns);        float smallestCellHeight = DeviceProfile.calculateCellWidth(Math.min(                idp.landscapeProfile.heightPx - paddingLand.top - paddingLand.bottom,                idp.portraitProfile.heightPx - paddingPort.top - paddingPort.bottom),                idp.numRows);        // We want to account for the extra amount of padding that we are adding to the widget        // to ensure that it gets the full amount of space that it has requested.        Rect widgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(                app.getContext(), provider, null);        spanX = Math.max(1, (int) Math.ceil(                        (minWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));        spanY = Math.max(1, (int) Math.ceil(                (minHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));        minSpanX = Math.max(1, (int) Math.ceil(                (minResizeWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));        minSpanY = Math.max(1, (int) Math.ceil(                (minResizeHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));    }复制代码

这段代码也不难,是为了算四个参数:spanX、spanY、minSpanX、minSpanY,看过我前面博客的都知道这个spanX和spanY参数是什么,其实这个LauncherAppWidgetProviderInfo对象比系统自带的AppWidgetProviderInfo带有的就是多了这几个参数,也就是方便我们添加到桌面是计算占用位置。

最后得到HashMap这个Widget集合,最后通过

mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);复制代码

将这个集合放到WidgetsModel中:

public void setWidgetsAndShortcuts(ArrayList rawWidgetsShortcuts) {        ...        HashMap
tmpPackageItemInfos = new HashMap<>(); // clear the lists. ... InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile(); // add and update. for (Object o: rawWidgetsShortcuts) { String packageName = ""; UserHandleCompat userHandle = null; ComponentName componentName = null; if (o instanceof LauncherAppWidgetProviderInfo) { LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o; // Ensure that all widgets we show can be added on a workspace of this size int minSpanX = Math.min(widgetInfo.spanX, widgetInfo.minSpanX); int minSpanY = Math.min(widgetInfo.spanY, widgetInfo.minSpanY); if (minSpanX <= (int) idp.numColumns && minSpanY <= (int) idp.numRows) { componentName = widgetInfo.provider; packageName = widgetInfo.provider.getPackageName(); userHandle = mAppWidgetMgr.getUser(widgetInfo); } else { ... continue; } } else if (o instanceof ResolveInfo) { ResolveInfo resolveInfo = (ResolveInfo) o; componentName = new ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name); packageName = resolveInfo.activityInfo.packageName; userHandle = UserHandleCompat.myUserHandle(); } if (componentName == null || userHandle == null) { ... continue; } ... PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName); ArrayList
widgetsShortcutsList = mWidgetsList.get(pInfo); if (widgetsShortcutsList != null) { widgetsShortcutsList.add(o); } else { widgetsShortcutsList = new ArrayList<>(); widgetsShortcutsList.add(o); pInfo = new PackageItemInfo(packageName); mIconCache.getTitleAndIconForApp(packageName, userHandle, true /* userLowResIcon */, pInfo); pInfo.titleSectionName = mIndexer.computeSectionName(pInfo.title); mWidgetsList.put(pInfo, widgetsShortcutsList); tmpPackageItemInfos.put(packageName, pInfo); mPackageItemInfos.add(pInfo); } } // 排序. ... } }复制代码

在这里将不同应用的Widget放到同一个列表中然后在放到mWidgetsList中,以供应加载Widget列表。接着执行绑定过程,绑定过程我们在第三章介绍过,但是里面还有些东西在这里需要介绍一下,我们看源码知道其实Widget是通过适配器放置到WidgetsRecyclerView里面的,WidgetsRecyclerView是一个RecyclerView,而每个Widget视图是一个WidgetCell,那么WidgetCell是什么,我们看WidgetsListAdapter适配器,这个我们就不详细介绍了,在里面的onBindViewHolder方法中对WidgetCell进行了初始化,其中在里面会调动下面方法:

widget.applyFromAppWidgetProviderInfo(info, mWidgetPreviewLoader);复制代码

我们看看这个方法:

public void applyFromAppWidgetProviderInfo(LauncherAppWidgetProviderInfo info,            WidgetPreviewLoader loader) {        InvariantDeviceProfile profile =                LauncherAppState.getInstance().getInvariantDeviceProfile();        mInfo = info;        // TODO(hyunyoungs): setup a cache for these labels.        mWidgetName.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info));        int hSpan = Math.min(info.spanX, profile.numColumns);        int vSpan = Math.min(info.spanY, profile.numRows);        mWidgetDims.setText(String.format(mDimensionsFormatString, hSpan, vSpan));        mWidgetPreviewLoader = loader;    }复制代码

上面代码通过mWidgetName.setText显示名字,通过mWidgetDims.setText显示大小。最后给mWidgetPreviewLoader赋值,我们看到这个loader是从WidgetsListAdapter中传递进来的,在WidgetsListAdapter中,是通过LauncherAppState.getInstance().getWidgetCache()获取的,其实这个loader是在LauncherAppState初始化的时候就初始化了。

在WidgetCell初始化后调用了widget.ensurePreview()方法:

public void ensurePreview() {        ...        int[] size = getPreviewSize();        mActiveRequest = mWidgetPreviewLoader.getPreview(mInfo, size[0], size[1], this);    }复制代码

在这里调用mWidgetPreviewLoader.getPreview方法:

public PreviewLoadRequest getPreview(final Object o, int previewWidth,            int previewHeight, WidgetCell caller) {        String size = previewWidth + "x" + previewHeight;        WidgetCacheKey key = getObjectKey(o, size);        PreviewLoadTask task = new PreviewLoadTask(key, o, previewWidth, previewHeight, caller);        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);        return new PreviewLoadRequest(task);    }复制代码

在这里执行了一个异步任务PreviewLoadTask,我们看一下这个异步任务,首先看doInBackground方法:

...preview = generatePreview(launcher, mInfo, unusedBitmap, mPreviewWidth, mPreviewHeight);...复制代码

接着看generatePreview方法:

Bitmap generatePreview(Launcher launcher, Object info, Bitmap recycle,            int previewWidth, int previewHeight) {        if (info instanceof LauncherAppWidgetProviderInfo) {            return generateWidgetPreview(launcher, (LauncherAppWidgetProviderInfo) info,                    previewWidth, recycle, null);        } else {            return generateShortcutPreview(launcher,                    (ResolveInfo) info, previewWidth, previewHeight, recycle);        }    }复制代码

我们看到是生成一个Bitmap,然后调用generateWidgetPreview生成Bitmap:

public Bitmap generateWidgetPreview(Launcher launcher, LauncherAppWidgetProviderInfo info,            int maxPreviewWidth, Bitmap preview, int[] preScaledWidthOut) {        // Load the preview image if possible        if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE;        Drawable drawable = null;        if (info.previewImage != 0) {            // 获取相对应的drawable            drawable = mManager.loadPreview(info);            ...        }        // Draw the scaled preview into the final bitmap        int x = (preview.getWidth() - previewWidth) / 2;        if (widgetPreviewExists) {            drawable.setBounds(x, 0, x + previewWidth, previewHeight);            drawable.draw(c);        } else {            ...            for (int i = 0; i < spanX; i++, tx += tileW) {                float ty = 0;                for (int j = 0; j < spanY; j++, ty += tileH) {                    dst.offsetTo(tx, ty);                    c.drawBitmap(tileBitmap, src, dst, p);                }            }            ...            try {                Drawable icon = mutateOnMainThread(mManager.loadIcon(info, mIconCache));                if (icon != null) {                    ...                    icon.draw(c);                }            } catch (Resources.NotFoundException e) { }            c.setBitmap(null);        }        int imageHeight = Math.min(preview.getHeight(), previewHeight + mProfileBadgeMargin);        return mManager.getBadgeBitmap(info, preview, imageHeight);    }复制代码

整个过程就是从系统加载出Widget对应的Drawable然后绘制到Bitmap上面返回,然后在onPostExecute方法中将该图片添加到WidgetCell上面,就显示到了WidgetCell列表中。整个加载就完成了。

Widget的添加:

我们之前讲过,Widget列表最后是绑定到WidgetsContainerView中的,而我们将Widget放置到桌面是通过长按拖拽到桌面来完成的,因此我们可以知道添加的触发事件是通过长按事件来触发的,因为我们找到WidgetsContainerView中的长按事件:

@Override    public boolean onLongClick(View v) {        ...        boolean status = beginDragging(v);        if (status && v.getTag() instanceof PendingAddWidgetInfo) {            WidgetHostViewLoader hostLoader = new WidgetHostViewLoader(mLauncher, v);            boolean preloadStatus = hostLoader.preloadWidget();            ...            mLauncher.getDragController().addDragListener(hostLoader);        }        return status;    }复制代码

首先调用beginDragging方法:

private boolean beginDragging(View v) {        if (v instanceof WidgetCell) {            if (!beginDraggingWidget((WidgetCell) v)) {                return false;            }        } else {            Log.e(TAG, "Unexpected dragging view: " + v);        }        // 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;    }复制代码

如果是Widget的视图(WidgetCell)也就是长按的是Widget布局则调用beginDraggingWidget方法:

private boolean beginDraggingWidget(WidgetCell v) {        WidgetImageView image = (WidgetImageView) v.findViewById(R.id.widget_preview);        ...        if (createItemInfo instanceof PendingAddWidgetInfo) {            ...            Bitmap icon = image.getBitmap();            float minScale = 1.25f;            int maxWidth = Math.min((int) (icon.getWidth() * minScale), size[0]);            ...            preview = getWidgetPreviewLoader().generateWidgetPreview(mLauncher,                    createWidgetInfo.info, maxWidth, null, previewSizeBeforeScale);            ...            scale = bounds.width() / (float) preview.getWidth();        } else {            // shortcut            ...        }        // Don't clip alpha values for the drag outline if we're using the default widget preview        boolean clipAlpha = !(createItemInfo instanceof PendingAddWidgetInfo &&                (((PendingAddWidgetInfo) createItemInfo).previewImage == 0));        // Start the drag        mLauncher.lockScreenOrientation();        mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, preview, clipAlpha);        mDragController.startDrag(image, preview, this, createItemInfo,                bounds, DragController.DRAG_ACTION_COPY, scale);        preview.recycle();        return true;    }复制代码

上面代码中的generateWidgetPreview方法我们在上面已经讲过了,就是生产WidgetCell图片的,然后锁定屏幕旋转,然后调用onDragStartedWithItem方法:

public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {        int[] size = estimateItemSize(info, false);        // The outline is used to visualize where the item will land if dropped        mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, size[0], size[1], clipAlpha);    }复制代码

整个方法在拖拽中讲过,就是在workspace中生成一个拖拽view的轮廓边框,然后调用mDragController.startDrag方法,之后的过程在拖拽章节中有很详细的讲解,所以在此不再重复了,没看过拖拽的可以去看拖拽过程详解。下面只是个提示过程。

在放置到桌面时会调用onDrop方法,然后调用onDropExternal方法,然后调用addPendingItem方法:

public void addPendingItem(PendingAddItemInfo info, long container, long screenId,                               int[] cell, int spanX, int spanY) {        switch (info.itemType) {            case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:                int span[] = new int[2];                span[0] = spanX;                span[1] = spanY;                addAppWidgetFromDrop((PendingAddWidgetInfo) info,                        container, screenId, cell, span);                break;            ...            }    }复制代码

如果是Widget则调用addAppWidgetFromDrop方法,然后调用addAppWidgetImpl方法,然后调用completeAddAppWidget方法,最后调用mWorkspace.addInScreen方法就讲WidgetCell添加到了桌面上。

Widget的大小调节:

我们在桌面上添加完Widget后,如果长按你会发现在Widget四个边缘会出现拖动框,如果拖动可以调节小插件的大小,那么这个拖动框在哪里添加的呢,我们看一下,其实这个方法是DragLayer中的addResizeFrame方法,这个方法是在Workspace中的onDrop方法中调用的,也就是放到桌面上的时候就添加了。

我们看一下这个方法:

public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget,            CellLayout cellLayout) {        AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(),                widget, cellLayout, this);        LayoutParams lp = new LayoutParams(-1, -1);        lp.customPosition = true;        addView(resizeFrame, lp);        mResizeFrames.add(resizeFrame);        resizeFrame.snapToWidget(false);    }复制代码

首先创建AppWidgetResizeFrame对象,传入参数LauncherAppWidgetHostView、CellLayout,还有draglayer:

public AppWidgetResizeFrame(Context context,            LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) {        //初始化数据        ...        // 初始化左侧拖动点        mLeftHandle = new ImageView(context);        mLeftHandle.setImageResource(R.drawable.ic_widget_resize_handle);        lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,                Gravity.LEFT | Gravity.CENTER_VERTICAL);        lp.leftMargin = handleMargin;        addView(mLeftHandle, lp);        // 初始化右侧拖动点        // 初始化顶部拖动点        // 初始化底部拖动点        ...    }复制代码

拖动调整大小是在DragLayer中的onTouchEvent方法中:

@Override    public boolean onTouchEvent(MotionEvent ev) {        ...        if (mCurrentResizeFrame != null) {            handled = true;            switch (action) {                case MotionEvent.ACTION_MOVE:                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);                    break;                case MotionEvent.ACTION_CANCEL:                case MotionEvent.ACTION_UP:                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);                    mCurrentResizeFrame.onTouchUp();                    mCurrentResizeFrame = null;            }        }        if (handled) return true;        return mDragController.onTouchEvent(ev);    }复制代码

由上面代码可以看出拖拽的的时候调用visualizeResizeForDelta方法,手指抬起的时候调用visualizeResizeForDelta方法和onTouchUp方法,我们先看visualizeResizeForDelta方法:

private void visualizeResizeForDelta(int deltaX, int deltaY, boolean onDismiss) {        updateDeltas(deltaX, deltaY);        DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();        if (mLeftBorderActive) {            lp.x = mBaselineX + mDeltaX;            lp.width = mBaselineWidth - mDeltaX;        } else if (mRightBorderActive) {            lp.width = mBaselineWidth + mDeltaX;        }        if (mTopBorderActive) {            lp.y = mBaselineY + mDeltaY;            lp.height = mBaselineHeight - mDeltaY;        } else if (mBottomBorderActive) {            lp.height = mBaselineHeight + mDeltaY;        }        resizeWidgetIfNeeded(onDismiss);        requestLayout();    }复制代码

首先调用updateDeltas方法:

public void updateDeltas(int deltaX, int deltaY) {        if (mLeftBorderActive) {            mDeltaX = Math.max(-mBaselineX, deltaX);             mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX);        } else if (mRightBorderActive) {            mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX);            mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX);        }        if (mTopBorderActive) {            mDeltaY = Math.max(-mBaselineY, deltaY);            mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY);        } else if (mBottomBorderActive) {            mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY);            mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY);        }    }复制代码

主要是根据上下左右点来计算mDeltaX和mDeltaY的值,然后设定DragLayer.LayoutParams的值,然后调用resizeWidgetIfNeeded方法:

private void resizeWidgetIfNeeded(boolean onDismiss) {        ...        if (mLeftBorderActive) {            cellXInc = Math.max(-cellX, hSpanInc);            cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc);            hSpanInc *= -1;            hSpanInc = Math.min(cellX, hSpanInc);            hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);            hSpanDelta = -hSpanInc;        }        ...        // Update the widget's dimensions and position according to the deltas computed above        if (mLeftBorderActive || mRightBorderActive) {            spanX += hSpanInc;            cellX += cellXInc;            if (hSpanDelta != 0) {                mDirectionVector[0] = mLeftBorderActive ? -1 : 1;            }        }        ...        if (mCellLayout.createAreaForResize(cellX, cellY, spanX, spanY, mWidgetView,                mDirectionVector, onDismiss)) {            lp.tmpCellX = cellX;            lp.tmpCellY = cellY;            lp.cellHSpan = spanX;            lp.cellVSpan = spanY;            mRunningVInc += vSpanDelta;            mRunningHInc += hSpanDelta;            if (!onDismiss) {                updateWidgetSizeRanges(mWidgetView, mLauncher, spanX, spanY);            }        }        mWidgetView.requestLayout();    }复制代码

这里计算拖拽过程中的参数,然后调用updateWidgetSizeRanges方法:

static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher,            int spanX, int spanY) {        getWidgetSizeRanges(launcher, spanX, spanY, sTmpRect);        widgetView.updateAppWidgetSize(null, sTmpRect.left, sTmpRect.top,                sTmpRect.right, sTmpRect.bottom);    }复制代码

首先调用getWidgetSizeRanges方法来设定sTmpRect参数,然后调用widgetView.updateAppWidgetSize方法更新widget大小,然后调用mWidgetView.requestLayout方法刷新widget。

我们再看onTouchUp方法:

public void onTouchUp() {        int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();        int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();        ...        post(new Runnable() {            @Override            public void run() {                snapToWidget(true);            }        });    }复制代码

这个方法是调整完widget大小手指离开屏幕时调用的,主要调用了snapToWidget方法,这个方法代码就不贴了,主要是四个点的动画,代码很简单。

到此widget的加载、添加以及大小调整就介绍完了,整个过程也是比较复杂的,所以还是要好好熟悉一下。

最后

同步发布:

Github地址:

微信公众账号:Code-MX

注:本文原创,转载请注明出处,多谢。

你可能感兴趣的文章
173. Binary Search Tree Iterator
查看>>
[python基础知识]python内置函数map/reduce/filter
查看>>
基因家族收缩和扩张分析 & Selective loss pathway & 泛基因组
查看>>
HDU2089 ------不要62(数位dp)
查看>>
hdu4756 Install Air Conditioning(MST + 树形DP)
查看>>
Android爬坑之旅之FileProvider(Failed to find configured root that contains)
查看>>
(四)Thread.join的作用和原理
查看>>
Watercolor Logos
查看>>
网络安全与机器学习(一):网络安全中的机器学习算法
查看>>
Egret场景切换管理类切换和单例使用方法
查看>>
linux 用户和用户组命令
查看>>
CSS3田字格列表的样式编写
查看>>
Docker+UPX 构建更小的镜像
查看>>
Centos下安装Python3.6和Python2共存
查看>>
深入理解ES6笔记(二)字符串和正则表达
查看>>
一个小众的php方法:hypot
查看>>
python操作redis(二)
查看>>
WordPress 主题开发:从入门到精通(必读)
查看>>
Vue入坑记
查看>>
SpringBoot使用AOP+注解实现简单的权限验证
查看>>