Android 4.0 Launher分析

一.整个Launcher应用的构成.

 

要看一个app的构成和入口,首先得了解它的AndroidManifest.xml.application标签包含以下内容:

1.Launcher 整个桌面的载体,整个应用真正的入口 .

2.wallpaperChooser 选择壁纸界面

3. RocketLauncher 好像没有用到.

4.installShorcutReceiver/uninstallShorcutReceiver接受添加/删除快捷方式的广播接收器

5.LauncherProvider 操作数据库,保存home的数据,比如桌面有那些小部件,快捷方式等.

 

二.Launcher 架构:

   

    从MVC模式分析,Launcher处于controller层,LauncherMode 处于mode层,而view层是DragLayer,它是整个lanuncher布局的根.

通过launcher.xml可以看到整个布局的层次结构:

从左图可以看出Draglayer是launcher布局的根节点.

前两个imageview是divider和page_indicator.

Workspace作为子view,默认包含五个cellLayout(每屏)

 

 

Hotseat就是桌面底部应用快捷栏,与Android2.3不同的是它也是个cellLayout,可以自定义里面的快捷方式.

 

 

 

SearchDropTargetBar 就是桌面固定不动的搜索框,以及删除图标时的删除区。

Android使用向导

 

应用程序界面.

 

三.启动流程分析.

   

启动LauncherActivity 进入onCreate()

执行LauncherApplicationapp = ((LauncherApplication)getApplication());

    mModel = app.setLauncher(this);

    初始化LauncherMode对象.

 

---->整个应用真正的入口,LaucherApplicationonCreate();

              创建mModel =new LauncherModel(this,mIconCache);LauncherMode继承自   BroadcastReceiver.注册监听应用应用程序的安装,卸载等等。

LauncherApplication 注册ContentResolver监   听favorites的变化。

     private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
    static {
        sWorkerThread.start();
    }
    private static final Handler sWorker = new Handler(sWorkerThread.getLooper());

 mLoaderTask = new LoaderTask(mApp, isLaunching);
                sWorkerThread.setPriority(Thread.NORM_PRIORITY);
                sWorker.post(mLoaderTask);

private class LoaderTask implements Runnable {

 

------>launcher.java 初始化Widget框架相关类       

         mAppWidgetManager = AppWidgetManager.getInstance(this);

       mAppWidgetHost =new LauncherAppWidgetHost(this,APPWIDGET_HOST_ID);

       mAppWidgetHost.startListening();作为Appwidget Service和 app交互的桥梁。

 

------>setupViews()初始化所有view对象

       mDragLayer.setup(this,dragController);配置DragController对象,DrayLayer的一些事件处理,会调用DragController中的方法处理。如:onInterceptTouchEvent,onTouchEvent.

       mWorkspace.setHapticFeedbackEnabled(false);

    mWorkspace.setOnLongClickListener(this);

    mWorkspace.setup(dragController);

    dragController.addDragListener(mWorkspace)

       初始化workspace信息,实现DragListener,赋给 dragController,workspace中view拖动到不同状态的具体处理,会调用workspace中的方法。如onStartDrag,onDrop等。

       mAppsCustomizeContent.setup(this,dragController);初始化应用主界面。

       初始化dragController信息,如:dragController.setDragScoller(mWorkspace);

       初始化桌面搜索和删除view。mSearchDropTargetBar.setup(this,dragController);

 

-------->mModel.startLoader(this, true);开始加载launcher数据。

       进入LauncherModel中 startLoader,加载默认的AllAppsList.loadTopPackage(context);

       mLoaderTask= new LoaderTask(context, isLaunching);sWorker.post(mLoaderTask);

       通过Task加载Launcher的内容。其run中会调用一下方法加载和绑定workpace和AllApp内容        onlyLoadWorkspace();onlyLoadAllApps();bindWorkspace();onlyBindAllApps();

 

------>LoadWorkspace()加载桌面上的内容。

              sWorkspaceItems.clear();

       sAppWidgets.clear();

       sFolders.clear();

       sItemsIdMap.clear();

       sDbIconCache.clear();

              清空各个list,每个放的信息显而易见,如AppWidgets  小部件信息。        contentResolver.query(LauncherSettings.Favorites.CONTENT_URI,...);

       查询Favorites表中的信息,其中存放的信息包括桌面上的小部件,快捷方式,文件夹等,  分类初始化后,放入list中以备后用。

 

------>loadApps加载应用程序界面信息

       packageManager.queryIntentActivities(mainIntent,0);查询所有app信息,每个app对应一个ResolveInfo对象。

       mAllAppsList.add(newApplicationInfo(packageManager, apps.get(i),

                            mIconCache,mLabelCache));

       根据app的信息创建ApplicationInfo对象,并放入mAllAppsList中以备后用。

 

------>bindWorkspace()绑定桌面上的小部件,快捷方式等。

       首先通过post一Runnable()调用为callbacks.startBinding();callbacks其实是Launcher

       在launcherApplication调用mModel.initialize(launcher);

       创建多个new Runnable(),通过Handler的post执行callbacks.bindItems,       callbacks.bindFolders,callbacks.bindAppWidget,调用Launcher中对应的方法。

       Launcher中绑定内容以bindAppWidget(...)为例,根据widget的信息

       mAppWidgetHost.createView(this,appWidgetId, appWidgetInfo);创建view,通过

       workspace.addInScreen添加到workpace中。

       和2.3不同的是addInScreen中加入了一下判断,

       if(container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {

            layout= mLauncher.getHotseat().getLayout();

            child.setOnKeyListener(null);

            //Hide folder title in the hotseat

            if (child instanceof FolderIcon) {

                ((FolderIcon)child).setTextVisible(false);

            }

            if(screen < 0) {

                screen= mLauncher.getHotseat().getOrderInHotseat(x, y);

            } else{

                x= mLauncher.getHotseat().getCellXFromOrder(screen);

                y= mLauncher.getHotseat().getCellYFromOrder(screen);

            }

        }

       这是由于,4.0的hostseat可以自己定制,把app快捷方式突入即可。

 

------>接下来Launcher调用onResume()

       if(mRestoring || mOnResumeNeedsLoad) {//如果Launcher回到前台,需要重新加载,执行

            mWorkspaceLoading= true;

            mModel.startLoader(this,true);

            mRestoring= false;

            mOnResumeNeedsLoad= false;

        }

       mAppsCustomizeTabHost.onResume();唤醒应用程序界面。

 

四.功能能点分析

 

       1.托放功能分析

   

    Luancher有一个相对比较复杂的功能就是拖放功能,要深入了解launcher,深入理解拖放功能是有必要的.
  1.首先直观感受什么时候开始拖放?我们长按桌面一个应用图标或者控件的时候拖放就开始了,包括在应用程序界面中长按应用图标,下面就是我截取的拖放开始的代码调用堆栈

 

1.  

2. at com.android.launcher2.DragController.startDrag

3. at com.android.launcher2.Workspace.startDrag

4. at com.android.launcher2.Launcher.onLongClick

5. at android.view.View.performLongClick

6. at android.widget.TextView.performLongClick

7. at android.view.View$CheckForLongPress.run

8. at android.os.Handler.handleCallback

9. at android.os.Handler.dispatchMessage

10. at android.os.Looper.loop

 

    桌面应用图标由Launcher.onLongClick负责监听处理,插入断点debug进入onLongclick函数

         if (!(vinstanceof CellLayout)) {

            v = (View)v.getParent().getParent();

        }

                   CellLayout.CellInfolongClickCellInfo = (CellLayout.CellInfo) v.getTag();

                   //获得长按的item的tag

                   ...

                  else {

                if (!(itemUnderLongClickinstanceof Folder)) {

                                    

                             // User long pressed on an item

                    mWorkspace.startDrag(longClickCellInfo);

    首先是获取被拖动的对象v.getTag()( v : worksapce.celllayout,hotseat),Tag什么时候被设置进去的了.相应onTouch之前,

onInterceptTouchEvent的处理从DargLayer,到workspace,再到CellLayout中的onInterceptTouchEven    t.
 @Override

   publicbooleanonInterceptTouchEvent(MotionEvent ev) {

        // First we clear the tag toensure that on every touch down we start with a fresh slate,

        // even in the case where wereturn early. Not clearing here was causing bugs whereby on

        // long-press we'd end up pickingup an item from a previous drag operation.

                   ...     

      if (action == MotionEvent.ACTION_DOWN) {

            clearTagCellInfo();

        }

        if (mInterceptTouchListener !=null &&mInterceptTouchListener.onTouch(this, ev)) {

                   //mInterceptTouchListener是workspace中的。

            returntrue;

        }

        if (action == MotionEvent.ACTION_DOWN) {

            setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());

        }

        returnfalse;

   }

         clearTagCellInfo------》初始化tag信息。

         privatevoid clearTagCellInfo() {

        final CellInfo cellInfo =mCellInfo;

        cellInfo.cell =null;

        cellInfo.cellX = -1;

        cellInfo.cellY = -1;

        cellInfo.spanX = 0;

        cellInfo.spanY = 0;

        setTag(cellInfo);

   }

         setTagToCellInfoForPoint-------

 

         final nt count =mChildren.getChildCount();

                   ...

        boolean found =false;

        for (int i = count - 1; i >= 0; i--) {

                    final View child = mShortcutsAndWidgets.getChildAt(i);

                    child.getHitRect(frame);

                  

                   // The child hitrectis relative to the CellLayoutChildren parent, so we need to

                // offset that by thisCellLayout's padding to test an (x,y) point that is relative

                // to this view.

          frame.offset(mPaddingLeft, mPaddingTop);

                   if (frame.contains(x, y)) {

                    cellInfo.cell = child;

                    cellInfo.cellX = lp.cellX;

                    cellInfo.cellY = lp.cellY;

                    cellInfo.spanX = lp.cellHSpan;

                    cellInfo.spanY = lp.cellVSpan;

                    found = true;

                    break;

         }

                   ...

                   if (!found) {

            finalint cellXY[] =mTmpXY;

            pointToCellExact(x, y, cellXY);

            cellInfo.cell =null;

            cellInfo.cellX = cellXY[0];

            cellInfo.cellY = cellXY[1];

            cellInfo.spanX = 1;

            cellInfo.spanY = 1;

        }

        setTag(cellInfo);

    看了上面代码知道,当开始点击桌面时,celllayout会先清楚tag,然后调用就会根据点击区域去查找在该区域是否有child存在,若有把它设置为tag.cell,后面在开始拖放时launcher.onlongclick中对tag进行处理.
  这个理顺了,再深入到workspace.startDrag函数.

         child.setVisibility(GONE);//隐藏真是item.

    // The outline is usedto visualize where the item will land if dropped

        mDragOutline =createDragOutline(child, canvas, bitmapPadding);

                   //创建拖动桌面item时,item下面可放置位置的轮廓。

    workspace.startDrag调用DragController.startDrag去处理拖放
    mDragController.startDrag(child, this,child.getTag(), DragController.DRAG_ACTION_MOVE);
  再分析一下上面调用的几个参数
  child = tag.cell  ->the longclick icon
  this = workspace

child.getTag()是什么呢?在什么时候被设置?再仔细回顾原来launcher加载过程代码,在launcher.createShortcut中它被设置了:注意下面我代码中的注释
    View createShortcut(int layoutResId, ViewGroupparent, ShortcutInfo info) {

        BubbleTextView favorite =(BubbleTextView)mInflater.inflate(layoutResId, parent,false);

        favorite.applyFromShortcutInfo(info, mIconCache);

        favorite.setOnClickListener(this);

        return favorite;

   }

         进入 applyFromShortcutInfo我们会看到:

         publicvoid applyFromShortcutInfo(ShortcutInfo info, IconCacheiconCache) {

       Bitmap b =info.getIcon(iconCache);

        setCompoundDrawablesWithIntrinsicBounds(null,new FastBitmapDrawable(b),//图标

                null,null);

        setText(info.title);//图标下的字

        setTag(info);//设置item信息

   }


 继续深入解读DragController.startDrag函数

publicvoid startDrag(Bitmap b,int dragLayerX,int dragLayerY,

    DragSource source, Object dragInfo, int dragAction, Point dragOffset, RectdragRegion) {

        // Hide soft keyboard, if visible

        if (mInputMethodManager ==null) {

            mInputMethodManager = (InputMethodManager)

                    mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE);

        }

        mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);

        for (DragListener listener :mListeners) {

            listener.onDragStart(source,dragInfo, dragAction);

        }

        finalint registrationX =mMotionDownX – dragLayerX;

              //记住手指点击位置与屏幕左上角位置偏差

        finalint registrationY =mMotionDownY - dragLayerY;

        finalint dragRegionLeft = dragRegion ==null ? 0 : dragRegion.left;

        finalint dragRegionTop = dragRegion ==null ? 0 : dragRegion.top;

              //手指触点相对draglayer左上角的偏移量。

        mDragObject.dragSource = source;

        mDragObject.dragInfo = dragInfo;

        mVibrator.vibrate(VIBRATE_DURATION);

 

              final DragView dragView =mDragObject.dragView =new DragView(mLauncher,                      b,registrationX, registrationY, 0, 0, b.getWidth(), b.getHeight());

              //用于手指拖动的,一个和这是item大小样式相同的view。

              ...

        dragView.show(mMotionDownX,mMotionDownY);

                   //显示drayView,并开启弹出到手指触点的动画。

        handleMoveEvent(mMotionDownX,mMotionDownY);

    }

 

       对于 handleMoveEvent(...):

       privatevoid handleMoveEvent(int x, int y) {

              //移动到手指点击位置

        mDragObject.dragView.move(x, y);

        // Drop on someone?

        finalint[] coordinates =mCoordinatesTemp;

                   //查找可放置此view的目标容器。

        DropTarget dropTarget =findDropTarget(x, y, coordinates);

                  

                   }

                   接下进入 findDropTarget(...):

          DropTarget findDropTarget(int x,int y,int[] dropCoordinates) {

                 ...

            DropTarget target =dropTargets.get(i);

            target.getHitRect(r);

            // Convert the hitrect toDragLayer coordinates

                            //target位置

       target.getLocationInDragLayer(dropCoordinates);

            r.offset(dropCoordinates[0] -target.getLeft(), dropCoordinates[1] - target.getTop());

                    ...

            if (r.contains(x, y)) {//手机触点是否在target区域

                            //一般返回空,是否一个可以吧事件传递给一个object

                DropTarget delegate =target.getDropTargetDelegate(mDragObject);

                if (delegate !=null) {...}

                // Make dropCoordinates relativeto the DropTarget

                                     //手指触点,相对target位移

                dropCoordinates[0] = x -dropCoordinates[0];

                dropCoordinates[1] = y -dropCoordinates[1];

                return target;

            }

        }

 

通过上边代码,找到目前可放置的target后,

接下来继续handleMoveEvent(...)中的代码:

        if (dropTarget !=null) {

                     //一般返回空,是否一个可以吧事件传递给一个object

            DropTarget delegate =dropTarget.getDropTargetDelegate(mDragObject);

            if (delegate !=null) {...}

            if (mLastDropTarget != dropTarget) {

                if (mLastDropTarget !=null) {

                            //和上次在不同target,调用 onDragExit,重置上次target数据信息。

                    mLastDropTarget.onDragExit(mDragObject);

                }

                            //准备当前Target数据,

                dropTarget.onDragEnter(mDragObject);

            }

            dropTarget.onDragOver(mDragObject);

        } else {

            if (mLastDropTarget !=null) {

                mLastDropTarget.onDragExit(mDragObject);

            }

       }

         接着进入onDragOver:如workspace中的onDragOver

         publicvoid onDragOver(DragObject d){

      // Skip drag over events while weare dragging over side pages

        if (mInScrollArea)return;

        if (mIsSwitchingState)return;

        // Identify whether we havedragged over a side page

        if (isSmall()) {

            if (mLauncher.getHotseat() !=null &&!isExternalDragWidget(d)) {

                mLauncher.getHotseat().getHitRect(r);

                if (r.contains(d.x, d.y)) {

                            //target是否是launcher上的快捷栏。

                    layout = mLauncher.getHotseat().getLayout();

                }

            }

            if (layout ==null) {

                     //获得view托进的cellayout

                layout =findMatchingPageForDragOver(d.dragView, d.x, d.y,false);

            }

                    ...

        }

        // Handle the drag over

        if (mDragTargetLayout !=null) {

        

            // We want the point to be mappedto the dragTarget.

      if (mLauncher.isHotseatLayout(mDragTargetLayout)) {

         mapPointFromSelfToSibling(mLauncher.getHotseat(),mDragViewVisualCenter);

       } else {

          mapPointFromSelfToChild(mDragTargetLayout,mDragViewVisualCenter,null);

       }

            ItemInfo info = (ItemInfo) d.dragInfo;

                     //找到view放置的离当前最近的cell位置信息。

     mTargetCell = findNearestArea((int)mDragViewVisualCenter[0],

           (int)mDragViewVisualCenter[1], 1, 1,mDragTargetLayout,mTargetCell);

      final View dragOverView =mDragTargetLayout.getChildAt(mTargetCell[0],

                    mTargetCell[1]);//可放置的target view

           

       我们继续handleMoveEvent(...)中剩余代码。

        mLastDropTarget = dropTarget;

  //After a scroll, the touch point will still be in the scroll region.

  //Rather than scrolling immediately, require a bit oftwiddling to scroll again

    finalint slop =ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();

        mDistanceSinceScroll +=

        Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));

        mLastTouch[0] = x;

        mLastTouch[1] = y;

                            ...

                    mScrollRunnable.setDirection(SCROLL_LEFT);

                    mHandler.postDelayed(mScrollRunnable,SCROLL_DELAY);

       

-------->最后拖动的出口是在DragController中onTouchEvent的MotionEvent.ACTION_UP

 privatevoid drop(float x, float y) {

        finalint[] coordinates =mCoordinatesTemp;

              //查找可放置的target

final DropTarget dropTarget =findDropTarget((int) x, (int) y, coordinates);

        mDragObject.x = coordinates[0];

        mDragObject.y = coordinates[1];

        boolean accepted =false;

        if (dropTarget !=null) {

            mDragObject.dragComplete =true;

            dropTarget.onDragExit(mDragObject);

            if (dropTarget.acceptDrop(mDragObject)) {

                            //判断target是否可以放置view

                dropTarget.onDrop(mDragObject);

                accepted = true;

            }

        }

 mDragObject.dragSource.onDropCompleted((View) dropTarget,mDragObject, accepted);

   }

 

         publicvoid onDrop(DragObject d) {// workspace.ondrop

                  

                   //Reparent the view

      getParentCellLayoutForView(cell).removeView(cell);

           addInScreen(cell, container, screen,mTargetCell[0],mTargetCell[1],

                                mDragInfo.spanX,mDragInfo.spanY);               

                  

          if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&

                            cell instanceofLauncherAppWidgetHostView) {

                        final CellLayout cellLayout =dropTargetLayout;

                        // We post this call sothat the widget has a chance to be placed

                        // in its finallocation

              //4.0支持resize widget,以下是显示resize把手。

 finalLauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;

                        AppWidgetProviderInfopinfo = hostView.getAppWidgetInfo();

               if (pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE){

                       final Runnable resizeRunnable=new Runnable() {

                            publicvoid run() {

                            DragLayer dragLayer=mLauncher.getDragLayer();

                           dragLayer.addResizeFrame(info,hostView, cellLayout);

                                }

                            };

                            post(new Runnable() {

                                publicvoid run() {

                                    if (!isPageMoving()) {

                                                              //设置resize frame大小位置。

                                        resizeRunnable.run();

                                    } else {

                                        mDelayedResizeRunnable = resizeRunnable;

                                    }

                                }

                            });

 

:::

DragLayer = 》

 

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean handled = false;
        int action = ev.getAction();

        int x = (int) ev.getX();
        int y = (int) ev.getY();

        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                if (handleTouchDown(ev, false)) {
                    return true;
                }
            }
        }

        if (mCurrentResizeFrame != null) {
            handled = true;
            switch (action) {
                case MotionEvent.ACTION_MOVE:
                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
                    break;
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
                    mCurrentResizeFrame.onTouchUp();
                    mCurrentResizeFrame = null;
            }
        }
        if (handled) return true;
        return mDragController.onTouchEvent(ev);
    }


。。。
}

 

    AppWidgetResizeFrame  ==》 static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher,
            int spanX, int spanY) {

:::

 

                                   //保存view的信息到数据库。

   LauncherModel.moveItemInDatabase(mLauncher, info, container, screen, lp.cellX,lp.cellY);

                  

                   android4.0支持快捷方式合并:
                  
// If the item being droppedis a shortcut and the nearest drop

                // cell also contains a shortcut,then create a folder with the two shortcuts.

                if (!mInScrollArea &&createUserFolderIfNecessary(cell, container,

                        dropTargetLayout, mTargetCell, false, d.dragView,null)) {

                    return;

                }

         if(addToExistingFolderIfNecessary(cell, dropTargetLayout,mTargetCell, d,   false)) {

                    return;

                }

createUserFolderIfNecessary

                   View v = target.getChildAt(targetCell[0],targetCell[1]);

            boolean aboveShortcut =(v.getTag()instanceof ShortcutInfo);

        boolean willBecomeShortcut =(newView.getTag()instanceof ShortcutInfo);

                   //如果都是快捷方式,才能进行合并

                   if (aboveShortcut&& willBecomeShortcut) {

            ShortcutInfo sourceInfo =(ShortcutInfo) newView.getTag();

            ShortcutInfo destInfo =(ShortcutInfo) v.getTag();

            // if the drag started here, weneed to remove it from the workspace

            if (!external) {

                getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);

            }

            Rect folderLocation = new Rect();

            float scale =mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v,folderLocation);

           target.removeView(v);

            FolderIcon fi =

                mLauncher.addFolder(target, container,screen, targetCell[0], targetCell[1]);

            // If the dragView is null, wecan't animate

            boolean animate = dragView !=null;

            if (animate) {

                fi.performCreateAnimation(destInfo,v, sourceInfo, dragView, folderLocation, scale,

                        postAnimationRunnable);

            } else {

                fi.addItem(destInfo);

                fi.addItem(sourceInfo);

            }

            returntrue;

        }

                  

-------------->最后调用endDrag()

 privatevoid endDrag() {

        if (mDragging) {

            mDragging =false;

            for (DragListener listener :mListeners) {

                listener.onDragEnd();

            }

            if (mDragObject.dragView != null) {

                mDragObject.dragView.remove();

                mDragObject.dragView =null;               

            }

            mDragObject =null;

        }

到此Launcher 上view的拖动就分析完了。

 

2.

 

Launcher开关门切换效果:

Workspace:

screenScrolledStandardUI()

PagedView : screenCenter = mScrollX +halfScreenSize 代表当前页面中心移动到的位置

 

getScrollProgress(screenCenter, cl, index)获取滑动的距离占最大距离。

 

        int totalDistance = getScaledMeasuredWidth(v)+ mPageSpacing;

        int delta = screenCenter -(getChildOffset(page) -

                getRelativeChildOffset(page) +halfScreenSize);

 

        float scrollProgress = delta /(totalDistance * 1.0f);

 

这样我们可以通过scrollProgress这个百分比,计算每个屏幕每个时刻旋转的角度,透明度,然后,根据屏幕位置设置旋转的轴位置,旋转方向。

 

 

循环滑动:

由于但scrollX = 0时,viewgroup就不能向左滑动,因此应该设置初始化位置离左边界很远,10万,这样想滑动到边界也要上万次滑动。向右滑动就不停romove第一个view,添加到最后,

然后再reLayout.更新view顺序在computeScrollHelper 的else中去处理。

posted on 2012-12-07 20:51  zyuchao  阅读(888)  评论(0)    收藏  举报

导航