上一章墨香带你学Launcher之(六)- 拖拽 我们介绍了Launcher的拖拽过程,涉及到的范围比较广,包括图标的拖拽,桌面上CellLayout的拖拽,小部件的拖拽,以及跨不同部件的拖拽,设计思想非常巧妙,不过整个流程相对也比较好掌握,只要跟着上一章的流程自己多跟踪几遍基本就熟悉了。按照计划本章我们继续学习Launcher的Widget的加载、添加以及Widget的大小调节。
 
其实我们在第二章墨香带你学Launcher之(二)-数据加载流程 介绍过Widget数据的加载,相对只是简单的做了介绍,下面我们稍微讲的详细点。
我们知道Widget的数据加载开始在LauncherModel中的updateWidgetsModel方法中,我们看下代码:
1 2 3 4 5 6 7 8 void  updateWidgetsModel (boolean  refresh)  {    PackageManager  packageManager  =  mApp.getContext().getPackageManager();     final  ArrayList<Object> widgetsAndShortcuts = new  ArrayList <Object>();     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的,代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public  static  List<LauncherAppWidgetProviderInfo> getWidgetProviders (Context context,                                                                          boolean  refresh)  {        ArrayList<LauncherAppWidgetProviderInfo> results =                 new  ArrayList <LauncherAppWidgetProviderInfo>();         try  {             synchronized  (sBgLock) {                 if  (sBgWidgetProviders == null  || refresh) {                     HashMap<ComponentKey, LauncherAppWidgetProviderInfo> tmpWidgetProviders                             = new  HashMap <>();                     AppWidgetManagerCompat  wm  =  AppWidgetManagerCompat.getInstance(context);                     LauncherAppWidgetProviderInfo info;                     List<AppWidgetProviderInfo> 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<CustomAppWidget> 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);                     }                                                               sBgWidgetProviders = tmpWidgetProviders;                 }                 results.addAll(sBgWidgetProviders.values());                 return  results;             }         } catch  (Exception e) {             ...             }     } 
我们看到首先是初始化AppWidgetManagerCompat,我们之前介绍过带有Compat的是兼容组件,我们看看是怎么兼容的,
我们下面代码:
1 2 3 4 5 6 7 8 9 10 11 12 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列表对象:
1 List<AppWidgetProviderInfo> widgets = wm.getAllProviders(); 
getAllProviders()方法是一个抽象方法,所以我们看哪里进行了复写,
可以看到还是上面两个兼容类复写了该方法,我们看这个两个类中做了什么处理,先看V16中的:
1 2 3 4 @Override public  List<AppWidgetProviderInfo> getAllProviders ()  {    return  mAppWidgetManager.getInstalledProviders(); } 
我们再看mAppWidgetManager这个是在哪里初始化,
1 2 3 4 5 6 7 8 9 10 @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()方法:
1 2 3 4 5 6 7 8 @Override public  List<AppWidgetProviderInfo> getAllProviders ()  {    ArrayList<AppWidgetProviderInfo> providers = new  ArrayList <AppWidgetProviderInfo>();     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的初始化时通过 
1 LauncherAppWidgetProviderInfo.fromProviderInfo(context, pInfo); 
方法进行初始化的,我们再看LauncherAppWidgetProviderInfo又继承AppWidgetProviderInfo,越来越怪,我们接着看fromProviderInfo(context, pInfo)方法:
1 2 3 4 5 6 7 8 9 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对象,那么这个对象构造函数中有什么:
1 2 3 4 public  LauncherAppWidgetProviderInfo (Parcel in)  {    super (in);     initSpans(); } 
这个构造函数调用了initSpans方法,我们接着追寻:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private  void  initSpans ()  {        LauncherAppState  app  =  LauncherAppState.getInstance();         InvariantDeviceProfile  idp  =  app.getInvariantDeviceProfile();                  Rect  paddingLand  =  idp.landscapeProfile.getWorkspacePadding(false  );         Rect  paddingPort  =  idp.portraitProfile.getWorkspacePadding(false  );                           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);                           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<ComponentKey, LauncherAppWidgetProviderInfo>这个Widget集合,最后通过
1 mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts); 
将这个集合放到WidgetsModel中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 public  void  setWidgetsAndShortcuts (ArrayList<Object> rawWidgetsShortcuts)  {        ...         HashMap<String, PackageItemInfo> tmpPackageItemInfos = new  HashMap <>();                  ...         InvariantDeviceProfile  idp  =  LauncherAppState.getInstance().getInvariantDeviceProfile();                  for  (Object o: rawWidgetsShortcuts) {             String  packageName  =  "" ;             UserHandleCompat  userHandle  =  null ;             ComponentName  componentName  =  null ;             if  (o instanceof  LauncherAppWidgetProviderInfo) {                 LauncherAppWidgetProviderInfo  widgetInfo  =  (LauncherAppWidgetProviderInfo) o;                                  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<Object> 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  , pInfo);                 pInfo.titleSectionName = mIndexer.computeSectionName(pInfo.title);                 mWidgetsList.put(pInfo, widgetsShortcutsList);                 tmpPackageItemInfos.put(packageName,  pInfo);                 mPackageItemInfos.add(pInfo);             }         }                  ...         }     } 
在这里将不同应用的Widget放到同一个列表中然后在放到mWidgetsList中,以供应加载Widget列表。接着执行绑定过程,绑定过程我们在第三章墨香带你学Launcher之(三)-绑定屏幕、图标、文件夹和Widget 介绍过,但是里面还有些东西在这里需要介绍一下,我们看源码知道其实Widget是通过适配器放置到WidgetsRecyclerView里面的,WidgetsRecyclerView是一个RecyclerView,而每个Widget视图是一个WidgetCell,那么WidgetCell是什么,我们看WidgetsListAdapter适配器,这个我们就不详细介绍了,在里面的onBindViewHolder方法中对WidgetCell进行了初始化,其中在里面会调动下面方法:
1 widget.applyFromAppWidgetProviderInfo(info, mWidgetPreviewLoader); 
我们看看这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 public  void  applyFromAppWidgetProviderInfo (LauncherAppWidgetProviderInfo info,            WidgetPreviewLoader loader)  {       InvariantDeviceProfile  profile  =                 LauncherAppState.getInstance().getInvariantDeviceProfile();        mInfo = info;                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()方法:
1 2 3 4 5 public  void  ensurePreview ()  {       ...        int [] size = getPreviewSize();        mActiveRequest = mWidgetPreviewLoader.getPreview(mInfo, size[0 ], size[1 ], this );    } 
在这里调用mWidgetPreviewLoader.getPreview方法:
1 2 3 4 5 6 7 8 9 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方法:
1 2 3 ... preview = generatePreview(launcher, mInfo, unusedBitmap, mPreviewWidth, mPreviewHeight); ... 
接着看generatePreview方法:
1 2 3 4 5 6 7 8 9 10 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public  Bitmap generateWidgetPreview (Launcher launcher, LauncherAppWidgetProviderInfo info,             int  maxPreviewWidth, Bitmap preview, int [] preScaledWidthOut)  {                 if  (maxPreviewWidth < 0 ) maxPreviewWidth = Integer.MAX_VALUE;         Drawable  drawable  =  null ;         if  (info.previewImage != 0 ) {                          drawable = mManager.loadPreview(info);             ...         }                  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列表最后是绑定到WidgetsContainerView中的,而我们将Widget放置到桌面是通过长按拖拽到桌面来完成的,因此我们可以知道添加的触发事件是通过长按事件来触发的,因为我们找到WidgetsContainerView中的长按事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @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方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private  boolean  beginDragging (View v)  {    if  (v instanceof  WidgetCell) {         if  (!beginDraggingWidget((WidgetCell) v)) {             return  false ;         }     } else  {         Log.e(TAG, "Unexpected dragging view: "  + v);     }          if  (mLauncher.getDragController().isDragging()) {                  mLauncher.enterSpringLoadedDragMode();     }     return  true ; } 
如果是Widget的视图(WidgetCell)也就是长按的是Widget布局则调用beginDraggingWidget方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 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  {                          ...         }                  boolean  clipAlpha  =  !(createItemInfo instanceof  PendingAddWidgetInfo &&                 (((PendingAddWidgetInfo) createItemInfo).previewImage == 0 ));                  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方法:
1 2 3 4 5 6 public  void  onDragStartedWithItem (PendingAddItemInfo info, Bitmap b, boolean  clipAlpha)  {    int [] size = estimateItemSize(info, false );          mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, size[0 ], size[1 ], clipAlpha); } 
整个方法在拖拽中讲过,就是在workspace中生成一个拖拽view的轮廓边框,然后调用mDragController.startDrag方法,之后的过程在拖拽章节中有很详细的讲解,所以在此不再重复了,没看过拖拽的可以去看拖拽过程详解。下面只是个提示过程。
在放置到桌面时会调用onDrop方法,然后调用onDropExternal方法,然后调用addPendingItem方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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四个边缘会出现拖动框,如果拖动可以调节小插件的大小,那么这个拖动框在哪里添加的呢,我们看一下,其实这个方法是DragLayer中的addResizeFrame方法,这个方法是在Workspace中的onDrop方法中调用的,也就是放到桌面上的时候就添加了。
我们看一下这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @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方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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;         }         ...                           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方法:
1 2 3 4 5 6 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方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 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地址:https://github.com/yuchuangu85/Launcher3_mx/tree/launcher3_6.0 
Android开发群:192508518
微信公众账号:Code-MX
注:本文原创,转载请注明出处,多谢。