Android4.4-Launcher源碼分析系列之CellLayout

一.CellLayout是什麼

在前面的 Android4.4-Launcher源碼分析系列之Launcher介紹分析了Launcher的佈局,CellLayout繼承自ViewGroup,

一個Workspace由多個CellLayout組成,每一個CellLayout負責裏面圖標(favorite)和widget的顯示.說白了,我們滑動屏幕的每一頁就是一個CellLayout.

二、CellLayout的佈局

CellLayout的佈局爲workspace_screen.xml.

<com.android.launcher3.CellLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:hapticFeedbackEnabled="false"      
    launcher:maxGap="@dimen/workspace_max_gap" />

hapticFeedbackEnabled是觸力反饋的意思,比如說按一下震動就是觸力反饋.

maxGap是CellLayout中元素(圖標,widget)之間的最大距離

除了這兩個屬性其他的屬性都是在代碼中定義的.

WorkSpace是在insertNewWorkspaceScreen方法中加載CellLayout的佈局的.

/**
     * @param  screenId       屏幕Id
     * @param  insertIndex	     插入的序號
     * 插入新的屏幕 
     */
    public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
    	 
    	System.out.println(".............WorkSpace.......增加一頁");
    	if (mWorkspaceScreens.containsKey(screenId)) {
            throw new RuntimeException("Screen id " + screenId + " already exists!");
        }
        //加載CellLayout的佈局
        CellLayout newScreen = (CellLayout)mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);

        newScreen.setOnLongClickListener(mLongClickListener);
        newScreen.setOnClickListener(mLauncher);
        newScreen.setSoundEffectsEnabled(false);
        mWorkspaceScreens.put(screenId, newScreen);
        mScreenOrder.add(insertIndex, screenId);
        addView(newScreen, insertIndex);
        return screenId;
    }

CellLayout上有很多Cell,都有對應的座標


紅色圓圈就是一個Cell.一個item可以佔多個Cell. 那麼怎麼知道一個item的起始座標,佔據多少格呢,CellLayout類裏有一個靜態內部類CellInfo用來紀錄這些信息.

static final class CellInfo {
        View cell;              //當前這個item對應的View 
        int cellX = -1;	    //該item水平方向上的起始單元格  
        int cellY = -1;	    //該item垂直方向上的起始單元格  
        int spanX;	    //該item水平方向上佔據的單元格數目  
        int spanY;		   //該item垂直方向上佔據的單元格數目 
        long screenId;          //屏幕所在的Id
        long container;     

        @Override
        public String toString() {
            return "Cell[view=" + (cell == null ? "null" : cell.getClass())
                    + ", x=" + cellX + ", y=" + cellY + "]";
        }
    }

之前說過CellLayout的主要屬性都在代碼裏定義的,那我們就看下它的一些重要的屬性.

       //item之間的寬度
        mWidthGap = mOriginalWidthGap =0;
        //item之間的高度
        mHeightGap = mOriginalHeightGap = 0;

現在我把item之間的寬度調到50像素,看下對比


決定水平和垂直方向的Cell個數的是以下屬性

        //一行有多少個Cell
        mCountX = (int) grid.numColumns;
        //一列有多少個Cell
        mCountY = (int) grid.numRows;
grid 是DeviceProfile類的對象

DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();

DeviceProfile類裏定義了整個Launcher的很多屬性

class DeviceProfile {
    String name;
    float minWidthDps;
    float minHeightDps;
    float numRows;
    float numColumns;
    float iconSize;
    float iconTextSize;
    float numHotseatIcons;
    float hotseatIconSize;

    boolean isLandscape;
    boolean isTablet;
    boolean isLargeTablet;
    boolean transposeLayoutWithOrientation;

    int desiredWorkspaceLeftRightMarginPx;
    int edgeMarginPx;
    Rect defaultWidgetPadding;

    int widthPx;
    int heightPx;
    int availableWidthPx;
    int availableHeightPx;
    int iconSizePx;
    int iconTextSizePx;
    int cellWidthPx;
    int cellHeightPx;
    int folderBackgroundOffset;
    int folderIconSizePx;
    int folderCellWidthPx;
    int folderCellHeightPx;
    int hotseatCellWidthPx;
    int hotseatCellHeightPx;
    int hotseatIconSizePx;
    int hotseatBarHeightPx;
    int hotseatAllAppsRank;
    int allAppsNumRows;
    int allAppsNumCols;
    int searchBarSpaceWidthPx;
    int searchBarSpaceMaxWidthPx;
    int searchBarSpaceHeightPx;
    int searchBarHeightPx;
    int pageIndicatorHeightPx;
這裏就不一一介紹了.

繼續介紹CellLayout的屬性.

這是CellLayout縮略圖背景,就是當你長按屏幕空白處時CellLayout縮小時的背景圖.

 mNormalBackground = res.getDrawable(R.drawable.screenpanel);

下面兩個分別是滑動屏幕到左右邊緣,繼續拖動而不能滑動過去時顯示的背景.

mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left)
mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
最後我們看下CellLayout的觸摸事件

CellLayout只有一個onInterceptTouchEvent方法

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        
        final int action = ev.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            System.out.println(".................776"+"ACTION_DOWN1");
        	//清除cellInfo信息
            clearTagCellInfo();
         }

        if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
            return true;
        }
        
        if (action == MotionEvent.ACTION_DOWN) {
        	System.out.println(".................787"+"ACTION_DOWN2");
        	setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
        }
        return false;
    }
當按下屏幕時會執行clearTagCellInfo方法和etTagToCellInfoForPoint方法,如下圖


默認是返回false的,如果mInterceptTouchListener不爲空並且執行了onTouch方法那麼返回true.mInterceptTouchListener

是在setOnInterceptTouchListener方法裏初始化的.

public void setOnInterceptTouchListener(View.OnTouchListener listener) {
        mInterceptTouchListener = listener;
    }
setOnInterceptTouchListener是在WorkSpace調用的

  CellLayout cl = ((CellLayout) child);
  cl.setOnInterceptTouchListener(this);
現在回頭看下clearTagCellInfo方法和etTagToCellInfoForPoint方法

clearTagCellInfo方法是是清除cellInfo的信息,然後設置一個Tag

 private void clearTagCellInfo() {
        final CellInfo cellInfo = mCellInfo;
        cellInfo.cell = null;
        cellInfo.cellX = -1;
        cellInfo.cellY = -1;
        cellInfo.spanX = 0;
        cellInfo.spanY = 0;
        setTag(cellInfo);
    }
setTagToCellInfoForPoint是將位置信息保存在CellLayout的tag中  

public void setTagToCellInfoForPoint(int touchX, int touchY) {
        final CellInfo cellInfo = mCellInfo;
        Rect frame = mRect;
        final int x = touchX + getScrollX();
        final int y = touchY + getScrollY();
        final int count = mShortcutsAndWidgets.getChildCount();

        boolean found = false;
        for (int i = count - 1; i >= 0; i--) {
            final View child = mShortcutsAndWidgets.getChildAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            //如果item可見並且item有動畫
            if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
                    lp.isLockedToGrid) {
            	//獲取item的尺寸信息,相對於CellLayout  
            	child.getHitRect(frame);

                float scale = child.getScaleX();
                frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
                        child.getBottom());
                
                frame.offset(getPaddingLeft(), getPaddingTop());
                frame.inset((int) (frame.width() * (1f - scale) / 2),
                        (int) (frame.height() * (1f - scale) / 2));
                //如果當前事件正好落在該child上  
                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;
                }
            }
        }

        mLastDownOnOccupiedCell = found;
        //如果點擊的是空白區域
        if (!found) {
            final int cellXY[] = mTmpXY;
            //得到當前事件所在的單元格  
            pointToCellExact(x, y, cellXY);
            //然後保存當前位置信息  
            cellInfo.cell = null;
            cellInfo.cellX = cellXY[0];
            cellInfo.cellY = cellXY[1];
            cellInfo.spanX = 1;
            cellInfo.spanY = 1;
        }
        //將位置信息保存在CellLayout的tag中  
        setTag(cellInfo);
    }
CellLayout代碼有3369行,想透徹的分析完不太現實,我只是把重點講了下,歡迎各位批評指正微笑



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章