一、增加墙纸
图片放入/packages/apps/Launcher2/res/drawable-mdpi
/packages/apps/Launcher2/res/values-mdpi/wallpapers.xml
<resources>
<string-array name="wallpapers" translatable="false">
<item>wallpaper_lake</item>
<item>wallpaper_sunset</item>
<item>wallpaper_beach</item>
<item>wallpaper_snow_leopard</item>
<item>wallpaper_path</item>
<item>wallpaper_sunrise</item>
<item>wallpaper_mountain</item>
<item>wallpaper_road</item>
<item>wallpaper_jellyfish</item>
<item>wallpaper_zanzibar</item>
<item>wallpaper_blue</item>
<item>wallpaper_grey</item>
<item>wallpaper_green</item>
<item>wallpaper_pink</item>
</string-array>
</resources>
设置默认墙纸
frameworks\base\core\res\res\drawable中default_wallpaper.jpg这个是默认的墙纸
二、源代码中设置wallpaper
在launcher.java中
public boolean onCreateOptionsMenu(Menu menu) {
if (isWorkspaceLocked()) {
return false;
}
super.onCreateOptionsMenu(menu);
menu.add(MENU_GROUP_ADD, MENU_ADD, 0, R.string.menu_add)
.setIcon(android.R.drawable.ic_menu_add)
.setAlphabeticShortcut('A');
menu.add(0, MENU_MANAGE_APPS, 0, R.string.menu_manage_apps)
.setIcon(android.R.drawable.ic_menu_manage)
.setAlphabeticShortcut('M');
menu.add(MENU_GROUP_WALLPAPER, MENU_WALLPAPER_SETTINGS, 0, R.string.menu_wallpaper)
.setIcon(android.R.drawable.ic_menu_gallery)
.setAlphabeticShortcut('W');
… …
}
点击wallpaper后会调用
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_ADD:
addItems();
return true;
case MENU_MANAGE_APPS:
manageApps();
return true;
case MENU_WALLPAPER_SETTINGS:
startWallpaper();
return true;
case MENU_SEARCH:
onSearchRequested();
return true;
case MENU_NOTIFICATIONS:
showNotifications();
return true;
}
return super.onOptionsItemSelected(item);
}
将进入
private void startWallpaper() {
closeAllApps(true);
final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
Intent chooser = Intent.createChooser(pickWallpaper,
getText(R.string.chooser_wallpaper));
startActivityForResult(chooser, REQUEST_PICK_WALLPAPER);
}
三、launcher中launcher的搜索框和ProtipWidget(widget修改)
Launcher2/res/xml/default_workspace.xml
<!-- Middle screen [2] <search>为添加google搜索框;-->
<search
launcher:screen="2"
launcher:x="0"
launcher:y="0" />
<!-- <appwidget>为添加相应的widget; -->
<appwidget
launcher:packageName="com.android.protips"
launcher:className="com.android.protips.ProtipWidget"
launcher:screen="2"
launcher:x="0"
launcher:y="1"
launcher:spanX="4"
launcher:spanY="1" />
此xml解析在Launcherprovider.java文件loadFavorites方法中
四、launcher屏数和默认屏
在launcher.java的Launcher类中有
static final int SCREEN_COUNT = 5; //屏数
static final int DEFAULT_SCREEN = 2; //默认显示的第几屏(从0屏开始)。
修改屏数还需要修改
Launcher2/res/layout-port/Launcher.xml
<!-- The workspace contains 3 screens of cells -->
<com.android.launcher2.Workspace
android:id="@+id/workspace"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="horizontal"
android:fadeScrollbars="true"
launcher:defaultScreen="2">
<include android:id="@+id/cell1" layout="@layout/workspace_screen" />
<include android:id="@+id/cell2" layout="@layout/workspace_screen" />
<include android:id="@+id/cell3" layout="@layout/workspace_screen" />
<include android:id="@+id/cell4" layout="@layout/workspace_screen" />
<include android:id="@+id/cell5" layout="@layout/workspace_screen" />
</com.android.launcher2.Workspace>
修改默认屏同时需要修改workspace.java中
public Workspace(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
… …
mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1);
a.recycle();
setHapticFeedbackEnabled(false);
initWorkspace();
}
五、页面标记实现原理
Launcher2/res/drawable/ home_arrows_left.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/all_apps_button_pressed" />
<item android:state_focused="true" android:state_window_focused="true" android:drawable="@drawable/all_apps_button_focused" />
<item android:state_focused="true" android:state_window_focused="false" android:drawable="@drawable/all_apps_button_normal" />
<item android:drawable="@drawable/all_apps_button_normal" />
</selector>
Launcher2/res/layout-port/Launcher.xml
<ImageView
android:id="@+id/previous_screen"
android:layout_width="93dip"
android:layout_height="@dimen/button_bar_height"
android:layout_gravity="bottom|left"
android:layout_marginLeft="6dip"
android:scaleType="center"
android:src="@drawable/home_arrows_left"
android:onClick="previousScreen" <!—点击事件处理-->
android:focusable="true"
android:clickable="true" />
在launcher.java中实现
private void setupViews() {
… …
mPreviousView = (ImageView) dragLayer.findViewById(R.id.previous_screen);
mNextView = (ImageView) dragLayer.findViewById(R.id.next_screen);
Drawable previous = mPreviousView.getDrawable();
Drawable next = mNextView.getDrawable();
mWorkspace.setIndicators(previous, next);
… …
}
获取这个两旁的图片设置到mCurrentScreen屏幕上
Workspace.java
void setIndicators(Drawable previous, Drawable next) {
mPreviousIndicator = previous;
mNextIndicator = next;
previous.setLevel(mCurrentScreen);
next.setLevel(mCurrentScreen);
}
在launcher.xml中定义
android:onClick="previousScreen" <!—点击事件处理-->
在launcher.java中的处理为
@SuppressWarnings({"UnusedDeclaration"})
public void previousScreen(View v) {
if (!isAllAppsVisible()) {
mWorkspace.scrollLeft();
}
}
@SuppressWarnings({"UnusedDeclaration"})
public void nextScreen(View v) {
if (!isAllAppsVisible()) {
mWorkspace.scrollRight();
}
}
进入到workspace中
public void scrollLeft() {
clearVacantCache();
if (mScroller.isFinished()) {
if (mCurrentScreen > 0) snapToScreen(mCurrentScreen - 1);
} else {
if (mNextScreen > 0) snapToScreen(mNextScreen - 1);
}
}
public void scrollRight() {
clearVacantCache();
if (mScroller.isFinished()) {
if (mCurrentScreen < getChildCount() -1) snapToScreen(mCurrentScreen + 1);
} else {
if (mNextScreen < getChildCount() -1) snapToScreen(mNextScreen + 1);
}
}
先判断滑动是否完成,然后跳转到目标屏。
void snapToScreen(int whichScreen) {
snapToScreen(whichScreen, 0, false);
}
private void snapToScreen(int whichScreen, int velocity, boolean settle) {
//if (!mScroller.isFinished()) return;
whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
clearVacantCache();
enableChildrenCache(mCurrentScreen, whichScreen);
mNextScreen = whichScreen;
mPreviousIndicator.setLevel(mNextScreen);
mNextIndicator.setLevel(mNextScreen);
View focusedChild = getFocusedChild();
if (focusedChild != null && whichScreen != mCurrentScreen &&
focusedChild == getChildAt(mCurrentScreen)) {
focusedChild.clearFocus();
}
final int screenDelta = Math.max(1, Math.abs(whichScreen - mCurrentScreen));
final int newX = whichScreen * getWidth();
final int delta = newX - mScrollX;
int duration = (screenDelta + 1) * 100;
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
if (settle) {
mScrollInterpolator.setDistance(screenDelta);
} else {
mScrollInterpolator.disableSettle();
}
velocity = Math.abs(velocity);
if (velocity > 0) {
duration += (duration / (velocity / BASELINE_FLING_VELOCITY))
* FLING_VELOCITY_INFLUENCE;
} else {
duration += 100;
}
awakenScrollBars(duration);
mScroller.startScroll(mScrollX, 0, delta, 0, duration);
invalidate();
}
六、左右滑动切换屏幕
在workspace中实现屏幕切换主要重写了以下几个方法:onMeasure()、onLayout()、onInterceptTouchEvent()、onTouchEvent()方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
}
// The children are given the same width and height as the workspace
final int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
}
if (mFirstLayout) {
setHorizontalScrollBarEnabled(false);
scrollTo(mCurrentScreen * width, 0);
setHorizontalScrollBarEnabled(true);
updateWallpaperOffset(width * (getChildCount() - 1));
mFirstLayout = false;
}
}
onInterceptTouchEvent()方法和onTouchEvent()主要是来响应手指按下划动时所需要捕获的消息,例如划动的速度,划动的距离等。当手指起来时,根据划动的速度与跨度来判断是向左滑动一页还是向右滑动一页,确保每次用户操作结束之后显示的都是整体的一个子view.
在View.java中framework\base\core\java\android\view
将当前视图内容偏移至(x , y)坐标处,即显示(可视)区域位于(x , y)坐标
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
invalidate();
}
}
}
在当前视图内容继续偏移(x , y)个单位,显示(可视)区域也跟着偏移(x,y)个单位
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
mScrollX 与 mScrollY 代表我们当前偏移的位置 , 在当前位置继续偏移(x ,y)个单位
七、获取应用列表(相当于mainmenu)
在launcher.java中
Launcher 布局文件初始化方法中
private void setupViews() {
… …
mHandleView = (HandleView) findViewById(R.id.all_apps_button);
mHandleView.setLauncher(this);
mHandleView.setOnClickListener(this);
mHandleView.setOnLongClickListener(this);
… …
}
点击中间button
public void onClick(View v) {
Object tag = v.getTag();
if (tag instanceof ShortcutInfo) {
… …
} else if (tag instanceof FolderInfo) {
handleFolderClick((FolderInfo) tag);
} else if (v == mHandleView) {
if (isAllAppsVisible()) {
closeAllApps(true);
} else {
showAllApps(true);
}
}
进入到showAllApps()方法
void showAllApps(boolean animated) {
mAllAppsGrid.zoom(1.0f, animated);
((View) mAllAppsGrid).setFocusable(true);
((View) mAllAppsGrid).requestFocus();
// TODO: fade these two too
mDeleteZone.setVisibility(View.GONE);
}
在上面使用到了mAllAppsGrid
privateAllAppsView mAllAppsGrid; //声明
在setupViews()方法中
private void setupViews() {
… …
mAllAppsGrid = (AllAppsView)dragLayer.findViewById(R.id.all_apps_view);
mAllAppsGrid.setLauncher(this);
mAllAppsGrid.setDragController(dragController);
((View) mAllAppsGrid).setWillNotDraw(false); // We don't want a hole punched in our window.
// Manage focusability manually since this thing is always visible
((View) mAllAppsGrid).setFocusable(false);
… …
}
all_apps_view是在all_app_2d.xml中定义
位于res/layout-port中
故找到AllApps2D.java可以找到addApps,removeApps,zoom这几个方法。
public void zoom(float zoom, boolean animate) {
// Log.d(TAG, "zooming " + ((zoom == 1.0) ? "open" : "closed"));
cancelLongPress();
mZoom = zoom;
if (isVisible()) {
getParent().bringChildToFront(this);
setVisibility(View.VISIBLE);
mGrid.setAdapter(mAppsAdapter);
if (animate) {
startAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.all_apps_2d_fade_in));
} else {
onAnimationEnd();
}
} else {
if (animate) {
startAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.all_apps_2d_fade_out));
} else {
onAnimationEnd();
}
}
}
在zoom()方法里面有mGrid.setAdapter(mAppsAdapter),在构造方法中,给adapter已经赋值。
public AllApps2D(Context context, AttributeSet attrs) {
super(context, attrs);
setVisibility(View.GONE);
setSoundEffectsEnabled(false);
mAppsAdapter = new AppsAdapter(getContext(), mAllAppsList);
mAppsAdapter.setNotifyOnChange(false);
}
在launcher.java函数中
private void loadHotseats() {
… …
PackageManager pm = getPackageManager();
… …
ResolveInfo bestMatch = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
List<ResolveInfo> allMatches = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
… …
}
如此获取了应用列表