Launcher从总体架构上来看采用了MVC模式。其中Launcher.java为控制器,LauncherModel为模型;XML界面配置文件为视图,其中Workspace为视图容器。模型中操作数据库模型,保持数据模型和数据库的一致。控制器同步视图和模型,视图和模型之间不直接关联,通过唯一通过控制器发生关联。
举个例子,当Gallery.apk应用被删除时,数据模型(LauncherModel)接收到ACTION_PACKAGE_REMOVED消息,然后在消息处理中,从数据模型中删除该应用的信息,从数据库记录中删除该应用的信息,然后通过回调(Callbacks)去更新视图(Workspace),而这个回调就是在Launcher中实现的。(LauncherModel——>Launcher(回调)——>Workspace)
再举一个例子,当Gallery应用移动位置时,假设有空位置可以移动,Gallery应用从原位置移动到目标位置过程。View处理移动事件,Controller 将DragTarget设置为数据模型(LauncherModel)。所以最终触发数据模型的放置动作,数据判断有无空位置放置,如果有成功放置,更新该应用的数据模型和数据库记录。 (Workspace——>Launcher(回调)——>LauncherModel)
(LauncherProvider)数据库帮助类。实现了数据库的常见,默认配置的加载;常见增删改查操作的重载,增删改查的适配方便具体使用环境调用。
public class LauncherProvider extends ContentProvider { private static final String DATABASE_NAME = "launcher.db"; private SQLiteOpenHelper mOpenHelper; @Override public boolean onCreate() { mOpenHelper = new DatabaseHelper(getContext()); return true; } public String getType(Uri uri); public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder); public Uri insert(Uri uri, ContentValues initialValues); public int bulkInsert(Uri uri, ContentValues[] values); public int delete(Uri uri, String selection, String[] selectionArgs); public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs); private void sendNotify(Uri uri); private static class DatabaseHelper extends SQLiteOpenHelper { private static final String TAG_FAVORITES = "favorites"; private static final String TAG_FAVORITE = "favorite"; private static final String TAG_SHORTCUT = "shortcut"; DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); ...... } public void onCreate(SQLiteDatabase db) { if (LOGD) Log.d(TAG, "creating new launcher database"); db.execSQL("CREATE TABLE favorites (" + "_id INTEGER PRIMARY KEY," + "title TEXT," + "intent TEXT," + "container INTEGER," + "screen INTEGER," + "cellX INTEGER," + "cellY INTEGER," + "spanX INTEGER," + "spanY INTEGER," + "itemType INTEGER," + "appWidgetId INTEGER NOT NULL DEFAULT -1," + "isShortcut INTEGER," + "iconType INTEGER," + "iconPackage TEXT," + "iconResource TEXT," + "icon BLOB," + "uri TEXT," + "displayMode INTEGER" + ");"); ...... if (!convertDatabase(db)) { // Populate favorites table with initial favorites loadFavorites(db); } } private boolean convertDatabase(SQLiteDatabase db); private int copyFromCursor(SQLiteDatabase db, Cursor c); public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion); private boolean updateContactsShortcuts(SQLiteDatabase db); private void convertWidgets(SQLiteDatabase db); /*Loads the default set of favorite packages from an xml file. 【R.xml.default_workspace】*/ private int loadFavorites(SQLiteDatabase db); private boolean addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a,PackageManager packageManager, Intent intent); private boolean addAppWidget(SQLiteDatabase db, ContentValues values, TypedArray a,PackageManager packageManager); private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,int spanX, int spanY); private boolean addUriShortcut(SQLiteDatabase db, ContentValues values,TypedArray a); static String buildOrWhereString(String column, int[] values); }
public class LauncherModel extends BroadcastReceiver { /*回调函数,当数据变更时,这些回调会被调用*/ public void initialize(Callbacks callbacks){mCallbacks = new WeakReference<Callbacks>(callbacks);} public interface Callbacks { public boolean setLoadOnResume(); public int getCurrentWorkspaceScreen(); public void startBinding(); public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end); public void bindFolders(HashMap<Long,FolderInfo> folders); public void finishBindingItems(); public void bindAppWidget(LauncherAppWidgetInfo info); public void bindAllApplications(ArrayList<ApplicationInfo> apps); public void bindAppsAdded(ArrayList<ApplicationInfo> apps); public void bindAppsUpdated(ArrayList<ApplicationInfo> apps); public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent); public boolean isAllAppsVisible(); } /*下面为数据模型的维护操作,将增删改查的数据项更新到数据库*/ static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container, int screen, int cellX, int cellY); static void moveItemInDatabase(Context context, ItemInfo item, long container, int screen, int cellX, int cellY); static boolean shortcutExists(Context context, String title, Intent intent); static void addItemToDatabase(Context context, ItemInfo item, long container, int screen, int cellX, int cellY, boolean notify); static void updateItemInDatabase(Context context, ItemInfo item); static void deleteItemFromDatabase(Context context, ItemInfo item); static void deleteUserFolderContentsFromDatabase(Context context, UserFolderInfo info); /* 程序包变化处理器: ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and ACTION_PACKAGE_CHANGED.*/ public void onReceive(Context context, Intent intent); /*加载过程管理*/ public void startLoader(Context context, boolean isLaunching); public void stopLoader(); private class LoaderTask implements Runnable { private void loadAndBindWorkspace();//从数据库将数据条目读到模型,并调用回调,将模型加载到视图 public void run() { loadAndBindWorkspace(); //加载和绑定workspace, 这里简化了,这里面还有些性能管理的代码 } /*其他函数略*/ }(Workspace)视图容器类,负责视图的布局工作;用户事件的分发与处理;拖放的实现;子视图的更新操作等。
public class Workspace extends ViewGroup{ private static class WorkspaceOvershootInterpolator implements Interpolator { private static final float DEFAULT_TENSION = 1.3f; private float mTension; public WorkspaceOvershootInterpolator(); public void setDistance(int distance); public void disableSettle(); public float getInterpolation(float t); } public Workspace(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); ...... initWorkspace(); } private void initWorkspace() { Context context = getContext(); mScrollInterpolator = new WorkspaceOvershootInterpolator(); mScroller = new Scroller(context, mScrollInterpolator); mCurrentScreen = mDefaultScreen; Launcher.setScreen(mCurrentScreen); LauncherApplication app = (LauncherApplication)context.getApplicationContext(); mIconCache = app.getIconCache(); final ViewConfiguration configuration = ViewConfiguration.get(getContext()); mTouchSlop = configuration.getScaledTouchSlop(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); } void setLauncher(Launcher launcher); /*重载父类函数,实现添加和布局功能*/ public void addView(View child, int index, LayoutParams params); public void addView(View child); public void addView(View child, int index); public void addView(View child, int width, int height); public void addView(View child, LayoutParams params); protected void dispatchDraw(Canvas canvas); protected void onAttachedToWindow(); protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec); protected void onLayout(boolean changed, int left, int top, int right, int bottom); /*简化外部调用的困难,最简添加视图的函数*/ int getCurrentScreen(); void setCurrentScreen(int currentScreen); void addInCurrentScreen(View child, int x, int y, int spanX, int spanY); void addInCurrentScreen(View child, int x, int y, int spanX, int spanY, boolean insert); void addInScreen(View child, int screen, int x, int y, int spanX, int spanY); void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert); CellLayout.CellInfo findAllVacantCells(boolean[] occupied); /*用户事件处理函数*/ public void setOnLongClickListener(OnLongClickListener l) public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate); public boolean dispatchUnhandledMove(View focused, int direction); public void addFocusables(ArrayList<View> views, int direction, int focusableMode); public boolean dispatchTouchEvent(MotionEvent ev); public boolean onInterceptTouchEvent(MotionEvent ev); /*拖动操作的实现过程*/ public void setDragController(DragController dragController); public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo); public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo); public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo); public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo); public boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo); /*计算对象可安放的区域*/ public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo, Rect recycle); }
2.Drop& Drag模型:
2.1 DragSource:可以拖动的对象来源的容器,在launcher中主要有AllAppGridView,workspace等。void onDropCompleted(View target, boolean success,int x,int y);
3.Touch event总结:
由于launcher的事件比较多比较复杂,所以在事件处理的时候一般采用rootview先用onInterceptTouchEvent(MotionEvent)拦截所有的touch事件,经过判断后分发给childview。三、几种问题的解决方式
1.将所有的应用都排列在桌面上2.动态增加屏幕
动态增加屏幕是通过worksapce .addchild(view)的方式实现。基本思路是:首先预先规定所允许的最大的屏幕数,然后在需要增加屏幕而且当前屏幕数没有超过最大屏幕数的时候通过(CellLayout)mInflater.inflate(R.layout.workspace_screen,null)创建一个celllayout实例出来,然后通过addchild把它加入进去。在屏幕上的item被删除时通过从最后一屏起判断屏幕上是否有item,如果有的话保留,没有的话则删除最后一屏,以此类推。com.estrongs.android.taskmanager.TaskManager为className。
workspace的布局如下: (0,0)-(1,0)-(2,0)-(3,0)-(4,0)
(0,1)- (1,1)-(2,1)-(3,1)-(4,1)
(0,2)- (1,2)-(2,2)-(3,2)-(4,2)
launcher:spany="2"/>
4.改变主界面的排列方式要修改桌面的排列方式,如下,先根据横竖屏设置修改workspace_screen.xml里shortAxisCells和longAxisCells的参数,然后在Launcher.java中修改NUMBER_CELLS_X和NUMBER_CELLS_Y的值,在2.3版本中刚开始往数据库中添加item的时候会去判断,如果不修改NUMBER_CELLS_X和NUMBER_CELLS_Y的话会导致一部分的item显示不出来,导致预制apk的失败。
5.增加worksapce上的屏数四、xml配置文件
1.workspace_screen.xmlshortAxisCells和longAxisCells决定一个workspace(即CellLayout)上可以容纳的item的个数为shortAxisCells*longAxisCells.
2. application_boxed.xml所有应用程序和系统文件夹中item的定义。
3.application.xml