Analyze the Launcher in Android
It’s based on the android 4.0.3.
You can find the configuration ofthe Launcher in /LINUX/android/packages/apps/Launcher
In /res/xml/default_workspace.xml,you can find the file.
1. <favorite //程序快捷键属性标签
2. launcher:className="com.apical.radio.radioMainActivity" //该应用的类,点击图标时,需要启动的类
3. launcher:packageName="com.apical.radio" //该应用的包名
4. launcher:screen="1" //第1屏,0-4屏共5屏
5. launcher:x="0" //图标X位置,左上角第一个为0,向左递增,0-4共5个
6. l0auncher:y="0" //图标Y位置,左上角第一个为0,向下递增,0-2共3个
7. />
In default, android has 5 screen.If you want to change it, you can modify the property target.
1. //桌面Widget的标签
2. <appwidget //插件
3. launcher:className="de.dnsproject.clock_widget_main.Clock1AppWidgetProvider" //该应用的类
4. launcher:packageName="de.dnsproject.clock_widget_main" //该应用的包名
5. launcher:screen="1" //第1屏,0-4屏共5屏
6. launcher:x="2" //图标X位置,左上角第一个为0,向左递增,0-4共5个
7. launcher:y="1" //图标Y位置,左上角第一个为0,向下递增,0-2共3个
8. launcher:spanX="3" //在x方向上所占格数
9. launcher:spanY="2" /> //在y方向上所占格数
Widget is the same property of shortcut.Launcher.spanX and Launcher.spanY means the size of Widget. The formula is :minWidth = 72 * grids – 2. The height is the same way to account.
1. <search //搜索栏
2. launcher:screen="1" //第2屏
3. launcher:x="0" //图标X位置
4. launcher:y="1"/> //图标Y位置
This is the search bar partconfiguration. If you don’t need it, you can just delete it.
There are some configuration :
1. //default_workspace.xml中,支持的标签有:
2. favorite:应用程序快捷方式。
3. shortcut:链接,如网址,本地磁盘路径等。
4. search:搜索框。
5. clock:桌面上的钟表Widget
6. //支持的属性有:
7. launcher:title:图标下面的文字,目前只支持引用,不能直接书写字符串;
8. launcher:icon:图标引用;
9. launcher:uri:链接地址,链接网址用的,使用shortcut标签就可以定义一个超链接,打开某个网址。
10. launcher:packageName:应用程序的包名;
11. launcher:className:应用程序的启动类名;
12. launcher:screen:图标所在的屏幕编号;
13. launcher:x:图标在横向排列上的序号;
14. launcher:y:图标在纵向排列上的序号;
The analyze java file to Launcheris LauncherProvider.java. In this file, the loadFavorites method will analyzeit. This is the code:
1. //pass the source ID and database to default_workspace. Analyze thedata in xml and store to Launcher database. Return the targets number toanalyze.
2. private int loadFavorites(SQLiteDatabase db,int workspaceResourceId) {
3. //.........
4. int type;
5. while (((type = parser.next()) != XmlPullParser.END_TAG ||
6. parser.getDepth()> depth) && type != XmlPullParser.END_DOCUMENT)
7. if (type !=XmlPullParser.START_TAG) {
8. continue;
9. boolean added = false;
10. final String name =parser.getName();
11. TypedArray a =mContext.obtainStyledAttributes(attrs, R.styleable.Favorite);
12. long container =LauncherSettings.Favorites.CONTAINER_DESKTOP;
13. if (a.hasValue(R.styleable.Favorite_container)){
14. container =Long.valueOf(a.getString(R.styleable.Favorite_container));
15. String screen =a.getString(R.styleable.Favorite_screen);
16. String x =a.getString(R.styleable.Favorite_x);
17. String y =a.getString(R.styleable.Favorite_y);
18. // If we are adding to the hotseat,the screen is used as the position in the
19. // hotseat. This screen can't be atposition 0 because AllApps is in the
20. // zeroth position.
21. if (container ==LauncherSettings.Favorites.CONTAINER_HOTSEAT
22. && Integer.valueOf(screen) == allAppsButtonRank) {
23. throw newRuntimeException("Invalid screen position for hotseat item");
24. values.clear();
25. values.put(LauncherSettings.Favorites.CONTAINER, container);
26. values.put(LauncherSettings.Favorites.SCREEN, screen);
27. values.put(LauncherSettings.Favorites.CELLX, x);
28. values.put(LauncherSettings.Favorites.CELLY, y);
29. //解析xml里面的标签,从这里可以找到支持的标签类型和相关属性参数。
30. if (TAG_FAVORITE.equals(name)) {
31. long id =addAppShortcut(db, values, a, packageManager, intent);
32. added = id >= 0;
33. } else if (TAG_SEARCH.equals(name)){
34. added =addSearchWidget(db, values);
35. } else if (TAG_CLOCK.equals(name)){
36. added =addClockWidget(db, values);
37. } else if(TAG_APPWIDGET.equals(name)) {
38. added =addAppWidget(parser, attrs, type, db, values, a, packageManager);
39. } else if(TAG_SHORTCUT.equals(name)) {
40. long id =addUriShortcut(db, values, a);
41. added = id >= 0;
42. } else if (TAG_FOLDER.equals(name))
43. //.........
44. //folder属性里面的参数要多于2个,才能形成文件夹。
45. if(folderItems.size() < 2 && folderId >= 0) {
46. // Wejust delete the folder and any items that made it
47. deleteId(db, folderId);
48. if(folderItems.size() > 0) {
49. deleteId(db, folderItems.get(0));
50. added =false;
51. if (added) i++;
52. a.recycle();
53. //.........
54. return i;
It's justanalyze XML and write data to database.
The size of icon and title
The configuration file is in:
/res/values/dimens.xml
The values support kinds of languages. The change shouldconsider about it. If the system support different resolution, the icon shouldchanges as well.
The code below is the modify of cell of values-land:
1. <font color="rgb(0, 0, 0)"><fontface="Verdana,"><!-- Workspace cell size -->
2. <dimenname="workspace_cell_width_land">88dp</dimen>
3. <dimenname="workspace_cell_width_port">96dp</dimen>
4. <dimenname="workspace_cell_height_land">88dp</dimen>
5. <dimenname="workspace_cell_height_port">96dp</dimen>
6. <dimenname="workspace_width_gap_land">32dp</dimen>
7. <dimenname="workspace_width_gap_port">0dp</dimen>
8. <dimen name="workspace_height_gap_land">0dp</dimen>
9. <dimenname="workspace_height_gap_port">24dp</dimen>
10. <!--Folders -->
11. <dimen name="folder_preview_size">68dp</dimen>
12. <dimen name="folder_cell_width">86dp</dimen>
13. <dimen name="folder_cell_height">90dp</dimen>
14. <dimen name="folder_width_gap">3dp</dimen>
15. <dimen name="folder_height_gap">3dp</dimen>
16. <dimenname="folder_padding">6dp</dimen></font></font>
Add the Launcher icons into default-background
The size of the icons are different, so the system should addone background of each icon, so the look would be consistent. The file is inclass--Utilities.java. In Bitmap createIconBitmap(Drawable icon, Contextcontext) solves the problems.
1. static Bitmap createIconBitmap(Drawable icon, Context context) {
2. //...............
3. final intleft = (textureWidth-width) / 2;
4. final inttop = (textureHeight-height) / 2;
5. //For testing, add colorful back frame.
6. if (false)
7. // draw a big box for the icon for debugging
8. canvas.drawColor(sColors[sColorIndex]);
9. if (++sColorIndex >= sColors.length) sColorIndex = 0;
10. Paint debugPaint =new Paint();
11. debugPaint.setColor(0xffcccc00);
12. canvas.drawRect(left, top, left+width, top+height, debugPaint);
13. //add icon background picture OWL
14. if (true)
15. Bitmap backBitmap =BitmapFactory.decodeResource(context.getResources(),
16. R.drawable.apical_icon_bg);
17. int backWidth =backBitmap.getWidth();
18. int backHeight =backBitmap.getHeight();
19. if(backWidth !=sIconWidth || backHeight != sIconHeight)
20. Matrix matrix = new Matrix();
21. matrix.postScale((float)sIconWidth/backWidth,(float)sIconHeight/backHeight);
22. canvas.drawBitmap(Bitmap.createBitmap(backBitmap, 0, 0,backWidth, backHeight, matrix, true),
23. .0f,0.0f, null);
24. }else
25. canvas.drawBitmap(backBitmap, 0.0f, 0.0f, null);
26. //................
27. return bitmap;
In the code, the picture, R.drawable.apical_icon_bg, is addedas the default background, so the icon is the same size.
Change Launcher’s default_background.
The default_background is in the /framework/base/core/res/,and the picture is also in the directory. There is one way to set up thedefault_background in Launcher. Thepicture should be put in the class – Launcher.java -> onCreate() ->showFirstRunWorkspaceCling(). The function can only be called for the firststart up or default-set.
The code :
1.private void setDefaultWallPaper()
2. //修改默认背景 OWL test,可以在Framework替换默认静态图default_wallpaper
3. WallpaperManager mwallpaerManager;
4. mwallpaerManager =WallpaperManager.getInstance(this);
5. mwallpaerManager.setResource(R.drawable.launcher_default_bg);
6. catch (IOException e)
7. Log.e(TAG, "set defaultwallpaper error");
8. e.printStackTrace();
The background pictures is called by WallpaperManager, andthey are in /framework/base/core/res/res/drawable-nodpi
If the default-dynamic-picture needs to change the/framework/base/core/res/res/values/config.xml
1. <stringname="default_wallpaper_component">@null</string>
2. //change the null to theprogram’s name as below:
3. <stringname="default_wallpaper_component" translatable="false">包名/动态壁纸服务名</string>
The set of the wallpaperis in /values/wallpaper.xml
1. <resources>
2. <string-array name="wallpapers"translatable="false">
3. <item>wallpaper_01</item>
4. <item>wallpaper_02</item>
5. <item>wallpaper_03</item>
6. <item>wallpaper_04</item>
7. <item>wallpaper_05</item>
8. <item>wallpaper_06</item>
9. <item>wallpaper_07</item>
10. <item>wallpaper_08</item>
11. <item>wallpaper_09</item>
12. <item>wallpaper_10</item>
13. <item>wallpaper_11</item>
14. <item>wallpaper_12</item>
15. </string-array>
16. </resources>
The R.array.wallpaper is the file to configuration ofdefault-wallpaper.
This is the code to set up the wallpaper.
1. private void selectWallpaper(int position) {
2. WallpaperManager wpm = (WallpaperManager) getActivity().getSystemService(
3. Context.WALLPAPER_SERVICE);
4. wpm.setResource(mImages.get(position)); //设置壁纸
5. Activityactivity = getActivity();
6. activity.setResult(Activity.RESULT_OK);
7. activity.finish();
8. } catch (IOException e) {
9. Log.e(TAG, "Failed to setwallpaper: " + e);
Init of Launcher
The picture show the process to initial the Launcher.
Activity Manager send the intent to start Launcher.
1. Intent intent = new Intent(mTopAction, mTopData != null ?
2. Uri.parse(mTopData) : null);
3. intent.setComponent(mTopComponent);
4. if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL)
5. intent.addCategory(Intent.CATEGORY_HOME);
6. startActivityLocked(null, intent, null, null, 0, aInfo,
7. null,null, 0, 0, 0, false, false);
If system exchange the Launcher, itshould add the action.MAIN, category.HOME, category.DEFAULT to <intent-filter>.If there are multiple Launchers added to the program, the system will pop onewindow to let you choose the initial.
This is the target ofApplication:
1. <application
2. android:name="com.android.launcher2.LauncherApplication"
3. android:label="@string/application_name"
4. android:icon="@drawable/ic_launcher_home"
5. android:hardwareAccelerated="@bool/config_hardwareAccelerated"
6. android:largeHeap="@bool/config_largeHeap"
7. android:configChanges="locale">
8. </application>
Android has four classes’ defines should be put intoapplication target, the default uses the system’s application class. If youreload it, you should write the new application’s class name to the nameproperty. Application will be load when start.
1. LauncherApplicationapp = ((LauncherApplication)getApplication());
The initialization to the Launcher.
- @Override
- public void onCreate()
- super.onCreate();
- //get the size of the screen to tell the type of the devices
- final int screenSize = getResources().getConfiguration().screenLayout &
- Configuration.SCREENLAYOUT_SIZE_MASK;
- sIsScreenLarge = screenSize == Configuration.SCREENLAYOUT_SIZE_LARGE ||
- screenSize == Configuration.SCREENLAYOUT_SIZE_XLARGE;
- //screen dense
- sScreenDensity = getResources().getDisplayMetrics().density;
- //IconCahe stores all the data about icon pictures.
- //to improve the effecting to picture the screen.
- mIconCache = new IconCache(this);
- //data load class. LauncherModel and
- //Model function, manage the data and initialize the data.
- mModel = new LauncherModel(this, mIconCache);
- //below registers the listener, the notify about APK update and delete.
- //After get the notify, update the data of Launcher. It will only load once.
- IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- filter.addDataScheme("package");
- registerReceiver(mModel, filter);
- filter = new IntentFilter();
- filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
- filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
- filter.addAction(Intent.ACTION_LOCALE_CHANGED);
- filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
- registerReceiver(mModel, filter);
- filter = new IntentFilter();
- filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
- registerReceiver(mModel, filter);
- filter = new IntentFilter();
- filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
- registerReceiver(mModel, filter);
- //contentresolver manage all the program’s contentprovider instances.
- ContentResolver resolver = getContentResolver();
- //register the content obvious, listene the data change , and recall
- resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true, mFavoritesObserver);
The code above is just for initial the key class ofLauncher, register some listeners. Listen the update about the install, delete andupdate the apps leads to change the data about the Launcher. All the data aboutLauncher is provided by contentprovider. The register listener interface is :
1. private final ContentObserver mFavoritesObserver = newContentObserver(new Handler())
2. @Override
3. public voidonChange(boolean selfChange)
4. //重新加载界面数据
5. mModel.startLoader(LauncherApplication.this, false);
LauncherSettings.Favorites.CONTENT_URI calls the mModel.startLoader()when changes and reload the data of Launchers. The rest code is getting thescreen dense, the size of screen etc.
Initial of the Launcher.java
Launcher.java is the main class, and it is Activity.
The code :
1. @Override
2. protected void onCreate(Bundle savedInstanceState)
3. super.onCreate(savedInstanceState);
4. //获取Application实例
5. LauncherApplication app =((LauncherApplication)getApplication());
6. //LauncherModel类里面获取Launcher的对象引用
7. mModel =app.setLauncher(this);
8. //获取IconCache,IconCache在Application里面初始化
9. mIconCache =app.getIconCache();
10. mDragController = new DragController(this);
11. mInflater = getLayoutInflater();
12. mAppWidgetManager =AppWidgetManager.getInstance(this);
13. //监听widget改变,以后在Model里面回调处理的结果
14. mAppWidgetHost = new LauncherAppWidgetHost(this,APPWIDGET_HOST_ID);
15. mAppWidgetHost.startListening();
16. //这个是设置Launcher的跟踪调试文件,下面很多信息会写到这个文件里面。
17. if (PROFILE_STARTUP)
18. android.os.Debug.startMethodTracing(
19. Environment.getExternalStorageDirectory() +"/launcher");
20. //读取本地配置,保存更新配置,清空IconCache
21. checkForLocaleChange();
22. setContentView(R.layout.launcher);
23. //对所有的UI控件进行加载和配置
24. setupViews();
25. //显示操作提示,第一次启动的时候才会显示
26. showFirstRunWorkspaceCling();
27. //注册监控Launcher数据库变化
28. registerContentObservers();
29. //锁住APP,初始化不能操作。
30. lockAllApps();
31. mSavedState = savedInstanceState;
32. restoreState(mSavedState);
33. // Update customization drawer _after_ restoringthe states
34. if (mAppsCustomizeContent != null)
35. mAppsCustomizeContent.onPackagesUpdated();
36. if (PROFILE_STARTUP)
37. android.os.Debug.stopMethodTracing();
38. //加载启动数据,所有界面数据(快捷方式、folder、widget、allApp)等在loader里面加载,这部分后面我会详细分析。
39. if (!mRestoring) {
40. mModel.startLoader(this, true);
41. if (!mModel.isAllAppsLoaded())
42. ViewGroupappsCustomizeContentParent = (ViewGroup) mAppsCustomizeContent.getParent();
43. mInflater.inflate(R.layout.apps_customize_progressbar,appsCustomizeContentParent);
44. // For handling default keys
45. mDefaultKeySsb = new SpannableStringBuilder();
46. Selection.setSelection(mDefaultKeySsb, 0);
47. IntentFilter filter = newIntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
48. registerReceiver(mCloseSystemDialogsReceiver,filter);
49. //下面这几个就是Android原生界面上的Market、搜索、声音输入按钮的默认图标显示。
50. boolean searchVisible = false;
51. boolean voiceVisible = false;
52. // If we have a saved version of these externalicons, we load them up immediately
53. int coi =getCurrentOrientationIndexForGlobalIcons();
54. if (sGlobalSearchIcon[coi] == null ||sVoiceSearchIcon[coi] == null ||
55. sAppMarketIcon[coi]== null) {
56. updateAppMarketIcon();
57. searchVisible =updateGlobalSearchIcon();
58. voiceVisible =updateVoiceSearchIcon(searchVisible);
59. if (sGlobalSearchIcon[coi] != null) {
60. updateGlobalSearchIcon(sGlobalSearchIcon[coi]);
61. searchVisible = true;
62. if (sVoiceSearchIcon[coi] != null)
63. updateVoiceSearchIcon(sVoiceSearchIcon[coi]);
64. voiceVisible = true;
65. if (sAppMarketIcon[coi] != null)
66. updateAppMarketIcon(sAppMarketIcon[coi]);
67. mSearchDropTargetBar.onSearchPackagesChanged(searchVisible,voiceVisible);
68. // On large interfaces, we want the screen toauto-rotate based on the current orientation
69. if (LauncherApplication.isScreenLarge() ||Build.TYPE.contentEquals("eng"))
70. setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
71. Log.i(TAG,"------------------------>Launcher initover") ;
All above is reloading the datafor Launcher. mModel.startLoader(this, true);
In Launcher.java, onCreate() calls the load data interfaces:
1. //加载启动数据
2. if(!mRestoring)
3. mModel.startLoader(this, true);
The load data is in LauncherModel class.
Callbacks interface
1. publicinterface Callbacks {
2. publicboolean setLoadOnResume();
3. publicint getCurrentWorkspaceScreen();
4. publicvoid startBinding();
5. publicvoid bindItems(ArrayList<ItemInfo> shortcuts, int start, int end);
6. publicvoid bindFolders(HashMap<Long,FolderInfo> folders);
7. publicvoid finishBindingItems();
8. publicvoid bindAppWidget(LauncherAppWidgetInfo info);
9. publicvoid bindAllApplications(ArrayList<ApplicationInfo> apps);
10. publicvoid bindAppsAdded(ArrayList<ApplicationInfo> apps);
11. publicvoid bindAppsUpdated(ArrayList<ApplicationInfo> apps);
12. publicvoid bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent);
13. publicvoid bindPackagesUpdated();
14. publicboolean isAllAppsVisible();
15. public void bindSearchablesChanged();
Callbacks have a lot of interfaces.
setLoadOnResume() :When Launcher callonPause, it needs the onResume to recover.
getCurrentWorkspace():get the screens number(0-4)
startBinding():notify the Launcher toload data. Clean the data and reload it.
bindItems(ArrayList<ItemInfo> shortcuts, int start, int end):load App shortcut, LiveFolder, widget toLauncher’s .
bindFolders(HashMap<Long, FolderInfo> folders):load the content of folder.
finishBindingItems():数据加载完成。
bindAppWidget(LauncherAppWidgetInfo item):workspace load the app’ shortcut.
bindAllApplications(final ArrayList<ApplicationInfo> apps):All the app’s list connect to APP icon data.
bindAppsAdded(ArrayList<ApplicationInfo> apps):notify Launcher to install new APP, and updatethe data.
bindAppsUpdated(ArrayList<ApplicationInfo> apps):notify Launcher there is one new APP isupdate.
bindAppsRemoved(ArrayList<ApplicationInfo> apps, booleanpermanent):notify the app there is one app deleted.
bindPackagesUpdated():multiple apps update.
isAllAppsVisible():return all the app’listthat whether it can show the states.
bindSearchablesChanged():Google search bar or delete area changed to notifythe Launcher.
Data load process
Launcher.java inherits Callbacks’interface and realized it. LauncherModel will call this interface and returnthe data and states to Launcher. The data load part divides into two parts:load the data of workspace, load the all app’s screen data.
startLoader()
startLoader initial one thread to load data.
1. public void startLoader(Context context,boolean isLaunching) {
2. synchronized (mLock)
3. //...............
4. if (mCallbacks != null &&mCallbacks.get() != null) {
5. isLaunching = isLaunching ||stopLoaderLocked();
6. mLoaderTask = new LoaderTask(context,isLaunching);
7. sWorkerThread.setPriority(Thread.NORM_PRIORITY);
8. sWorker.post(mLoaderTask);
startLoader starts the run in LoaderTask’s thread. sWorkeris one Handles’ object to start run for thread.
LoaderTask’s run() method
1. public void run() {
2. //............
3. keep_running: {
4. //...............
5. //加载当前页面的数据,先把一页的数据加载完成,
6. //主要是为了增加程序流畅性,提高用户体验
7. if (loadWorkspaceFirst) {
8. if (DEBUG_LOADERS) Log.d(TAG, "step 1:loading workspace");
9. loadAndBindWorkspace();
10. } else {
11. if (DEBUG_LOADERS) Log.d(TAG, "step 1:special: loading all apps");
12. loadAndBindAllApps();
13. if (mStopped) {
14. break keep_running;
15. // THREAD_PRIORITY_BACKGROUND设置线程优先级为后台,
16. //这样当多个线程并发后很多无关紧要的线程分配的CPU时间将会减少,有利于主线程的处理
17. synchronized (mLock) {
18. if (mIsLaunching) {
19. if (DEBUG_LOADERS) Log.d(TAG, "Settingthread priority to BACKGROUND");
20. android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
21. //等待线程空闲的时候,继续加载其他页面数据
22. waitForIdle();
23. //加载剩余页面的数据,包含workspace和all app页面
24. if (loadWorkspaceFirst) {
25. if (DEBUG_LOADERS) Log.d(TAG, "step 2:loading all apps");
26. loadAndBindAllApps();
27. } else {
28. if (DEBUG_LOADERS) Log.d(TAG, "step 2:special: loading workspace");
29. loadAndBindWorkspace();
30. // Restore the default thread priority afterwe are done loading items
31. synchronized (mLock) {
32. android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
This code divides to two parts:
Load the page’s data and wait the free time of thread toload the rest data .
This method’s goal is to increase the speed to start. Showthe screen to user and load the data.
Workspace loads the data.
LoadAndBindWorkspace() runs the loadWorkspace() and bindWorkspace()
1. private void loadWorkspace() {
2. //..........
3. //清空容器,存放界面不同的元素,App快捷方式、widget、folder
4. synchronized (sBgLock) {
5. sBgWorkspaceItems.clear();
6. sBgAppWidgets.clear();
7. sBgFolders.clear();
8. sBgItemsIdMap.clear();
9. sBgDbIconCache.clear();
10. final ArrayList<Long> itemsToRemove = newArrayList<Long>();
11. final Cursor c = contentResolver.query(
12. LauncherSettings.Favorites.CONTENT_URI,null, null, null, null);
13. // +1 for the hotseat (it can be larger than the workspace)
14. // Load workspace in reverse order to ensure that latestitems are loaded first (and
15. // before any earlier duplicates)
16. //表示屏幕上的位置,
17. //第一维表示分屏的序号,其中最后一个代表Hotseat
18. //第二维表示x方向方格的序号
19. //第三维表示y方向方格的序号
20. final ItemInfo occupied[][][] =
21. newItemInfo[Launcher.SCREEN_COUNT + 1][mCellCountX + 1][mCellCountY + 1];
22. //读取数据库响应键值列序号
23. final int idIndex =c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
24. final int intentIndex =c.getColumnIndexOrThrow
25. (LauncherSettings.Favorites.INTENT);
26. final int titleIndex =c.getColumnIndexOrThrow
27. (LauncherSettings.Favorites.TITLE);
28. final int iconTypeIndex =c.getColumnIndexOrThrow(
29. LauncherSettings.Favorites.ICON_TYPE);
30. //...........
31. while (!mStopped && c.moveToNext()) {
32. intitemType = c.getInt(itemTypeIndex);
33. switch(itemType) {
34. //item类型为ITEM_TYPE_APPLICATION或者ITEM_TYPE_SHORTCUT
35. //container为CONTAINER_DESKTOP或者CONTAINER_HOTSEAT
36. //把当前的item添加到sWorkspaceItems中
37. caseLauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
38. caseLauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
39. emType ==LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
40. info = getShortcutInfo(manager, intent,context, c, iconIndex,
41. titleIndex, mLabelCache);
42. } else {
43. info = getShortcutInfo(c, context,iconTypeIndex,
44. iconPackageIndex, iconResourceIndex, iconIndex,
45. titleIndex);
46. switch (container) {
47. caseLauncherSettings.Favorites.CONTAINER_DESKTOP:
48. caseLauncherSettings.Favorites.CONTAINER_HOTSEAT:
49. //添加数据
50. sBgWorkspaceItems.add(info);
51. break;
52. default:
53. //如果item的属性是folder,添加到folder,创建forder
54. FolderInfo folderInfo =
55. findOrMakeFolder(sBgFolders, container);
56. folderInfo.add(info);
57. break;
58. sBgItemsIdMap.put(info.id, info);
59. } else {
60. break;
61. //item类型为文件夹,添加
62. caseLauncherSettings.Favorites.ITEM_TYPE_FOLDER:
63. id = c.getLong(idIndex);
64. FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
65. //.........
66. sBgItemsIdMap.put(folderInfo.id, folderInfo);
67. sBgFolders.put(folderInfo.id, folderInfo);
68. break;
69. //Widget添加
70. caseLauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
71. // Read all Launcher-specific widget details
72. int appWidgetId = c.getInt(appWidgetIdIndex);
73. id = c.getLong(idIndex);
74. //...........
75. sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
76. sBgAppWidgets.add(appWidgetInfo);
77. break;
78. } catch (Exception e){
79. Log.w(TAG, "Desktop items loading interrupted:", e);
80. } finally {
81. c.close();
the loading data of workspace is laoding
/*****************************************************/
Analyze the HotSeat
Hotseat is the shortcut guide bar. The default settings hasno Hotseat. So we need to add it.
The configure profile of Hotseat.
Hotseat is belonging to the workspace, the configure is inthe configure file of workspace. The Hotseat is in the launcher.xml. Not all the launcher.xmlhas the hotseat performance. E.g: layout-sw600 has no hotseat configuration inits launcher.xml.
1. Add the hotseat into the launcher.xml of layout_sw-600dp.
1. <!-- WorkSpace最下面的五个快捷位置 mythou-->
2. <includelayout="@layout/hotseat"
3. android:id="@+id/hotseat"
4. android:layout_width="match_parent"
5. android:layout_height="@dimen/button_bar_height_plus_padding"
6. android:layout_gravity="bottom" />
2. The bottoms of the hotseat are 5, the middleone is for entering All the app list.
The setting is below:
1. <!-- Hotseat (We use the screen as theposition of the item in the hotseat) -->
2. <!-- 使用screen作为按钮位置标识-->
3. <favorite
4. launcher:packageName="com.example.naviback"
5. launcher:className="com.example.naviback.MainActivity"
6. launcher:container="-101"
7. launcher:screen="0"
8. launcher:x="0"
9. launcher:y="0" />
10. <favorite
11. launcher:packageName="com.csr.dvd"
12. launcher:className="com.csr.dvd.LoadDVD"
13. launcher:container="-101"
14. launcher:screen="1"
15. launcher:x="1"
16. launcher:y="0" />
17. <favorite
18. launcher:packageName="com.apical.apicalradio"
19. launcher:className="com.apical.apicalradio.RadioMainActivity"
20. launcher:container="-101"
21. launcher:screen="3"
22. launcher:x="3"
23. launcher:y="0" />
24. <favorite
25. launcher:packageName="com.csr.BTApp"
26. launcher:className="com.csr.BTApp.CSRBluetoothDemoActivity"
27. launcher:container="-101"
28. launcher:screen="4"
29. launcher:x="4"
30. launcher:y="0" />
The configuration ofthe hotseat is different. The instruction of it shows below:
Launcher:container : target with -101, namely the defaultsbottom of hotseat.
Launcher:screen:stands for the position of the bottom. 0 means the the first one ,and allapp isthe 2. So it has not the number 2 screen.
The horizon screen:
1. <com.android.launcher2.Hotseat
2. xmlns:android="http://schemas.android.com/apk/res/android"
3. xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
4. android:background="@drawable/hotseat_bg_panel" <!--设置hotseat的背景 -->
5. launcher:cellCountX="5" <!--代表hotseat横向有多少个方格(图标)-->
6. launcher:cellCountY="1"> <!--代码hotseat竖向有多少个方格-->
7. <com.android.launcher2.CellLayout <!--实际容纳按钮的容器是CellLayout -->
8. android:id="@+id/layout"
9. android:layout_width="match_parent"
10. android:layout_height="wrap_content"
11. android:layout_gravity="center"
12. <!--下面几个属性就是控制5个按钮的显示位置调整,跟我们一般使用控件属性一样 -->
13. android:paddingTop="3dp"
14. android:paddingBottom="3dp"
15. android:paddingLeft="150dp"
16. android:paddingRight="@dimen/button_bar_height_bottom_padding"
17. <!-- hotseat里面按钮的大小以及两个按钮之间的间距 -->
18. launcher:cellWidth="106dip"
19. launcher:cellHeight="106dip"
20. launcher:widthGap="25dp"
21. launcher:heightGap="@dimen/hotseat_height_gap"
22. launcher:maxGap="@dimen/workspace_max_gap" />
23. </com.android.launcher2.Hotseat>
We need noticesome configuration: laucher:cellCountXand launcher:cellCountY. The hotseat contains one CellLayout. Because thehotseat needs some room, so the workspace should share some room to hotseat.
Hotseat’s constructedfunction. Since the performance of the Hotseat is not perfect. So we need toconfigure the class:Hotseat.java.
1. public Hotseat(Context context, AttributeSetattrs, int defStyle)
2. super(context, attrs, defStyle);
3. TypedArraya = context.obtainStyledAttributes(attrs,
4. R.styleable.Hotseat, defStyle, 0);
5. mCellCountX = a.getInt(R.styleable.Hotseat_cellCountX, -1);
6. mCellCountY = a.getInt(R.styleable.Hotseat_cellCountY, -1);
7. mIsLandscape =context.getResources().getConfiguration().orientation ==
8. Configuration.ORIENTATION_LANDSCAPE;
9. //设置成竖屏,使用竖屏时候的hotseat
10. mIsLandscape = false;
There is one judgeabout the size of the screen.
The load of the Hotseat.
The load of the Hotseatdivided into two parts. Allapp bottom and other default bottom.
Other app bottoms:
1. private void loadWorkspace()
2. //........
3. switch (container)
4. caseLauncherSettings.Favorites.CONTAINER_DESKTOP:
5. caseLauncherSettings.Favorites.CONTAINER_HOTSEAT:
6. sWorkspaceItems.add(info);
7. break;
8. //........
Hotseat bind the data.
The flow of the binding data for hotseat is the same asworkspace. The beginning of bindingdata.
1. public void startBinding()
2. //..........
3. // 清空Hotseat的数据
4. if (mHotseat != null)
5. mHotseat.resetLayout();
Inside of the Hotseatis just one CellLayout to response the interior elements.
The way to bind thedata.
1. void addInScreen(View child, long container,int screen, int x, int y, int spanX, int spanY,
2. boolean insert) {
3. //...........
4. //创建CellLayout,用于添加Hotseat的对象。
5. finalCellLayout layout;
6. if(container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
7. layout = mLauncher.getHotseat().getLayout();
8. child.setOnKeyListener(null);
9. // Hide folder title in the hotseat
10. if (child instanceof FolderIcon) {
11. ((FolderIcon) child).setTextVisible(false);
12. if (screen < 0) {
13. screen = mLauncher.getHotseat().getOrderInHotseat(x, y);
14. } else {
15. // Note: We do this to ensure that the hotseat is alwayslaid out in the orientation
16. // of the hotseat in order regardless of which orientationthey were added
17. //获取child的位置,返回true添加成功,false失败
18. x = mLauncher.getHotseat().getCellXFromOrder(screen);
19. y = mLauncher.getHotseat().getCellYFromOrder(screen);
20. } else {
21. //如果Hotseat里面有Folder,隐藏文件夹名字
22. if (child instanceof FolderIcon) {
23. ((FolderIcon) child).setTextVisible(true);
24. layout = (CellLayout) getChildAt(screen);
25. child.setOnKeyListener(new IconKeyEventListener());
26. //.........
Hotseat class
The class of Hotseat is just constructed function.
1. void resetLayout() {
2. //清空原来的内容
3. mContent.removeAllViewsInLayout();
4. //添加AllAPP按钮,也是一个BubbleTextView对象
5. Contextcontext = getContext();
6. LayoutInflater inflater = LayoutInflater.from(context);
7. BubbleTextView allAppsButton = (BubbleTextView)
8. inflater.inflate(R.layout.application, mContent, false);
9. //加载AllAPP按钮的图标,这里使用了selector作为按钮配置
10. allAppsButton.setCompoundDrawablesWithIntrinsicBounds(null,
11. context.getResources().getDrawable(R.drawable.all_apps_button_icon), null,null);
12. //allAppsButton.setText(context.getString(R.string.all_apps_button_label));
13. allAppsButton.setContentDescription(context.getString(R.string.all_apps_button_label));
14. //allapp按钮触摸和点击响应,回调Launcher的功能
15. allAppsButton.setOnTouchListener(new View.OnTouchListener()
16. @Override
17. public boolean onTouch(View v, MotionEvent event) {
18. if (mLauncher != null &&
19. (event.getAction() &MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
20. mLauncher.onTouchDownAllAppsButton(v);
21. return false;
22. //AllAPP按钮点击响应的方法,点击的处理在Launcher类里面
23. allAppsButton.setOnClickListener(new View.OnClickListener()
24. @Override
25. public void onClick(android.view.View v) {
26. if (mLauncher != null) {
27. mLauncher.onClickAllAppsButton(v);
28. //这里会判断是小屏幕还是大屏幕,决定AllAPP按钮的位置
29. int x =getCellXFromOrder(sAllAppsButtonRank);
30. int y =getCellYFromOrder(sAllAppsButtonRank);
31. Log.d("Mythou_Launcher","Hotseat------>x="+x+" y="+y);
32. //Hotseat中清空了装载的内容,然后重新加载allAppsButton到CellLayout mythou
33. mContent.addViewToCellLayout(allAppsButton, -1, 0, newCellLayout.LayoutParams(x,y,1,1),
34. true);
Conclusion
Hotseat is just one CellLayout to manage allthe data.
Most of the configuration can make by the XML.
Load and bind data is the same as workspace.
Paged View
The PageView inherited theViewGroup Class, and it is one contain. The PageView actually contains thedisplay of the CellLayout. When we touchthe screen, the View class will start the function of onInterceptTouchEvent().This function response for the original message driver, decide whether to passthe info to the upper View.
The flow of pass the info :
The code of onInterceptTouchEvent. This code forjudging the state of TouchState.
1. * 这个方法主要是决定触摸消息如何是否需要往上层传递。
2. * 如果返回为true,则会调用PagedView类的onTouchEvent方法,处理触摸事件。否则传给上层View
3. public booleanonInterceptTouchEvent(MotionEvent ev)
4. if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent Enterchlid="+getChildCount());
5. //获取速度跟踪器,记录各个时刻的速度。并且添加当前的MotionEvent以记录更新速度值。 OWL
6. acquireVelocityTrackerAndAddMovement(ev);
7. //没有页面,直接跳过给父类处理。
8. if(getChildCount() <= 0) return super.onInterceptTouchEvent(ev);
9. * 最常见的需要拦截的情况:用户已经进入滑动状态,而且正在移动手指滑动
10. * 对这种情况直接进行拦截,调用PagedView的onTouchEvent()
11. finalint action = ev.getAction();
12. if ((action== MotionEvent.ACTION_MOVE) &&
13. (mTouchState == TOUCH_STATE_SCROLLING))
14. if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent movescrolling...");
15. //截断触摸反馈,直接触发onToucEvent,滑动过程中。
16. return true;
17. switch(action & MotionEvent.ACTION_MASK)
18. case MotionEvent.ACTION_MOVE:
19. if(OWL_DEBUG) Log.d(OWL,"onInterceptTouchEvent ACTION_MOVE...");
20. * 当在这里接受到ACTION_MOVE时,说明mTouchState!=TOUCH_STATE_SCROLLING
21. * 并且mIsBeingDragged的值应该为false,
22. * 否则DragLayer就应该截获了MotionEvent用于实现拖拽。
23. * 此时还没有进入滑动状态,当mActivePointerId== INVALID_POINTER时,
24. * 也就是在此之前没有接收到任何touch事件。
25. * 这种情况发生在Workspace变小时,也就是之前Workspace处于SPRING_LOADED状态。
26. * 当出现这种情况时直接把当前的事件当作ACTION_DOWN进行处理。
27. * 反之,则通过determineScrollingStart()尝试能够进入滑动状态。
28. if (mActivePointerId != INVALID_POINTER) //已经发生触摸,获取到触点OWL
29. determineScrollingStart(ev);
30. break;
31. case MotionEvent.ACTION_DOWN:
32. if(OWL_DEBUG) Log.d(OWL,"onInterceptTouchEvent ACTION_DOWN...");
33. final float x = ev.getX();
34. final float y = ev.getY();
35. //记录点击的位置的坐标以及触点的记录(多点触摸识别)
36. mDownMotionX = x;
37. mLastMotionX = x;
38. mLastMotionY = y;
39. mLastMotionXRemainder = 0;
40. mTotalMotionX = 0;
41. mActivePointerId = ev.getPointerId(0); //第一个触点,返回0
42. mAllowLongPress = true;
43. if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEventmDownMotionX="+mDownMotionX+"
44. mLastMotionY="+mLastMotionY+" mActivePointerId="+mActivePointerId);
45. * 判断目前是真正滑动页面还是已经滑动结束,如果是真正滑动会拦截消息
46. final int xDist = Math.abs(mScroller.getFinalX() -mScroller.getCurrX());
47. final boolean finishedScrolling = (mScroller.isFinished()|| xDist < mTouchSlop);
48. if (finishedScrolling)
49. if(OWL_DEBUG) Log.d(OWL,"onInterceptTouchEvent finishedScrolling..");
50. mTouchState = TOUCH_STATE_REST;
51. mScroller.abortAnimation();
52. else //按下拖动应该属于滑动状态
53. if(OWL_DEBUG) Log.d(OWL,"onInterceptTouchEvent TOUCH_STATE_SCROLLING..");
54. mTouchState =TOUCH_STATE_SCROLLING;
55. // 识别是否触摸状态是否是直接翻页状态,如果是直接翻页,在onTouchEvent里面会直接调用
56. // snapToPage()方法,跳到目标页面
57. if (mTouchState != TOUCH_STATE_PREV_PAGE &&mTouchState != TOUCH_STATE_NEXT_PAGE)
58. if (getChildCount() > 0)
59. if(hitsPreviousPage(x, y))
60. if(OWL_DEBUG) Log.d(OWL,"onInterceptTouchEvent hitsPreviousPage true...");
61. //点击上一页
62. mTouchState = TOUCH_STATE_PREV_PAGE;
63. else if(hitsNextPage(x, y))
64. if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent hitsNextPagetrue...");
65. mTouchState = TOUCH_STATE_NEXT_PAGE;
66. if(OWL_DEBUG)Log.d(OWL, "onInterceptTouchEvent no next or pre page..");
67. break;
68. //不需要拦截触摸消息
69. case MotionEvent.ACTION_UP:
70. case MotionEvent.ACTION_CANCEL:
71. if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEventACTION_UP || ACTION_CANCEL");
72. mTouchState = TOUCH_STATE_REST;
73. mAllowLongPress = false;
74. mActivePointerId = INVALID_POINTER;
75. releaseVelocityTracker();
76. break;
77. case MotionEvent.ACTION_POINTER_UP:
78. if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEventACTION_POINTER_UP");
79. onSecondaryPointerUp(ev);
80. releaseVelocityTracker();
81. break;
82. if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEventmTouchState="+mTouchState);
83. * 只要是mTouchState的状态不为TOUCH_STATE_REST,那么就进行事件拦截,调用onTouchEvent
84. returnmTouchState != TOUCH_STATE_REST;
There are four states in PageView:
//滑动结束状态
1. protected final static int TOUCH_STATE_REST =0;
2. //正在滑动
3. protected final static intTOUCH_STATE_SCROLLING = 1;
4. //滑动到上一页
5. protected final static intTOUCH_STATE_PREV_PAGE = 2;
6. //滑动到下一页
7. protected final static intTOUCH_STATE_NEXT_PAGE = 3;
Except for TOUCH_STATE_REST state, the message will not passto the upper level. The message which is hold here will handle in onTouchEvent.
The code of dealing with data in onTouchEvent:
1. public boolean onTouchEvent(MotionEvent ev)
2. if(OWL_DEBUG) Log.d(OWL,"onTouchEvent entering..");
3. //........switch (action &MotionEvent.ACTION_MASK)
4. //.........此处省略ACTION_DOWN和ACTION_MOVE
5. caseMotionEvent.ACTION_UP:
6. if(OWL_DEBUG) Log.d(OWL, "onTouchEvent ACTION_UP..");
7. if (mTouchState == TOUCH_STATE_SCROLLING)
8. if(OWL_DEBUG) Log.d(OWL,"onTouchEvent ACTION_UP.. TOUCH_STATE_SCROLLING");
9. final int activePointerId = mActivePointerId;
10. final int pointerIndex =ev.findPointerIndex(activePointerId);
11. final float x = ev.getX(pointerIndex);
12. final VelocityTracker velocityTracker = mVelocityTracker;
13. velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
14. //横向速率
15. int velocityX = (int)velocityTracker.getXVelocity(activePointerId);
16. //移动距离
17. final int deltaX = (int) (x - mDownMotionX);
18. //页面宽
19. final int pageWidth =getScaledMeasuredWidth(getPageAt(mCurrentPage));
20. //移动距离超过页面宽40%
21. boolean isSignificantMove = Math.abs(deltaX) > pageWidth*SIGNIFICANT_MOVE_THRESHOLD;
22. final int snapVelocity = mSnapVelocity;
23. mTotalMotionX += Math.abs(mLastMotionX +mLastMotionXRemainder - x);
24. //根据滑动距离和速率,判断是否是滑动
25. boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING&& Math.abs(velocityX) > snapVelocity;
26. // 这钟情况是页面朝一个方向移动了一段距离,然后又弹回去了。
27. //我们使用一个阀值来判断是进行翻页还是返回到初始页面
28. boolean returnToOriginalPage = false;
29. //Math.signum 函数将正数转换为 1.0,将负数转换为 -1.0,0 仍然是 0。实际上,它只是提取一个数的符号
30. if (Math.abs(deltaX) > pageWidth *RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
31. Math.signum(velocityX) != Math.signum(deltaX) && isFling)
32. //返回当前页面
33. returnToOriginalPage = true;
34. int finalPage;//页面朝左移动
35. if (((isSignificantMove && deltaX > 0 &&!isFling) ||
36. (isFling &&velocityX > 0)) && mCurrentPage > 0)
37. if(OWL_DEBUG) Log.d(OWL,"onTouchEvent ACTION_UP.. TOUCH_STATE_SCROLLINGLeftSnap");
38. finalPage = returnToOriginalPage ?mCurrentPage : mCurrentPage - 1;
39. snapToPageWithVelocity(finalPage,velocityX);
40. //页面朝右移动
41. else if (((isSignificantMove && deltaX < 0&& !isFling) ||
42. (isFling &&velocityX < 0)) && mCurrentPage < getChildCount() - 1)
43. if(OWL_DEBUG) Log.d(OWL,"onTouchEvent ACTION_UP.. TOUCH_STATE_SCROLLINGRightSnap");
44. finalPage = returnToOriginalPage ?mCurrentPage : mCurrentPage + 1;
45. snapToPageWithVelocity(finalPage,velocityX);
46. if(OWL_DEBUG) Log.d(OWL,"onTouchEvent ACTION_UP.. TOUCH_STATE_SCROLLING BackOrigation");
47. //寻找离屏幕中心最近的页面移动
48. snapToDestination();
49. else if (mTouchState == TOUCH_STATE_PREV_PAGE)
50. if(OWL_DEBUG) Log.d(OWL,"onTouchEvent ACTION_UP.. TOUCH_STATE_PREV_PAGE");
51. //直接切换到上一页,没有进行滑动
52. int nextPage = Math.max(0, mCurrentPage - 1);
53. if (nextPage != mCurrentPage) {
54. snapToPage(nextPage);
55. } else {
56. snapToDestination();
57. else if (mTouchState == TOUCH_STATE_NEXT_PAGE)
58. if(OWL_DEBUG) Log.d(OWL,"onTouchEvent ACTION_UP.. TOUCH_STATE_NEXT_PAGE");
59. //直接切换到下一页
60. int nextPage = Math.min(getChildCount() - 1, mCurrentPage +1);
61. if (nextPage != mCurrentPage) {
62. snapToPage(nextPage);
63. } else {
64. snapToDestination();
65. if(OWL_DEBUG) Log.d(OWL,"onTouchEvent ACTION_UP.. onUnhandledTap()");
66. //这里是空回调,继承PagedView的方法,会重载这方法处理一些操作。
67. onUnhandledTap(ev);
68. mTouchState = TOUCH_STATE_REST;
69. mActivePointerId = INVALID_POINTER;
70. releaseVelocityTracker();
71. break;return true;
We can see the ACTION_UP in onTouchEvent handle 3conditions, map to TOUCH_STATE_SCROLLING, TOUCH_STATE_PREV_PAGE, andTOUCH_STATE_NEXT_PAGE.
When we slip to right , the logcat will print the info.First, start the onInterceptTouchEvent(), then change the state of mTouchStatefrom TOUCH_STATE_REST to TOUCH_STATE_SCROLLING. Last , callsnapToPageWithVelocity() and turn to next page.
The code in snapToPageWithVelocity:
1. protected void snapToPageWithVelocity(intwhichPage, int velocity)
2. //..............
3. //根据滑动的速度,计算一个切换动画的显示时间,速度越快,动画显示时间越短。
4. duration= 4 * Math.round(1000 * Math.abs(distance / velocity));
5. //跳转到指定页面。
6. snapToPage(whichPage,delta, duration);
The snapToPage is called in the part, and add one more wayto handle the speed of slipping. TheVelocityTracker will record the speed of slipping. The speed will determine the time to show theanimation of changing.
1. protected void snapToPage(int whichPage, intdelta, int duration)
2. mNextPage = whichPage;
3. //............
4. //切换的时候调用Scroller的startScroll进行切换
5. if(!mScroller.isFinished()) mScroller.abortAnimation();
6. mScroller.startScroll(mUnboundedScrollX, 0, delta, 0,duration);
7. notifyPageSwitchListener();
8. invalidate();
When system calls the startScroll(), the computeScroll()will also be called. And in computeScroll(), the scrollTo() will scroll thescreen, and call invalidate() to refresh the screen, create the effect ofanimation and scroll page.
1. protected boolean computeScrollHelper()
2. //computeScrolloffset用来计算滑动是否结束,当你调用startScroll()方法的时候这个方法返回的值一直都为true,
3. //如果采用其它方式移动视图比如:scrollTo()或 scrollBy时那么这个方法返回false
4. if(mScroller.computeScrollOffset())
5. if (mScrollX != mScroller.getCurrX() || mScrollY != mScroller.getCurrY())
6. scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
7. //刷新界面
8. invalidate();
9. return true;
10. //.............
11. returnfalse;
The scrollTo is the method of View class, PagedView rewriteit for judging the position, update the X&Y coordinate.
AppsCustomizeTabHost
In the Android 4.0 launche, the widget is added.
AllApp list
The configuration of AllApp list is in the file :\res\Layout\apps_customize_pane.xml
It uses TabHost to organize 2 pages(AllApp and widget), whichcan be switched by the TabHost on the screen.
1. <!-- 取消TabHost的显示,把TabHost设置为0dp高,避免影响all app显示 mythou -->
2. <com.android.launcher2.AppsCustomizeTabHost
3. android:background="@android:color/transparent">
4. <LinearLayout
5. android:id="@+id/apps_customize_content"
6. android:orientation="vertical"
7. android:layout_width="match_parent"
8. android:layout_height="match_parent">
9. <!--TabHost栏,配置TahHost栏的高度宽度
10. 我这里把TabHost取消了,因为我的Launcher需要把Widget反正workspace里面实现,
11. 所以全部应用列表修改成和2.X的Launcher一样 mythou-->
12. <FrameLayout
13. android:id="@+id/tabs_container"
14. android:layout_width="0dp"
15. android:layout_height="0dp"
16. android:layout_marginTop="0dp"
17. android:layout_gravity="center_horizontal">
18. <!-- TabHost上面Widget 的按钮-->
19. <com.android.launcher2.FocusOnlyTabWidget
20. android:id="@android:id/tabs"
21. android:layout_width="match_parent"
22. android:layout_height="match_parent"
23. android:layout_gravity="left"
24. android:background="@android:color/transparent"
25. android:tabStripEnabled="false"
26. android:tabStripLeft="@null"
27. android:tabStripRight="@null"
28. android:divider="@null" />
29. <!--TabHost 右边的Android市场的图标,不需要可以去掉-->
30. <include
31. android:id="@+id/market_button"
32. layout="@layout/market_button"
33. android:layout_width="wrap_content"
34. android:layout_height="match_parent"
35. android:layout_gravity="right" />
36. </FrameLayout>
37. <!--下面这里就是我们所有应用列表的选项和所有应用列表的显示View
38. 需要注意的是AppsCustomizePagedView同时支持显示所有应用列表和Widget列表 mythou-->
39. <FrameLayout
40. android:id="@android:id/tabcontent"
41. android:layout_width="match_parent"
42. android:layout_height="match_parent"
43. <!-- 所有应用列表是通过自定义VIewAppsCustomizePagedView显示,后面会详细分析这个View
44. 下面只对部分重要属性加入注释-->
45. <com.android.launcher2.AppsCustomizePagedView
46. android:id="@+id/apps_customize_pane_content"
47. android:layout_width="match_parent"
48. android:layout_height="match_parent"
49. //MaxAppCellCountX 和MaxAppCellCounY指的是所有App图标排列的最大行列数。
50. //一般设置为-1,表示无限制
51. launcher:maxAppCellCountX="@integer/apps_customize_maxCellCountX"
52. launcher:maxAppCellCountY="@integer/apps_customize_maxCellCountY"
53. //pageLayoutWidthGap和pageLayoutHeightGap分别表示菜单界面与屏幕边缘的距离,
54. //一般小屏幕这里设置为-1。避免边框太窄误触屏幕才需要设置。
55. launcher:pageLayoutWidthGap="@dimen/apps_customize_pageLayoutWidthGap"
56. launcher:pageLayoutHeightGap="@dimen/apps_customize_pageLayoutHeightGap"
57. launcher:pageLayoutPaddingTop="50dp"
58. //pageLayoutPaddingXXX指的是内填充,这个和系统的padding一样
59. launcher:pageLayoutPaddingBottom="@dimen/apps_customize_pageLayoutPaddingBottom"
60. launcher:pageLayoutPaddingLeft="@dimen/apps_customize_pageLayoutPaddingLeft"
61. launcher:pageLayoutPaddingRight="@dimen/apps_customize_pageLayoutPaddingRight"
62. //widgetCellWithGap和widgetCellHeightGap指的是widget列表界面各个widget之间的间隔,
63. //和系统的margin属性类似
64. launcher:widgetCellWidthGap="@dimen/apps_customize_widget_cell_width_gap"
65. launcher:widgetCellHeightGap="@dimen/apps_customize_widget_cell_height_gap"
66. //widgetCountX和WidgetCountY都是表示Widget界面每行每列显示多少Widget
67. launcher:widgetCountX="@integer/apps_customize_widget_cell_count_x"
68. launcher:widgetCountY="@integer/apps_customize_widget_cell_count_y"
69. //提示界面的焦点
70. launcher:clingFocusedX="@integer/apps_customize_cling_focused_x"
71. launcher:clingFocusedY="@integer/apps_customize_cling_focused_y"
72. launcher:maxGap="@dimen/workspace_max_gap" />
73. <!-- 加载全部应用时的旋转动画 -->
74. <FrameLayout
75. android:id="@+id/animation_buffer"
76. android:layout_width="match_parent"
77. android:layout_height="match_parent"
78. android:background="#FF000000"
79. android:visibility="gone" />
80. <!-- 分页符,代表多少页和当前页面-->
81. <include
82. android:id="@+id/paged_view_indicator"
83. layout="@layout/scroll_indicator"
84. android:layout_width="wrap_content"
85. android:layout_height="wrap_content"
86. android:layout_gravity="bottom" />
87. </FrameLayout>
88. </LinearLayout>
89. <!--第一次进入所有应用列表的提示界面,和workspace提示界面一样-->
90. <includelayout="@layout/all_apps_cling"
91. android:id="@+id/all_apps_cling"
92. android:layout_width="match_parent"
93. android:layout_height="match_parent"/>
94. </com.android.launcher2.AppsCustomizeTabHost>
All the list of apps and widget is showed byAppsCustomizedPagedView.
AppsCustomizedTabHost
AppsCustomizedTabHost is inherited by TabHost class forexpand the TabHost and adding some function.
1. protected void onFinishInflate()
2. //.......//创建所有应用列表Tab mythou
3. TextViewtabView;
4. Stringlabel;
5. label =mContext.getString(R.string.all_apps_button_label);
6. tabView= (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs,false);
7. tabView.setText(label);
8. tabView.setContentDescription(label);
9. addTab(newTabSpec(APPS_TAB_TAG).setIndicator(tabView).setContent(contentFactory));
10. //Widget的Tab页面
11. label =mContext.getString(R.string.widgets_tab_label);
12. tabView= (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs,false);
13. tabView.setText(label);
14. tabView.setContentDescription(label);
15. addTab(newTabSpec(WIDGETS_TAB_TAG).setIndicator(tabView).setContent(contentFactory));
16. //设置监听器
17. setOnTabChangedListener(this);
18. AppsCustomizeTabKeyEventListener keyListener = new AppsCustomizeTabKeyEventListener();
19. ViewlastTab = tabs.getChildTabViewAt(tabs.getTabCount() - 1);
20. lastTab.setOnKeyListener(keyListener);
21. //Android商店按钮
22. ViewshopButton = findViewById(R.id.market_button);
23. shopButton.setOnKeyListener(keyListener);
24. // Hidethe tab bar until we measure
25. mTabsContainer.setAlpha(0f);
onFInishInflate function creates Tab View for TabHost.
onTabChanged runs when Tab changed.
When TabHost switch the items, it runs onTabChangedfunction. This function will switch the AppsCustomizedPagedView class. All theapps and widget will show by AppsCustomizedPagedView.
1. public void onTabChanged(String tabId)
2. //使用Runnable执行一个切换的动画效果,因为切换的时候会存在数据加载导致的延时问题。
3. //在加载切换数据的过程中,加入动画可以增强用户体验 mythou
4. post(newRunnable()
5. @Override
6. public void run()
7. ArrayList<View> visiblePages = newArrayList<View>();
8. for (int i = visiblePageRange[0]; i <=visiblePageRange[1]; i++)
9. visiblePages.add(mAppsCustomizePane.getPageAt(i));
10. //保证每个页面都是使用统一的动画效果
11. mAnimationBuffer.scrollTo(mAppsCustomizePane.getScrollX(),0);
12. // mAppsCustomizePane显示子页面是使用相反的顺序,所以添加页面动画的时候,
13. //也是使用相反的添加顺序
14. for (int i = visiblePages.size() - 1; i >= 0; i--)
15. View child = visiblePages.get(i);
16. //增加切换动画缓存,提供下面切换动画使用
17. mAnimationBuffer.addView(child, p);
18. // Toggle the new content
19. onTabChangedStart();
20. onTabChangedEnd(type);
21. //过渡动画开始
22. ObjectAnimator outAnim =ObjectAnimator.ofFloat(mAnimationBuffer, "alpha", 0f);
23. //。。。。。。。。
onTabChanged offered one animation of switching. It willcost some time to load and switch data, so the animation created by one threadwill executed.
onLauncherTransitionStart and onLauncherTransitionEnd
These two function is called byLauncher.java. They will run when switchworkspace to AllApp list. Before this switch, it will callonLauncherTransitionStart, and after the switch, it will callonLauncherTransitionEnd.
PagedViewWithDraggableItems
The relationship of AppsCustomizedPagedView inherited.
Touch intercept
PagedViewWithDraggableItem is inherited by PagedView.PagedView is for screen switch, and it will intercepted the info about pageswitch in onInterceptTouchEvent(), then deal with it in onTouchEvent().
The code about PagedViewWithDraggableItems. (intercept andhandle info)
1. //Edited by mythou
2. //http://www.cnblogs.com/mythou/
3. @Override
4. public booleanonInterceptTouchEvent(MotionEvent ev)
5. if(OWL_DEBUG) Log.d(OWL, "enter- onInterceptTouchEvent");
6. handleTouchEvent(ev);
7. returnsuper.onInterceptTouchEvent(ev);
8. @Override
9. public booleanonTouchEvent(MotionEvent ev)
10. if(OWL_DEBUG) Log.d(OWL, "enter- onTouchEvent ");
11. handleTouchEvent(ev);
12. returnsuper.onTouchEvent(ev);
The onlnterceptTouchEvent and onTouchEvent is simple inPagedViewWithDraggableItems, and both of them called the same way to deal withit. The screen will callonInterceptTouchEvnet of PageViewWithDraggableItem() first, and then callPagedView to handle.
handleTouchEvent
1. //Edited by mythou
2. //http://www.cnblogs.com/mythou/
3. private void handleTouchEvent(MotionEvent ev)
4. finalint action = ev.getAction();
5. if(OWL_DEBUG) Log.d(OWL, "handleTouchEventaction="+(action & MotionEvent.ACTION_MASK)+
6. " mTouchState="+mTouchState+" mIsDragging="+mIsDragging+"mIsDragEnabled="+mIsDragEnabled);
7. switch(action & MotionEvent.ACTION_MASK)
8. case MotionEvent.ACTION_DOWN: //按下事件处理
9. cancelDragging();
10. mIsDragEnabled = true;
11. break;
12. case MotionEvent.ACTION_MOVE: //进入滑动状态
13. if (mTouchState != TOUCH_STATE_SCROLLING &&!mIsDragging && mIsDragEnabled)
14. //根据是否进入滚动状态,判断是否需要拖曳按钮
15. if(OWL_DEBUG) Log.d(OWL,"handleTouchEvent--->before drag ");
16. determineDraggingStart(ev);
17. if(OWL_DEBUG) Log.d(OWL,"handleTouchEvent--->Not drag ");
18. break;
There are two states about touch:ACITON_DOWN andACITON_MOVE. ACTION_DOWNwill handle the mark about drag the icon. And the ACTION_MOVIE will judge thescrolling state by TouchState. If not, it will run drag icon. TouchState isjudged the states by PagedView.
When we slip the screen, the system will callPagedViewWithDraggableItems’ onInterceptTouchEvent and call the code byorder. The system will run handleTouchEvent’sACTION_MOVE, and then call determineDraggingStart(). Not the PagedViewWithDraggableItems’sdetermineDraggingPagedView. Because the function in AppsCustomizedPagedView isempty and AppsCustomizedPagedView rewrite it.
Drag icon
If you drag one icon, the system will determine it is thelong click.
1. //Edited by mythou
2. //http://www.cnblogs.com/mythou/
3. @Override
4. public boolean onLongClick(Viewv)
5. if(OWL_DEBUG) Log.d(OWL, "onLongClick Enter");
6. //下面有几种情况会取消长按触摸,不是触摸状态,正在动画过渡,离开了allAPP页面
7. if(!v.isInTouchMode()) return false;
8. //Return early if we are still animating the pages
9. if(mNextPage != INVALID_PAGE) return false;
10. // Whenwe have exited all apps or are in transition, disregard long clicks
11. if(!mLauncher.isAllAppsCustomizeOpen() ||
12. mLauncher.getWorkspace().isSwitchingState()) return false;
13. if(OWL_DEBUG) Log.d(OWL, "onLongClickbeginDragging()");
14. //调用开始拖曳的设置,里面会设置一些标记
15. return beginDragging(v);
The onLongClick() of PagedViewDraggableItems is just runsimple mark configuration.