launcher 应用

应用的图标---
在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

&nbsp;
  @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;// 新增代码
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值