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行,想透彻的分析完不太现实,我只是把重点讲了下,欢迎各位批评指正微笑



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