应用的图标---
在ApplicationInfo中有个Bitmap类型的成员是iconBitmap,在Launcher.java文件中,有一些方法是去bind all apps,bind apps added,bind apps updated,在这些地方,可以将这个iconBitmap拿出来,合成一个包含背景的bitmap,然后替换掉原来的,就可以了,在AllAppsView里面加一个接口方法,在AllApps2D等方法中去实现这个方法。这个思路供你参考。
--------------
launcher.java 中看一下:private void showPreviews(final View anchor, int start, int end)实现屏幕预览,没有增加|删除屏幕功能哦
------------------------
让你自己写的Android的Launcher成为系统中第一个启动的,也是唯一的Launcher.
分类: Android 移动应用2010-09-08 20:58 3306人阅读 评论(6) 收藏 举报
如果你要定制一个Android系统,你想用你自己的Launcher(Home)作主界面来替换Android自己的Home,而且不希望用户安装的Launcher来替换掉你的Launcher.
我们可以通过修改Framework来实现这样的功能。
这里以Android2.1的源代码为例来实际说明。
1)首先了解一下Android的启动过程。
Android系统的启动先从Zygote开始启动,然后......(中间的过程就不说了).....一直到了SystemServer(framework)这个地方,看到这段代码:
/**
* This method is called from Zygote to initialize the system. This will cause the native
* services (SurfaceFlinger, AudioFlinger, etc..) to be started. After that it will call back
* up into init2() to start the Android services.
*/
native public static void init1(String[] args);
public static void main(String[] args) {
if (SamplingProfilerIntegration.isEnabled()) {
SamplingProfilerIntegration.start();
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
SamplingProfilerIntegration.writeSnapshot("system_server");
}
}, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL);
}
// The system server has to run all of the time, so it needs to be
// as efficient as possible with its memory usage.
VMRuntime.getRuntime().setTargetHeapUtilization(0.8f);
System.loadLibrary("android_servers");
init1(args);
}
public static final void init2() {
Log.i(TAG, "Entered the Android system server!");
Thread thr = new ServerThread();
thr.setName("android.server.ServerThread");
thr.start();
}
}
从SystemServer的main函数开始启动各种服务。
首先启动init1,然后启动init2.
从上面的注释可以看到:init1这个方法时被Zygote调用来初始化系统的,init1会启动native的服务如SurfaceFlinger,AudioFlinger等等,这些工作做完以后会回调init2来启动Android的service。
这里我们主要来关注init2的过程。
init2中启动ServerThread线程,
ServerThread中启动了一系列的服务,比如这些:
ActivityManagerService
EntropyService
PowerManagerService
TelephonyRegistry
PackageManagerService
AccountManagerService
BatteryService
HardwareService
Watchdog
SensorService
BluetoothService
StatusBarService
ClipboardService
InputMethodManagerService
NetStatService
ConnectivityService
AccessibilityManagerService
NotificationManagerService
MountService
DeviceStorageMonitorService
LocationManagerService
SearchManagerService
FallbackCheckinService
WallpaperManagerService
AudioService
BackupManagerService
AppWidgetService
这些大大小小的服务起来以后,开始
((ActivityManagerService)ActivityManagerNative.getDefault()).systemReady()
在systemReady后开始开始启动Launcher。
在寻找Launcher的时候是根据HOME的filter(在Manifest中定义的<category android:name="android.intent.category.HOME" />)来过滤。
然后根据filter出来的HOME来启动,如果只有一个HOME,则启动这个HOME,如果用户自己装了HOME,那就会弹出来一个列表供用户选择。
我们现在希望从这里弹出我们自己定制的Launcher,同时也不希望弹出选择HOME的界面,我们不希望用户修改我们的home,比如我们的home上放了好多广告,以及强制安装的程序,不希望用户把它干掉。
我们可以通过这样来实现:
2) 定义一个私有的filter选项,然后用这个选项来过滤HOME.
一般情况下我们使用Manifest中定义的<category android:name="android.intent.category.HOME"来过滤的,我们现在增加一个私有的HOME_FIRST过滤。
在Intent.java(frameworks/base/core/java/android/content/Intent.java)中添加两行代码
//lixinso:添加CATEGORY_HOME_FIRST
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String CATEGORY_HOME_FIRST = "android.intent.category.HOME_FIRST";
3)修改和CATEGORY_HOME相关的所有的地方,都改成HOME_FIRST,主要是framework中的这几个地方:
frameworks/base/services/java/com/android/server/am/ActivityManagerService.java中
//intent.addCategory(Intent.CATEGORY_HOME);
改成intent.addCategory(Intent.CATEGORY_HOME_FIRST); //lixinso:
//if (r.intent.hasCategory(Intent.CATEGORY_HOME)) {
改成if (r.intent.hasCategory(Intent.CATEGORY_HOME_FIRST)) { //lixinso: Intent.CATEGORY_HOME -> Intent.CATEGORY_HOME_FIRST
frameworks/base/services/java/com/android/server/am/HistoryRecorder.java中
// _intent.hasCategory(Intent.CATEGORY_HOME) &&
改成 _intent.hasCategory(Intent.CATEGORY_HOME_FIRST) && //lixinso: Intent.CATEGORY_HOME->Intent.CATEGORY_HOME_FIRST
frameworks/policies/base/mid/com/android/internal/policy/impl/MidWindowManager.java中
//mHomeIntent.addCategory(Intent.CATEGORY_HOME);
改成 mHomeIntent.addCategory(Intent.CATEGORY_HOME_FIRST); //lixinso
frameworks/policies/base/mid/com/android/internal/policy/impl/RecentApplicationsDialog.java中
//new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME),0);
改成 new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME_FIRST),0); //lixinso
frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindowManager.java中
//mHomeIntent.addCategory(Intent.CATEGORY_HOME);
改成 mHomeIntent.addCategory(Intent.CATEGORY_HOME_FIRST); //lixinso
frameworks/policies/base/phone/com/android/internal/policy/impl/RecentApplicationsDialog.java中
//ResolveInfo homeInfo = pm.resolveActivity(new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME),0);
改成 ResolveInfo homeInfo = pm.resolveActivity(new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME_FIRST),0); //lixinso
4) 写一个自己的Launcher.
可以参考android sample中的Launcher,或者android源代码中的 /packages/apps/Launcher 来写。
在Launcher中标记其是不是Launcher的最关键的代码时Manifest中的filter:android:name="android.intent.category.HOME"
现在我们定义了自己的filter,那么,我们在我们自己写的Launcher中将Manifest改为:
<application android:process="android.process.acore3" android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".FirstAppActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME_FIRST" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY" />
</intent-filter>
</activity>
</application>
然后将编译好的apk放到/out/target/product/generic/system/app目录下。
5)将Android自带的Launcher删除掉,包括源代码(packages/apps/Launcher)和apk(/out/target/product/generic/system/app/Launcher.apk)。
6)
做完这些工作,就可以重新编译Android了,我们可以编译修改过的几个相关的包。
如果之前编译过了Android源码,可以用mmm命令来编译部分的改动。
这里需要这样编译:
$ . build/envsetup.sh
$ mmm frameworks/base
$ mmm frameworks/base/services/java
$ mmm frameworks/policies/base/mid
$ mmm frameworks/policies/base/phone
7)
编译完成后重新生成img文件。
$ make snod
8) 现在可以启动Android模拟器来看效果了。
首先设置环境变量:
$ export ANDROID_PRODUCT_OUT= ./out/target/product/generic
然后切换到
$ cd ./out/host/linux-x86/bin
运行
$ ./emulator
这样我们启动的模拟器里面用的image就是我们刚才编译好的自己定制的东西了。
从模拟器上可以看到启动的Launcher是我们自己的Launcher,不会出现默认的Launcher了,也不会出现选择界面。
9)我们再验证一下,如果用户装上了一个其他的Launcher(Home)会怎么样。
从网上找一个一般的Launcher或者自己写一个一般的Launcher装上去,重新启动,不会出现选择界面。
按HOME键也不会出来两个HOME来选择。
这样我们就牢牢控制了用户的桌面。
只有我们自己定制的HOME才能装上。 这对于定制Android设备的厂商很有用处。
-----------------------------------------
我们下面要说的就是Launcher,把所有Launcher程序都列出来,再通过PackageManager 来获取。
1. 定义内部类 LauncherItem 用于定义Application相关属性
Java代码:
public class LauncherItem {
Drawable icon;
String name;
ComponentName component;
LauncherItem(Drawable d, String s,ComponentName cn){
icon = d;
name = s;
component = cn;
}
};
复制代码
2. 定义List lvalue 用于存放查询结果
Java代码:
public void addLauncher(){
lvalue = new ArrayList();
pkgMgt = this.getPackageManager();
//to query all launcher & load into List<>
Intent it = new Intent(Intent.ACTION_MAIN);
it.addCategory(Intent.CATEGORY_LAUNCHER);
List ra =pkgMgt.queryIntentActivities(it,0);
for(int i=0;i< p>
ActivityInfo ai = ra.get(i).activityInfo;
//String ainfo = ai.toString();
Drawable icon = ai.loadIcon(pkgMgt);
String label = ai.loadLabel(pkgMgt).toString();
ComponentName c = new ComponentName(ai.applicationInfo.packageName,ai.name);
LauncherItem item = new LauncherItem(icon,label,c);
lvalue.add(item);
}
}
复制代码
3. 定义LauncherAdapter 并指定各个item显示样式
Java代码:
public class LauncherAdapter extends BaseAdapter {
Activity activity;
public LauncherAdapter(Activity a){
activity = a;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return lvalue.size();
}
@Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return arg0;
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
return composeItem(position);
}
public View composeItem(int position){
LinearLayout layout = new LinearLayout(activity);
layout.setOrientation(LinearLayout.HORIZONTAL);
ImageView iv = new ImageView(activity);
iv.setImageDrawable(lvalue.get(position).icon);
layout.addView(iv);
TextView tv = new TextView(activity);
tv.setText(lvalue.get(position).name);
tv.setPadding(10, 5, 0, 0);
layout.addView(tv);
return layout;
}
}
复制代码
4. 启动某个item 当单击时
Java代码:
adapter = new LauncherAdapter(this);
lv.setAdapter(adapter);
lv.setOnItemClickListener(new OnItemClickListener(){
@Override
public void onItemClick(AdapterView arg0, View arg1, int arg2,long arg3) {
// TODO Auto-generated method stub
Intent intent =new Intent(Intent.ACTION_VIEW);
intent.setComponent(lvalue.get(arg2).component);
startActivity(intent);
}
});
复制代码
--------------------------------------
关于Launcher添加新空间的问题
目前在做Laucnehr的整体方案,希望可以定制出自己的Laucnher,基于Launcher的修改,已经做的差不多,但是在添加功能按键的时候遇到问题,方案如下:
在原有Hotseat基础上添加两个Hotseat,暂时不实现应用功能,只作出布局,希望达到的效果:在all_app_button的上下添加两个Imageiew,之前的三个Hotseat位置不变,然后Hotseat所在的Relativeayout整体上移至屏幕中间。
添加Hotseat的时候出现问题,Laucnehr启动不起来,原因未知,目前所做的操作如下:
在launcher.xml里面声明新添加的两个hotseat,在Launcher.java里面用findViewById联系到这两个HOtseat。因为在android里面,功能和布局是分开实现的,应该是只修改布局文件,不实现功能的话,不需要过多修改java文件。
问题提出,望达人关注,提出修改意见
-
添加Hotseat的话,需要修改下面几个文件:
1./valus/arrays.xml
2./valus/styles.xml
3./res/layout-port/launcher.xml
4./res/layout-lan/launcher.xml
5./src/Launcher.java
删除Hotseat的话,也是和这几个文件相关。
关于Launcher自适应屏幕大小的问题
把下面的代码粘贴到在AndroidManifest.xml中
<supports-screens
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:anyDensity="true"
/>
是在标签</application>外面
提取Launcher中的WorkSapce,可以左右滑动切换屏幕页面的类http://download.csdn.net/source/3246818
提取Launcher中的WorkSapce,可以左右滑动切换屏幕页面的类
源码下载:http://download.csdn.net/source/3246818
对于Launcher的桌面滑动大家应该都比较熟悉了,最好的体验应该是可以随着手指的滑动而显示不同位置的桌面,
比一般用ViewFlinger+动画所实现的手势切换页面感觉良好多了~~~~
分析了一下Launcher中的WorkSpace,里面有太多的代码我们用不上了(拖拽,长按,,,),把里面的冗余代码去掉得到实现滑动切换屏幕所必需的。。。。
新建一个ScrollLayout类,继承自ViewGroup。
重写onMeasure和onLayout两个方法:
其中onMeasure方法中,得到ScrollLayout的布局方式(一般使用FILL_PARENT),然后再枚举其中所有的子view,设置它们的布局(FILL_PARENT),这样在ScrollLayout之中的每一个子view即为充满屏幕可以滑动显示的其中一页。
在onLayout方法中,横向画出每一个子view,这样所得到的view的高与屏幕高一致,宽度为getChildCount()-1个屏幕宽度的view。
添加一个Scroller来平滑过渡各个页面之间的切换,
重写onInterceptTouchEvent和onTouchEvent来响应手指按下划动时所需要捕获的消息,例如划动的速度,划动的距离等。再配合使用scrollBy (int x, int y)方法得到慢速滑动小距离的时候,所需要显示的内容。最后当手指起来时,根据划动的速度与跨度来判断是向左滑动一页还是向右滑动一页,确保每次用户操作结束之后显示的都是整体的一个子view.
ScrollLayout源码:
package com.yao_guet.test;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;
/**
* 仿Launcher中的WorkSapce,可以左右滑动切换屏幕的类
* @author Yao.GUET
* blog: http://blog.csdn.net/Yao_GUET
* date: 2011-05-04
*/
public class ScrollLayout extends ViewGroup {
private static final String TAG = "ScrollLayout";
private Scroller mScroller;
private VelocityTracker mVelocityTracker;
private int mCurScreen;
private int mDefaultScreen = 0;
private static final int TOUCH_STATE_REST = 0;
private static final int TOUCH_STATE_SCROLLING = 1;
private static final int SNAP_VELOCITY = 600;
private int mTouchState = TOUCH_STATE_REST;
private int mTouchSlop;
private float mLastMotionX;
private float mLastMotionY;
public ScrollLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
// TODO Auto-generated constructor stub
}
public ScrollLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
mScroller = new Scroller(context);
mCurScreen = mDefaultScreen;
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
if (changed) {
int childLeft = 0;
final int childCount = getChildCount();
for (int i=0; i<childCount; i++) {
final View childView = getChildAt(i);
if (childView.getVisibility() != View.GONE) {
final int childWidth = childView.getMeasuredWidth();
childView.layout(childLeft, 0,
childLeft+childWidth, childView.getMeasuredHeight());
childLeft += childWidth;
}
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.e(TAG, "onMeasure");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("ScrollLayout only canmCurScreen run at EXACTLY mode!");
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("ScrollLayout only can run at EXACTLY mode!");
}
// The children are given the same width and height as the scrollLayout
final int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
}
// Log.e(TAG, "moving to screen "+mCurScreen);
scrollTo(mCurScreen * width, 0);
}
/**
* According to the position of current layout
* scroll to the destination page.
*/
public void snapToDestination() {
final int screenWidth = getWidth();
final int destScreen = (getScrollX()+ screenWidth/2)/screenWidth;
snapToScreen(destScreen);
}
public void snapToScreen(int whichScreen) {
// get the valid layout page
whichScreen = Math.max(0, Math.min(whichScreen, getChildCount()-1));
if (getScrollX() != (whichScreen*getWidth())) {
final int delta = whichScreen*getWidth()-getScrollX();
mScroller.startScroll(getScrollX(), 0,
delta, 0, Math.abs(delta)*2);
mCurScreen = whichScreen;
invalidate(); // Redraw the layout
}
}
public void setToScreen(int whichScreen) {
whichScreen = Math.max(0, Math.min(whichScreen, getChildCount()-1));
mCurScreen = whichScreen;
scrollTo(whichScreen*getWidth(), 0);
}
public int getCurScreen() {
return mCurScreen;
}
@Override
public void computeScroll() {
// TODO Auto-generated method stub
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
final int action = event.getAction();
final float x = event.getX();
final float y = event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "event down!");
if (!mScroller.isFinished()){
mScroller.abortAnimation();
}
mLastMotionX = x;
break;
case MotionEvent.ACTION_MOVE:
int deltaX = (int)(mLastMotionX - x);
mLastMotionX = x;
scrollBy(deltaX, 0);
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "event : up");
// if (mTouchState == TOUCH_STATE_SCROLLING) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000);
int velocityX = (int) velocityTracker.getXVelocity();
Log.e(TAG, "velocityX:"+velocityX);
if (velocityX > SNAP_VELOCITY && mCurScreen > 0) {
// Fling enough to move left
Log.e(TAG, "snap left");
snapToScreen(mCurScreen - 1);
} else if (velocityX < -SNAP_VELOCITY
&& mCurScreen < getChildCount() - 1) {
// Fling enough to move right
Log.e(TAG, "snap right");
snapToScreen(mCurScreen + 1);
} else {
snapToDestination();
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
// }
mTouchState = TOUCH_STATE_REST;
break;
case MotionEvent.ACTION_CANCEL:
mTouchState = TOUCH_STATE_REST;
break;
}
return true;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
Log.e(TAG, "onInterceptTouchEvent-slop:"+mTouchSlop);
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) &&
(mTouchState != TOUCH_STATE_REST)) {
return true;
}
final float x = ev.getX();
final float y = ev.getY();
switch (action) {
case MotionEvent.ACTION_MOVE:
final int xDiff = (int)Math.abs(mLastMotionX-x);
if (xDiff>mTouchSlop) {
mTouchState = TOUCH_STATE_SCROLLING;
}
break;
case MotionEvent.ACTION_DOWN:
mLastMotionX = x;
mLastMotionY = y;
mTouchState = mScroller.isFinished()? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mTouchState = TOUCH_STATE_REST;
break;
}
return mTouchState != TOUCH_STATE_REST;
}
}
测试程序布局:
<?xml version="1.0" encoding="utf-8"?>
<com.yao_guet.test.ScrollLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ScrollLayoutTest"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:background="#FF00"
android:layout_width="fill_parent"
android:layout_height="fill_parent"></LinearLayout>
<FrameLayout
android:background="#F0F0"
android:layout_width="fill_parent"
android:layout_height="fill_parent"></FrameLayout>
<FrameLayout
android:background="#F00F"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
</FrameLayout>
<LinearLayout
android:background="#FF00"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button1" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button2" />
</LinearLayout>
</com.yao_guet.test.ScrollLayout>
----------------------------------
Launcher 桌面的3D转屏效果实现(1)
从现有方法来讲为了实现桌面的3D转屏效果主要是通过Launcher中的workspace实现(现有的有源码的方法),具体实现见:
http://www.eoeandroid.com/viewth ... p;extra=&page=1 (写这篇文章也是为了“报答”该作者开源的贡献,共同学习,在此也感谢他一下)
不过该方法存在以下几个问题:
1. 不同机器的分辨率和内存大小不同,从而使用cache保持截图的方法很有可能会出现内存方面的错误
2. 界面上面的变化,例如图标增加和删除,需要程序对应做出很多修改,用以保证整体效果的统一。其根本原因就是修改的模块位置在Launcher中过于考上
3. 图标变形和覆盖(我在2.2源码上总是搞不出来,╮(╯▽╰)╭)
转载请注明http://ishelf.javaeye.com/admin/blogs/836929
依据以上问题本文从每个屏的dispatchDraw入手,修改CellLayout的dispatchDraw方法,这篇文章先给出2D的实现方式(利用Matrix实现):
上个图
由于代码过多,本文只给出做过修改的代码
///CellLayout.java
@Override
public void dispatchDraw(Canvas canvas) {
long start_time = System.currentTimeMillis();
startRotate(canvas, currentX, canvas.getWidth(), canvas.getHeight());
super.dispatchDraw(canvas);
canvas.restore();
long end_time = System.currentTimeMillis();
Log.d("CellLayout" + currentScrenn, (end_time - start_time) + " ms");
}
// 上面的Log信息是用来对比用opengl实现两者效率
//startRotate使用来计算该屏显示的位置以及显示的大小,xCor是手指移动的位置大小
public void startRotate(Canvas mCanvas, float xCor, int width, int height) {
boolean flag = true;
if (isCurrentScrenn && xCor < 0) {
xCor = width + xCor;
flag = false;
} else if (isCurrentScrenn && xCor >= 0) {
// xCor = width - xCor;
} else if (!isCurrentScrenn && xCor < 0) {
xCor = width + xCor;
} else if (!isCurrentScrenn && xCor >= 0) {
flag = false;
}
final float SPAN = 0.000424f;
float f = xCor - 10;
if (f <= 0) {
f = 0;
xCor = 10;
}// the maximum left
float value = f * SPAN;
if (f > width) {
xCor = width - 10;
value = 0.127225f;
}// the maximum right
if (isBorder) {
doDraw(mCanvas, new float[] {
0, 0, width, 0, width, height, 0, height
}, new float[] {
0, 0, width, 0, width, height, 0, height
});
} else if (!flag) {
doDraw(mCanvas, new float[] {
0, 0, width, 0, width, height, 0, height
}, new float[] {
0, 0, xCor, height * (1 / 7.0f - value), xCor, height * (6 / 7.0f + value), 0,
height
});
} else {
doDraw(mCanvas, new float[] {
0, 0, width, 0, width, height, 0, height
}, new float[] {
xCor, height * (1 / 30.0f + value), width, 0, width, height, xCor,
height * (29 / 30.0f - value)
});
}
}
private Matrix mMatrix = new Matrix();
private int currentScrenn;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private boolean isBorder;
//doDraw使用计算该屏如何变形,这里使用matrix的polyToPoly来实现,具体描述见APIDemo
private void doDraw(Canvas canvas, float src[], float dst[]) {
canvas.save();
mMatrix.setPolyToPoly(src, 0, dst, 0, src.length >> 1);
canvas.concat(mMatrix);
switch (currentScrenn) {
case 0:
mPaint.setColor(Color.RED);
break;
case 1:
mPaint.setColor(Color.BLUE);
break;
case 2:
mPaint.setColor(Color.YELLOW);
break;
case 3:
mPaint.setColor(Color.CYAN);
break;
case 4:
mPaint.setColor(Color.GREEN);
break;
}
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawRect(0, 0, src[4], src[5], mPaint);
}
复制代码
以下是workspace,该类主要是要传给cellLayout移动的参数
// 该方法用来画屏
protected void dispatchDraw(Canvas canvas) {
boolean restore = false;
int restoreCount = 0;
// ViewGroup.dispatchDraw() supports many features we don't need:
// clip to padding, layout animation, animation listener, disappearing
// children, etc. The following implementation attempts to fast-track
// the drawing dispatch by drawing only what we know needs to be drawn.
boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN;
Log.d("Scroller","dispatchDraw"+mScrollX);
// If we are not scrolling or flinging, draw only the current screen
if (fastDraw) {
((CellLayout) getChildAt(mCurrentScreen)).setPara(mCurrentScreen,
(mCurrentScreen - mCurrentScreen) >= 0 ? true : false, true,
mChangeMotionX - mLastMotionX);
drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());
} else {
final long drawingTime = getDrawingTime();
final float scrollPos = (float) mScrollX / getWidth();
final int leftScreen = (int) scrollPos;
final int rightScreen = leftScreen + 1;
if (leftScreen >= 0) {
((CellLayout) getChildAt(leftScreen)).setPara(leftScreen,
(leftScreen - mCurrentScreen) >= 0 ? true : false, scrollPos == leftScreen,
mChangeMotionX - mLastMotionX);
drawChild(canvas, getChildAt(leftScreen), drawingTime);
}
if (scrollPos != leftScreen && rightScreen < getChildCount()) {
((CellLayout) getChildAt(rightScreen)).setPara(rightScreen, mCurrentScreen
- rightScreen >= 0 ? true : false, scrollPos == leftScreen, mChangeMotionX
- mLastMotionX);
drawChild(canvas, getChildAt(rightScreen), drawingTime);
}
}
if (restore) {
canvas.restoreToCount(restoreCount);
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mLauncher.isWorkspaceLocked()) {
return false; // We don't want the events. Let them fall through to
// the all apps view.
}
if (mLauncher.isAllAppsVisible()) {
// Cancel any scrolling that is in progress.
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
snapToScreen(mCurrentScreen);
return false; // We don't want the events. Let them fall through to
// the all apps view.
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
/*
* If being flinged and user touches, stop the fling. isFinished
* will be false if being flinged.
*/
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
// Remember where the motion event started
mLastMotionX = ev.getX();
mChangeMotionX = mLastMotionX;
mActivePointerId = ev.getPointerId(0);
if (mTouchState == TOUCH_STATE_SCROLLING) {
enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1);
}
break;
case MotionEvent.ACTION_MOVE:
if (mTouchState == TOUCH_STATE_SCROLLING) {
// Scroll to follow the motion event
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float deltaX = mLastMotionX - x;
mLastMotionX = x;
if (deltaX < 0) {
if (mTouchX > 0) {
mTouchX += Math.max(-mTouchX, deltaX);
mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
invalidate();
}
} else if (deltaX > 0) {
final float availableToScroll = getChildAt(getChildCount() - 1).getRight()
- mTouchX - getWidth();
if (availableToScroll > 0) {
mTouchX += Math.min(availableToScroll, deltaX);
mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
invalidate();
}
} else {
awakenScrollBars();
}
}
break;
case MotionEvent.ACTION_UP:
if (mTouchState == TOUCH_STATE_SCROLLING) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
final int velocityX = (int) velocityTracker.getXVelocity(mActivePointerId);
final int screenWidth = getWidth();
final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
final float scrolledPos = (float) mScrollX / screenWidth;
mChangeMotionX = mLastMotionX;
if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
// Fling hard enough to move left.
// Don't fling across more than one screen at a time.
final int bound = scrolledPos < whichScreen ? mCurrentScreen - 1
: mCurrentScreen;
snapToScreen(Math.min(whichScreen, bound), velocityX, true);
} else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
// Fling hard enough to move right
// Don't fling across more than one screen at a time.
final int bound = scrolledPos > whichScreen ? mCurrentScreen + 1
: mCurrentScreen;
snapToScreen(Math.max(whichScreen, bound), velocityX, true);
} else {
snapToScreen(whichScreen, 0, true);
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
mTouchState = TOUCH_STATE_REST;
mActivePointerId = INVALID_POINTER;
break;
case MotionEvent.ACTION_CANCEL:
mTouchState = TOUCH_STATE_REST;
mActivePointerId = INVALID_POINTER;
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
}
return true;
}
//修改该方法主要目的是记录滑动的距离
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final boolean workspaceLocked = mLauncher.isWorkspaceLocked();
final boolean allAppsVisible = mLauncher.isAllAppsVisible();
if (workspaceLocked || allAppsVisible) {
return false; // We don't want the events. Let them fall through to
// the all apps view.
}
/*
* This method JUST determines whether we want to intercept the motion.
* If we return true, onTouchEvent will be called and we do the actual
* scrolling there.
*/
/*
* Shortcut the most recurring case: the user is in the dragging state
* and he is moving his finger. We want to intercept this motion.
*/
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
return true;
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_MOVE: {
/*
* mIsBeingDragged == false, otherwise the shortcut would have
* caught it. Check whether the user has moved far enough from
* his original down touch.
*/
/*
* Locally do absolute value. mLastMotionX is set to the y value
* of the down event.
*/
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
final int xDiff = (int) Math.abs(x - mLastMotionX);
final int yDiff = (int) Math.abs(y - mLastMotionY);
final int touchSlop = mTouchSlop;
boolean xMoved = xDiff > touchSlop;
boolean yMoved = yDiff > touchSlop;
if (xMoved || yMoved) {
if (xMoved) {
// Scroll if the user moved far enough along the X axis
mTouchState = TOUCH_STATE_SCROLLING;
mLastMotionX = x;
mTouchX = mScrollX;
mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1);
}
// Either way, cancel any pending longpress
if (mAllowLongPress) {
mAllowLongPress = false;
// Try canceling the long press. It could also have been
// scheduled
// by a distant descendant, so use the mAllowLongPress
// flag to block
// everything
final View currentScreen = getChildAt(mCurrentScreen);
currentScreen.cancelLongPress();
}
}
break;
}
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
// Remember location of down touch
mLastMotionX = x;
mChangeMotionX = x;
mLastMotionY = y;
mActivePointerId = ev.getPointerId(0);
mAllowLongPress = true;
/*
* If being flinged and user touches the screen, initiate drag;
* otherwise don't. mScroller.isFinished should be false when
* being flinged.
*/
mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (mTouchState != TOUCH_STATE_SCROLLING) {
final CellLayout currentScreen = (CellLayout) getChildAt(mCurrentScreen);
if (!currentScreen.lastDownOnOccupiedCell()) {
getLocationOnScreen(mTempCell);
// Send a tap to the wallpaper if the last down was on
// empty space
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
mWallpaperManager.sendWallpaperCommand(getWindowToken(),
"android.wallpaper.tap",
mTempCell[0] + (int) ev.getX(pointerIndex), mTempCell[1]
+ (int) ev.getY(pointerIndex), 0, null);
}
}
// Release the drag
clearChildrenCache();
mTouchState = TOUCH_STATE_REST;
mActivePointerId = INVALID_POINTER;
mAllowLongPress = false;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
}
/*
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
return mTouchState != TOUCH_STATE_REST;
}
复制代码
这些类的修改特别是变换时一定要注意canvas的save和restore方法,不清楚的先百度一下,不然很容易就变形了。下篇讨论使用openGL实现的方法
http://ishelf.javaeye.com/admin/blogs/836955,这篇给出了使用camera实现转屏的方法,并和这篇进行了比较,也可以说是2D和3D的不同实现。欢迎大家给以指正
Android_launcher中celllayout类简单分析
(1) 大家都知道workspace是有celllayout组成
Celllayout被划分为了4行4列的表格,用Boolean类型的mOccupied二维数组来标记每个cell是否被占用。在attrs.xml中定义了shortAxisCells和longAxisCells分别存储x轴和y轴方向的cell个数。在Celllayout构造函数中初始化。
(2) 内部类CellInfo为静态类,实现了ContextMenu.ContextMenuInfo接口,其对象用于存储cell的基本信息
VacantCell类用于存储空闲的cell,用到了同步机制用于管理对空闲位置的操作。所有的空cell都存储在vacantCells中。
cellX和cellY用于记录cell的位置,起始位0。如:(0,0) (0,1),每一页从新开始编号。
clearVacantCells作用是将Vacant清空:具体是释放每个cell,将list清空。
findVacantCellsFromOccupied从存放cell的数值中找到空闲的cell。在Launcher.Java中的restoreState方法中调用。
(3) mPortrait用于标记是横屏还是竖屏,FALSE表示竖屏,默认为FALSE。
(4)修改CellLayout页面上cell的布局:
CellLayout页面上默认的cell为4X4=16个,可以通过修改配置文件来达到修改目的。
在CellLayout.Java类的CellLayout(Context context, AttributeSet attrs, int defStyle)构造方法中用变量mShortAxisCells和mLongAxisCells存储行和列。
其值是在自定义配置文件attrs.xml中定义的,并在workspace_screen.xml中赋初值的,初值都为4,即4行、4列。可以在workspace_screen.xml修改对应的值。
注意:CellLayout构造方法中从attrs.xml中获取定义是这样的:mShortAxisCells = a.getInt(R.styleable.CellLayout_shortAxisCells, 4);当workspace_screen.xml中没有给定义的变量赋值时,上面的4就起作用。
(5)Launcher(主屏/待机) App的BUG: 没有初始化定义CellLayout中屏幕方向的布尔值参数:
Launcher App:\cupcake\packages\apps\Launcher
复制代码
待机画面分为多层,桌面Desktop Items在\res\layout-*\workspace_screen.xml中置:
<com.android.launcher.CellLayout
... ...
launcher:shortAxisCells="4"
launcher:longAxisCells="4"
... ...
/>
复制代码
以上表示4行4列.
再看看com.android.launcher.CellLayout ,其中有定义屏幕方向的参数:
private boolean mPortrait;
复制代码
但是一直没有初始化,也就是mPortrait=false,桌面的单元格设置一直是以非竖屏(横屏)的设置定义进行初始化。
再来看看横屏和竖屏情况下的初始化不同之处,就可以看出BUG了:
boolean[][] mOccupied;//二元单元格布尔值数组
if (mPortrait) {
mOccupied = new boolean[mShortAxisCells][mLongAxisCells];
} else {
mOccupied = new boolean[mLongAxisCells][mShortAxisCells];
}
复制代码
如果我们满屏显示桌面(横向和纵向的单元格数不一致),而不是默认的只显示4行4列,则mShortAxisCells = 4, mLongAxisCells = 5,数组应该初始化是:new boolean[4][5],但是实际是按照非竖屏处理,初始化成了new boolean[5][4],会产生数组越界异常。
可以在构造函数中,添加通过屏幕方向初始化mPortrait,代码如下:
public CellLayout(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
mPortrait = this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;// 新增代码