说说Android桌面(Launcher应用)背后的故事(三)——CellLayout的秘密

2024-04-04 20:18

本文主要是介绍说说Android桌面(Launcher应用)背后的故事(三)——CellLayout的秘密,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

博客搬家啦——为了更好地经营博客,本人已经将博客迁移至www.ijavaboy.com。这里已经不再更新,给您带来的不便,深感抱歉!这篇文章的新地址:点击我微笑


 CellLayout的设计主要为了存放大小不一的控件。为了更好的控制item的添加和删除,选择直接继承ViewGroup来实现该控件。

我们长按桌面的时候,有两种情况,一种是我们按的是一个item,还有一种是我们按的是一个空的位置。这里,就有一个问题。

1、我怎么知道当前按下的位置上是空白区域还是item呢?

2、就算我知道了当前的位置坐标,我又如何知道当前的坐标属于哪个单元格呢?

3、如果上面两个问题都解决了,当我选择了某个要添加的item,这个item怎么样才能添加到指定的单元格呢,怎么根据当前item的大小来分配大小合适的空间呢?

为了处理单元格和item占据的空间问题,CellLayout按照如下图示进行布局:

下面就来看看CellLayout中是如何表示上面的CellInfo的:

[java]  view plain copy
  1. public static final class CellInfo implements ContextMenu.ContextMenuInfo{  
  2.     public View view; //当前这个item对应的View  
  3.       
  4.     public int cellX; //该item水平方向上的起始单元格  
  5.     public int cellY;   //该item垂直方向上的起始单元格  
  6.     public int cellHSpan; //该item水平方向上占据的单元格数目  
  7.     public int cellVSpan; //该item垂直方向上占据的单元格数目  
  8.       
  9.     public boolean valid; //是否有效  
  10.       
  11.     public int screen; //所在的屏幕  
  12.       
  13.     Rect current = new Rect(); //用于递归寻找连续单元格,当前连续区域的大小  
  14.       
  15.     final ArrayList<VacantCell> vacantCells = new ArrayList<UorderCellLayout.CellInfo.VacantCell>();  
  16.       
  17.       
  18.     public void clear(){  
  19.         final ArrayList<VacantCell> list = vacantCells;  
  20.         final int count = list.size();  
  21.         for(int i=0; i<count; i++){  
  22.             list.get(i).release();  
  23.         }  
  24.           
  25.         list.clear();  
  26.     }  
  27.       
  28.     public String toString(){  
  29.           
  30.         return "cellinfo:[cellX="+cellX+",cellY="+cellY+",cellHSpan="+cellHSpan+",cellVSpan="+cellVSpan+"]";  
  31.     }  
  32.     /** 
  33.      *  
  34.      * VacantCell:代表空的cells,由多个cell组成,将其实现为一个cell池,减少对象的创建 
  35.      * 
  36.      */  
  37.     static final class VacantCell{  
  38.           
  39.         private static final int POOL_SIZE = 100//池最多缓存100个VacantCell  
  40.         private static final Object mLock = new Object(); //用作同步锁  
  41.           
  42.         private static VacantCell mRoot;  
  43.         private static int count;  
  44.           
  45.         private VacantCell mNext;  
  46.   
  47.         //VacantCell的大小信息  
  48.         private int cellX;  
  49.         private int cellY;  
  50.         private int cellHSpan;  
  51.         private int cellVSpan;  
  52.           
  53.         public static VacantCell acquire(){  
  54.             synchronized (mLock) {  
  55.                 if(mRoot == null){  
  56.                     return new VacantCell(); //一开始没有的时候,一直新创建再返回  
  57.                 }  
  58.                 //如果池存在,则从池中取  
  59.                 VacantCell info = mRoot;  
  60.                 mRoot = info.mNext;  
  61.                 count--; //记得将统计更新  
  62.                   
  63.                 return info;  
  64.             }  
  65.         }  
  66.           
  67.         //release这个对象自身  
  68.         public void release(){  
  69.             synchronized(mLock){  
  70.                 if(count < POOL_SIZE){  
  71.                     count++;  
  72.                     mNext = mRoot;  
  73.                     mRoot = this;  
  74.                 }  
  75.             }  
  76.         }  
  77.     }  
  78. }  


其用一个CellInfo保存当前位置上的View信息和其位置信息,但是注意到其还定义了一个VancantCell类,这个主代表某个空的“区域”,这个区域可能有多个单元格。同时,其实现为一个链表结构的单元格池,这样主要不用每次都来创建新对象,优化性能。

对CellLayout的大概结构有所了解后,我们就可以接着去寻找开始提到的三个问题的答案了。

一、如何标识当前位置上的信息

为了可以知道某个位置是空还是已经被占用了,CellLayout用一个二维布尔数组boolean[水平单元格数][竖直单元格数]来保存每个单元格的占用信息,被占用的为true,空的为false。

为了判断当前长按事件的位置是否在item上,可以在onInterceptTouchEvent方法中如下判断当前长按事件的位置是否在某个item的位置里。如下:

[java]  view plain copy
  1. final Rect frame = mRect;  
  2. final int x = (int)ev.getX();   
  3. final int y = (int)ev.getY();  
  4.   
  5. Log.v(TAG, "MotionEvent.getX,getY:[x,y]=["+x+","+y+"]");  
  6.   
  7. final int count = getChildCount();  
  8. boolean found = false;  
  9. Log.v(TAG, "CellLayout Child count:"+count);  
  10. for(int i=count-1; i>=0; i--){  
  11.     final View child = getChildAt(i);  
  12.       
  13.     if(child.getVisibility() == VISIBLE || child.getAnimation() != null){  
  14.           
  15.         child.getHitRect(frame); //获取child的尺寸信息,相对于CellLayout  
  16.           
  17.         Log.v(TAG, "View.getHitRect:"+frame.toString());  
  18.           
  19.         if(frame.bottom<=frame.top || frame.right<= frame.left){  
  20.             Log.v(TAG, "The rectangle of the view is incorrect");  
  21.             continue;  
  22.         }  
  23.           
  24.         if(frame.contains(x,y)){  
  25.             //如果当前事件正好落在该child上  
  26.             final LayoutParams lp = (LayoutParams)child.getLayoutParams();  
  27.             cellInfo.view = child;  
  28.             cellInfo.cellX = lp.cellX;  
  29.             cellInfo.cellY = lp.cellY;  
  30.             cellInfo.cellHSpan = lp.cellHSpan;  
  31.             cellInfo.cellVSpan = lp.cellVSpan;  
  32.             cellInfo.valid = true;  
  33.             found = true;  
  34.             Log.v(TAG, "YES,Found!");  
  35.             break;  
  36.         }  
  37.     }  
  38. }  


上面我们记录了如果落在某个item上,我们记录下当前的位置信息和view信息。那么如果当前长按的是一块空的区域呢?

[java]  view plain copy
  1. if(!found){  
  2.     /** 
  3.      * 如果点击的位置是空白区域,则也需要保存当前的位置信息 
  4.      * 点击空白区域的时候,是需要做更多的处理,在外层弹出对话框添加应用,文件夹,快捷方式等,然后在桌面该 
  5.      * 位置处创建图标 
  6.      */  
  7.     int cellXY[] = mCellXY;  
  8.     pointToCellExact(x,y,cellXY); //得到当前事件所在的单元格  
  9.     Log.v(TAG, "Not Found the cellXY is =["+cellXY[0]+","+cellXY[1]+"]");  
  10.     //然后保存当前位置信息  
  11.     cellInfo.view = null;  
  12.     cellInfo.cellX = cellXY[0];  
  13.     cellInfo.cellY = cellXY[1];  
  14.     cellInfo.cellHSpan = 1;  
  15.     cellInfo.cellVSpan = 1;  
  16.       
  17.     //这里需要计算哪些单元格被占用了  
  18.     final int xCount = mHCells; //TODO:没有考虑横竖屏的情况  
  19.     final int yCount = mVCells;  
  20.     final boolean[][] occupied = mOccupied;  
  21.     findOccupiedCells(xCount, yCount, occupied);  
  22.       
  23.     //判断当前位置是否有效,这里不用再判断cellXY是否越界,因为在pointToCellExact已经进行了处理  
  24.     cellInfo.valid = !occupied[cellXY[0]][cellXY[1]];  
  25.       
  26.     //这里其实我们需要以当前的cellInfo表示的单元格为中心,向四周递归开辟连续的最大空间  
  27.     //但是,这里还并不需要,只有当getTag()方法被调用的时候,才说明需要一块区域去放一个View  
  28.     //所以,将这个开辟的方法放在getTag()中调用  
  29.     //这里标记一下  
  30.     mTagFlag = true;  
  31. }  
  32.   
  33. //将位置信息保存在CellLayout的tag中  
  34. setTag(cellInfo);  


长按某个区域,我们记录下当前的位置信息,注意,我们是记录下当前事件所在的单元格,然后保存的是该单元格的信息,所以,上面调用了pointToCellExact这个方法来计算当前事件坐标落在哪个单元格内,并且调用了findOccupiedCells方法计算整个CellLayout上所有单元格的被占用情况。关于事件坐标到单元格的对应,计算并不困难,因为我们知道每个单元格的宽度和高度,同时知道当前的事件坐标,那么简单的除法就可以计算得到。

 

二、我们添加的item如何被添加到CellLayout上面

我们知道,要想绘制每个孩子自然在onLayout中调用每个孩子的layout方法,下面就看看这个方法的实现:

[java]  view plain copy
  1. protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  2.     int count = getChildCount();  
  3.     for(int i=0; i<count; i++){  
  4.         View child = getChildAt(i);  
  5.         if(child.getVisibility() != GONE){  
  6.             LayoutParams lp = (LayoutParams)child.getLayoutParams();  
  7.             child.layout(lp.x, lp.y, lp.x+lp.width, lp.y+lp.height);  
  8.         }  
  9.     }  
  10. }  

注意,在该方法中每个孩子的布局,是按照他们自身的LayoutParams对象中保存的信息来布局到具体的位置的。那么接下来,我们就要分析下CellLayout中每个孩子的LayoutParams的结构。CellLayout中有个自定义的LayoutParams类,该类保存了该孩子所在的单元格信息和其真实的坐标位置,其含有一个set方法,在这个方法中计算了孩子的width,height,起始坐标x和y。

[java]  view plain copy
  1. public void set(int cellWidth, int cellHeight, int hStartPadding, int vStartPadding, int widthGap, int heightGap){  
  2.     //计算item的宽和高  
  3.     //这里计算的时候,注意是width,height是需要排除掉margin的  
  4.     this.width = cellHSpan*cellWidth+(cellHSpan-1)*widthGap-leftMargin-rightMargin;  
  5.     this.height = cellVSpan*cellHeight + (cellVSpan-1)*heightGap - topMargin - bottomMargin;  
  6.       
  7.     Log.v(TAG, "The width and height of the view are:"+this.width+","+this.height);  
  8.     //同时计算item的真实坐标  
  9.     //除去item的margin和padding,view开始的位置  
  10.     this.x = cellX*(cellWidth+widthGap)+leftMargin+hStartPadding;  
  11.     this.y = cellY*(cellHeight+heightGap)+topMargin+vStartPadding;  
  12.       
  13.     Log.v(TAG, "The x and y of the view are:"+this.x+","+this.y);  
  14. }  


这个时候,也就知道了,每个孩子的宽度和高度,以及如何在布局的时候根据其所在单元格信息,转换为其真实坐标。我们知道,控件的布局需要经过两个阶段,一个是measure,接下来就是layout。measure主要完成控件的测绘工作,计算每个控件绘制需要的空间信息,所以,在onMeasure中,自然可以看到给每个孩子测量大小的时候,就同时为其调用了set方法。如下:

[java]  view plain copy
  1. int count = getChildCount();  
  2. Log.v(TAG, "onMeasure 开始。。。");  
  3. for(int i=0; i<count; i++){  
  4.     //对每个子控件进行测量了  
  5.     View child = getChildAt(i);  
  6.     LayoutParams lp = (LayoutParams)child.getLayoutParams();  
  7.     //这里需要将我们计算的结果封装进LayoutParams中,供CellLayout在布局子控件的时候使用  
  8.     //这里横竖屏需要不同对待  
  9.     //TODO:暂时不考虑  
  10.     lp.set(mCellWidth, mCellHeight, mHStartPadding, mVStartPadding, mHCellGap, mVCellGap);  
  11.       
  12.     //下面将获取子控件的宽度和高度,并用MeasureSpec编码  
  13.     int cWidth = lp.width;  
  14.     int cHeight = lp.height;  
  15.     int cWidthSpec = MeasureSpec.makeMeasureSpec(cWidth, MeasureSpec.EXACTLY);  
  16.     int cHeightSpec = MeasureSpec.makeMeasureSpec(cHeight, MeasureSpec.EXACTLY);  
  17.     child.measure(cWidthSpec, cHeightSpec);  
  18. }  


到这里,关于CellLayout上面孩子的绘制工作就介绍完毕了。但是还没有说到,我们长按桌面的时候,怎样将我们选择的item给添加到桌面上来。这个就得再回到Launcher的onLongClick方法中,我们看下:

[java]  view plain copy
  1. if(!(v instanceof UorderCellLayout)){  
  2.     v = (View)v.getParent(); //如果当前点击的是item,得到其父控件,即UorderCellLayout  
  3. }  
  4.   
  5. CellInfo cellInfo = (CellInfo)v.getTag(); //这里获取cellInfo信息  
  6.   
  7. if(cellInfo == null){  
  8.     Log.v(TAG, "CellInfo is null");  
  9.     return true;  
  10. }  
  11.   
  12. //Log.v(TAG, ""+cellInfo.toString());  
  13.   
  14. if(cellInfo.view == null){  
  15.     //说明是空白区域  
  16.     //ActivityUtils.alert(getApplication(), "空白区域");  
  17.     Log.v(TAG, "onLongClick,cellInfo.valid:"+cellInfo.valid);  
  18.     if(cellInfo.valid){  
  19.         //如果是有效的区域  
  20.         //ActivityUtils.alert(getApplication(), "有效区域");  
  21.         addCellInfo = cellInfo;  
  22.         showPasswordDialog(REQUEST_CODE_SETUP, null);     
  23.     }  
  24. }else{  
  25.     mWorkspace.startDrag(cellInfo);  
  26. }  
  27. return true;  


我们看到在onLongClick中有CellInfo cellInfo = (CellInfo)v.getTag(); 这样,我们就知道,在上面onInterceptTouchEvent方法中我们将cellInfo放入tag是为了在CellLayout的getTag方法中,返回cellInfo信息。有了cellInfo信息,我们就可以调用CellLayout.addView方法将我们所选择的控件添加到桌面了。在Workspace类中,调用addInScreen方法设置其单元格信息,然后直接调用CellLayout.addView方法添加到CellLayout。

[java]  view plain copy
  1. public void addInScreen(View child, int screen, int cellX, int cellY, int spanX, int spanY, boolean insertFirst){  
  2.     if(screen<0 || screen >= getChildCount()){  
  3.         throw new IllegalArgumentException("The screen must be >= 0 and <"+getChildCount());  
  4.     }  
  5.     final UorderCellLayout group = getCellLayout(screen);  
  6.     UorderCellLayout.LayoutParams lp = (UorderCellLayout.LayoutParams)child.getLayoutParams();  
  7.       
  8.     //初始化当前需要添加的View在CellLayout中的布局参数  
  9.     if(lp == null){  
  10.         lp = new UorderCellLayout.LayoutParams(cellX, cellY, spanX, spanY);  
  11.     }else{  
  12.         lp.cellX = cellX;  
  13.         lp.cellY = cellY;  
  14.         lp.cellHSpan = spanX;  
  15.         lp.cellVSpan = spanY;  
  16.     }  
  17.       
  18.     Log.v(TAG, "Before add view on the screen");  
  19.     group.addView(child, insertFirst?0:-1, lp);  
  20.       
  21.     child.setOnLongClickListener(mOnLongClickListener);  
  22.     //child的onClickListener在创建出View的时候设置的,在Uorderlauncher中  
  23.       
  24. }  


上面,我们看到直接调用了group.addView方法,但是之前我们仅仅设置了child的LayoutParams中单元格信息,至于怎么根据这些信息得到其真实坐标,怎么布局,上面已经介绍了。

至此,CellLayout的大致就介绍完了。但是CellLayout还不至于如此简单。当我们添加的控件不止一个单元格那么大的时候,如何分配其空间,如果空间不够怎么处理等问题都是CellLayout需要考虑的问题。但是,我想有了上面的理解,这个也不是什么难题了...

这篇关于说说Android桌面(Launcher应用)背后的故事(三)——CellLayout的秘密的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/876744

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

zoj3820(树的直径的应用)

题意:在一颗树上找两个点,使得所有点到选择与其更近的一个点的距离的最大值最小。 思路:如果是选择一个点的话,那么点就是直径的中点。现在考虑两个点的情况,先求树的直径,再把直径最中间的边去掉,再求剩下的两个子树中直径的中点。 代码如下: #include <stdio.h>#include <string.h>#include <algorithm>#include <map>#

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

【区块链 + 人才服务】可信教育区块链治理系统 | FISCO BCOS应用案例

伴随着区块链技术的不断完善,其在教育信息化中的应用也在持续发展。利用区块链数据共识、不可篡改的特性, 将与教育相关的数据要素在区块链上进行存证确权,在确保数据可信的前提下,促进教育的公平、透明、开放,为教育教学质量提升赋能,实现教育数据的安全共享、高等教育体系的智慧治理。 可信教育区块链治理系统的顶层治理架构由教育部、高校、企业、学生等多方角色共同参与建设、维护,支撑教育资源共享、教学质量评估、

AI行业应用(不定期更新)

ChatPDF 可以让你上传一个 PDF 文件,然后针对这个 PDF 进行小结和提问。你可以把各种各样你要研究的分析报告交给它,快速获取到想要知道的信息。https://www.chatpdf.com/

【区块链 + 人才服务】区块链集成开发平台 | FISCO BCOS应用案例

随着区块链技术的快速发展,越来越多的企业开始将其应用于实际业务中。然而,区块链技术的专业性使得其集成开发成为一项挑战。针对此,广东中创智慧科技有限公司基于国产开源联盟链 FISCO BCOS 推出了区块链集成开发平台。该平台基于区块链技术,提供一套全面的区块链开发工具和开发环境,支持开发者快速开发和部署区块链应用。此外,该平台还可以提供一套全面的区块链开发教程和文档,帮助开发者快速上手区块链开发。