Tell me the story behind the android desktop (launcher application)-Give widgets the same benefits as applications

Source: Internet
Author: User

Blog migration-I have migrated my blog to www.ijavaboy.com to better manage it. We are sorry for the inconvenience caused by no updates! New address of this article: Click me

 

The previous article demonstrates how to develop a widget and how to develop a widgethost application. With this foundation, we know that to add widgets to the desktop, we need to do two things:

1. Implement a desktop application as a widgethost Application

2. You need to allocate sufficient space for each added widget in celllayout.

However, to add widgets to the desktop, the following functions should be available:

3. widgets can be dragged

4. widgets can slide between multiple screens

5. widgets can be removed from the desktop

As mentioned in the previous article, we can solve the first problem by adding several pieces of code to the application. The key issue is the second one. Previously we were faced with application and shortcut with the smallest size of only one cell. However, the most obvious feature of widgets is the differences in size. The third problem is that the drag function is handed over to the draglayer, which shields the application or widget of the dragged item on the desktop, it only knows that the item to be dragged is an iteminfo (ancestor of all items ). Therefore, this can be achieved without adding any code. The fourth problem is of the same nature as the third problem. The fifth problem does not involve any code. It is the responsibility of draglayer and deletezone, and does not involve the differences between widgets and applications.

Therefore, the core of the next step is to solve the second problem: celllayout needs to allocate sufficient space for each added widget.

Before solving this problem, let's take a look at the process when adding widgets.

In this process, we can see that when adding a widget to the desktop, we will first focus on the coordinates of the current user's long-pressed coordinates and keep searching for continuous spaces of various sizes and recording them, if yes, you can select the most appropriate one and add it. If not, search for the space that can be accommodated in the entire celllayout. If not, select the most suitable one. If no space is found, the system prompts the user that the screen does not have enough space to accommodate.

With the above analysis, the following describes how launcher completes each step.

When the user presses the desktop for a long time, the motion_down event is passed to celllayout. In the onintercepttouchevent method of celllayout, the user determines whether the current long press is in a blank area. If yes, convert the current coordinate to the current cell, and then recalculate the usage of all cells in the entire celllayout. The Code is as follows:

 

If (! Found) {/*** if you click a blank area, you also need to save the current location information * When you click a blank area, you need to do more processing, in the outer pop-up dialog box, add applications, folders, shortcuts, etc. Then, create the icon */INT cellxy [] = mcellxy; pointtocellexact (X, Y, cellxy) at the * position on the desktop ); // obtain the log of the cell where the current event is located. V (TAG, "not found the cellxy is = [" + cellxy [0] + "," + cellxy [1] + "]"); // Save the current location information cellinfo. view = NULL; cellinfo. cellx = cellxy [0]; cellinfo. celly = cellxy [1]; cellinfo. cellhspan = 1; cellinfo. cellvspan = 1; // here you need to calculate which cells are occupied by final int x Count = mportrait? Msortcells: mlongcells; Final int ycount = mportrait? Mlongcells: msortcells; Final Boolean [] [] occupied = moccupied; findoccupiedcells (xcount, ycount, occupied); // determine whether the current position is valid. You do not need to determine whether cellxy is out of bounds, because pointtocellexact has processed cellinfo. valid =! Occupied [cellxy [0] [cellxy [1]; // In fact, we need to center the cells represented by cellinfo, opening up continuous maximum space for recursion around the world // However, this is not required here, only when the gettag () method is called, it indicates that you need to put a view in a region. Therefore, put the opened method in gettag () and call // to mark mtagflag = true ;} // save location information in the tag of celllayout settag (cellinfo );

Note that the usage of cells in celllayout is calculated here. At the same time, the cells where the current event is located are not at this time, and the available space is explored for the surrounding area, instead, the process is postponed to the gettag method. Because we do not need this value in onintercepttouchevent. After onintercepttouchevent is returned, the event enters the onlongclick of launcher. In this method, we call the gettag method of celllayout:

@ Overridepublic Boolean onlongclick (view v) {// activityutils. Alert (getapplication (), "Long press"); If (! (V instanceof uordercelllayout) {v = (View) v. getparent (); // if the item is currently clicked, obtain its parent control, that is, uordercelllayout} cellinfo = (cellinfo) v. gettag (); // obtain the cellinfo information here. If (cellinfo = NULL) {log. V (TAG, "cellinfo is null"); Return true;} // log. V (TAG, "" + cellinfo. tostring ();/*** Note: When we obtain information about the current position in celllayout, we determine whether the current position is an item. If yes, save * item in cellinfo. view */If (cellinfo. view = NULL) {// It indicates a blank area log. V (TAG, "onlongclick, cellinfo. valid: "+ cellinfo. valid); If (cellinfo. valid) {// if the region is valid, addcellinfo = cellinfo; showpassworddialog (request_code_setup, null);} else {// process the drag-and-drop mworkspace. startdrag (cellinfo);} return true ;}

 

Public cellinfo gettag () {cellinfo info = (cellinfo) super. gettag (); If (info. valid & mtagflag) {// a continuous area as large as possible is expanded to the surrounding area. // which cells are counted as occupied by final int xcount = mportrait? Msortcells: mlongcells; Final int ycount = mportrait? Mlongcells: msortcells; Final Boolean [] [] occupied = moccupied; findoccupiedcells (xcount, ycount, occupied); log. E (TAG, "gettag --: xcount:" + xcount + "ycount:" + ycount + ", occupied:" + occupied. length); // The findvacantcells (Info, info. cellx, info. celly, xcount, ycount, occupied);} return Info ;}

 

In the gettag method, we call the findvacantcells method. This method is centered on the cells specified by the current cellx and celly, and continues to the left, right, top, bottom, explore available continuous space in four directions. The Code is as follows:

Public void findvacantcells (cellinfo info, int xcenter, int ycenter, int xcount, int ycount, Boolean [] [] occupied) {// you must first release vacantcellinfo maintained in info. clear (); If (occupied [xcenter] [ycenter]) {// if the center cell is occupied, return directly;} info. current. set (xcenter, ycenter, xcenter, ycenter); // recursive with current as the center // findvacantcellsrecursive (Info, info. current, xcount, ycount, occupied); findvacantcellsnonrecursive (Info, info. current, xcount, ycount, occupied );}

 

This method mainly completes the preparations before the exploration. findvacantcellsnonrecursive and findvacantcellsrecursive are actually completed. These two methods perform the same function. The latter is implemented by recursion in the system; the former is a non-recursive method implemented by myself; because the recursion method works well for the 4*4 used in the system launcher, however, for large screens such as tablets, celllayout may be 8x8, resulting in poor performance and memory overflow. Therefore, it is replaced by a non-recursive method. Let's first look at the recursive method:

public void findVacantCellsRecursive(CellInfo info, Rect current,int xCount, int yCount, boolean[][] occupied) {addVacantCell(current, info);        if (current.left > 0) {            if (isColumnEmpty(current.left - 1, current.top, current.bottom, occupied)) {                current.left--;                findVacantCellsRecursive(info, current, xCount, yCount, occupied);                current.left++;            }        }        if (current.right < xCount - 1) {            if (isColumnEmpty(current.right + 1, current.top, current.bottom, occupied)) {                current.right++;                findVacantCellsRecursive(info, current, xCount, yCount, occupied);                current.right--;            }        }        if (current.top > 0) {            if (isRowEmpty(current.top - 1, current.left, current.right, occupied)) {                current.top--;                findVacantCellsRecursive(info, current, xCount, yCount, occupied);                current.top++;            }        }        if (current.bottom < yCount - 1) {            if (isRowEmpty(current.bottom + 1, current.left, current.right, occupied)) {                current.bottom++;                findVacantCellsRecursive(info, current, xCount, yCount, occupied);                current.bottom--;            }        }}

 

Let's take a look at the non-recursive method. My algorithm is not refined, so I have achieved non-recursion. It is for reference only:

Public void Merge (cellinfo, rect current, int xcount, int ycount, Boolean [] [] occupied) {arraylist <rect> rectstack = new arraylist <rect> (); rectstack. add (current); int centerleft = current. left; // int centerright = current. right; addvacantcell (current, cellinfo);/*** it needs to be added to the stack when it is scaled to the left to the right * // extended to the left, while (isleftempty (current, occupied )) {current. left --; rectstack. add (0, new rect (current); // stack addvacantcell (current, cellinfo);} int realleft = current. left; // extend current to the right. left = centerleft; // restore while (isrightempty (current, occupied, xcount) {current. right ++; rectstack. add (0, new rect (current); addvacantcell (current, cellinfo); // At the same time, the extended left must be included in for (INT left = centerleft; left> = realleft; left --) {rect = new rect (left, current. top, current. right, current. bottom); rectstack. add (0, rect); addvacantcell (rect, cellinfo) ;}} int centertop = current. top;/*** the stack needs to be output when it is scaled up or down */while (rectstack. size ()> 0) {// current of the output stack. set (rectstack. remove (0); // up while (istopempty (current, occupied) {current. top --; addvacantcell (current, cellinfo);} int realtop = current. top; // down, current. top = centertop; while (isbottomempty (current, occupied, ycount) {current. bottom ++; addvacantcell (current, cellinfo); For (INT Top = centertop; top> = realtop; top --) {rect = new rect (current. left, top, current. right, current. bottom); addvacantcell (rect, cellinfo );}}}}

 

Both methods are called.Addvacantcell(Current, cellinfo); this method is to record the continuous region of the current exploration and save it to cellinfo. The Code is as follows:

 

    private static void addVacantCell(Rect current, CellInfo cellInfo) {        CellInfo.VacantCell cell = CellInfo.VacantCell.acquire();        cell.cellX = current.left;        cell.cellY = current.top;        cell.cellHSpan = current.right - current.left + 1;        cell.cellVSpan = current.bottom - current.top + 1;                cellInfo.vacantCells.add(cell);    }

 

You may see that the record is still in the fog, but you do not know what continuous areas are recorded. Let's explain a simple recursive process of exploration:

For example, on the desktop, if there are four cells left in the blank position in the middle, I will press the 0 position marked in yellow. After the event coordinates are converted to cells, we know that the current position of the widget to be added is in cell 0. How does one explore the space from cell 0 to the surrounding area? And what information does it save?

1. Record the current cell and cellinfo Save the information of cell 0

2. First, go to the left-> check that cell 3 is empty and is not occupied-> Save the current continuous area. Note, the current continuous area is composed of cells 0 and 3 --> continue to the left --> the left cell is occupied by MySpace, and end exploration to the left

3. Right Exploration --> the right side is occupied by folder and the right side of exploration is stopped.

4. On the basis of the current continuous area (), explore up --> judge that cells 1 and 2 are empty, and add them to cellinfo. At this time, the continuous space becomes [,]; --> continue to explore up --> occupied, stop exploring up

5. On the basis of the current continuous region [,], explore downward, be occupied, stop downward exploration, end with a recursion, and return the previous recursion.

The continuous regions recorded in cellinfo are: [0], [], [,], []. All the continuous areas must contain the cell where the current event is located, that is, the center cell of the exploration. That is, the available continuous space in various sizes centered on cells 0 is [0], [], [,], []

So that we can save the available space of various sizes in cellinfo, so that we can easily determine whether our widgets have a foothold. After we get the tag in the onlongclick of launcher, the Add dialog box is displayed. The information is the same as that of the add application, which is included here. How to call the built-in widgets application of the system is described in the previous article.

After selecting the widget to be added, what should we do? According to the sequence diagram above, we can see that

1. Obtain the size of the selected widget.

2. Convert the size information to the area size of the cells to be occupied.

3. Compare the current widget size with the various sizes saved in cellinfo to find the most appropriate

4. if not found, search for all contiguous spaces in the entire celllayout.

5. Compare the current widget size with the continuous space of all the sizes just obtained to find the most appropriate one.

6. If not found, the system prompts that there is not enough space available.

In launcher, obtain the widget size information, determine, and add the widget to the desktop as follows:

/*** Create a widget and add it to the desktop * @ Param data */private void createwidget (intent data) {int widgetid = data. getintextra (appwidgetmanager. extra_appwidget_id,-1); appwidgetproviderinfo appwidget = mwidgetmananger. getappwidgetinfo (widgetid); // calculates the information of the cell occupied by the current widget. uordercelllayout group = (uordercelllayout) mworkspace. getchildat (addcellinfo. screen); int [] spans = group. computeoccupiedspans (appwidget. minwidth, Appwidget. minheight); log. E (TAG, "the current widget occupies the cell:" + spans [0] + "," + spans [1]); // In current celllayout, You need to determine whether the size of the region can be satisfied, that is, whether there is sufficient space to accommodate int [] startcells = new int [2]; If (! Hasenoughspace (addcellinfo, spans [0], spans [1], startcells) {activityutils. alert (getapplication (), "This desktop does not have enough space"); return; // if there is enough space, startcells saves the Start Cell} log of the current region. E (TAG, "add widget --> startcells:" + startcells [0] + "," + startcells [1]); // save to database widgetinfo widget = new widgetinfo (widgetid); widget. screen = mworkspace. getcurrentscreen (); widget. cellx = startcells [0]; widget. celly = startcells [1]; widget. spanx = spans [0]; widget. spany = spans [1]; uorderdbutils. saveitemindb (this, widget); // create hostviewwidget according to appwidgetproviderinfo. hostview = mwidgethost. createview (this, widgetid, appwidget); widget. hostview. setappwidget (widgetid, appwidget); widget. hostview. settag (widget); // Add it to the desktop mworkspace. addinscreen (widget. hostview, mworkspace. getcurrentscreen (), startcells [0], startcells [1], spans [0], spans [1], false );}

Hasenoughspace is used to compare whether the current widget size can be placed on the desktop:

 

Private Boolean hasenoughspace (cellinfo info, int spanx, int spany, int [] startcells) {/*** because the gettag method of celllayout is called during long-pressed * findvacantcells called in this method fills vacantcell of cellinfo. Therefore, here, you can * directly determine whether to extend the current long-pressed position to the surrounding area to see if a proper position can be found * If yes, the returned result is true * if not, then, find the available region from the entire celllayout */info. print (); If (! Info. findcellforspan (startcells, spanx, spany) {// you cannot find a uordercelllayout group = mworkspace from celllayout. getcurrentcelllayout (); // call this method to fill in vacantcellsinfo = group of info. findallvacantcells (); If (! Info. findcellforspan (startcells, spanx, spany) {// if no area is large enough on the screen, falsereturn false; }} return true ;}

First, search for cells of various sizes that cellinfo has saved. Note: In the gettag method, we are looking for the cells where the current event is located. Therefore, we call cellinfo for the first time. in findcellforspan, the content stored in cellinfo is centered on the location where the current event occurred. If no suitable information is found, it will be searched in the entire celllayout.

Public Boolean findcellforspan (INT [] cellxy, int spanx, int spany) {// before calling this method, make sure that vacantcells has a value if (this. vacantcells = NULL | this. vacantcells. size () = 0) return false; // @ 1: if the current size of cellinfo is saved, it should be the span of a cell, then the IF (this. cellhspan> = spanx & this. cellvspan> = spany) {cellxy [0] = This. cellx; cellxy [1] = This. celly; return true;} final list <vacantcell> cells = This. vacantcells; Final int COUNT = cells. size (); // @ 2: First, find the appropriate area for (INT I = 0; I <count; I ++) {vacantcell cell = cells. get (I); If (cell. cellhspan = spanx & cell. cellvspan = spany) {cellxy [0] = cell. cellx; cellxy [1] = cell. celly; this. clear (); // after the region is successfully found, clear the vacantcells of the current cellinfo and return true;} // @ 3: A large for (INT I = 0; I <count; I ++) {vacantcell cell = cells is found. get (I); If (cell. cellhspan> = spanx & cell. cellvspan> = spany) {cellxy [0] = cell. cellx; cellxy [1] = cell. celly; this. clear (); // after the region is successfully found, clear the vacantcells of the current cellinfo; return true ;}} return false ;}

 

After all this is done, widgets can be perfectly added to the desktop. In addition to some details, this is the general process of adding widgets.

However, after adding an implementation, when you try to drag and drop widgets by long-pressing them, you will find that some widgets can be dragged, while others cannot. This is mainly because widgets that do not have a focus can be dragged. However, for widgets with focus, such as system clock widgets, as soon as you press it, you will enter the clock setting interface. In this case, we need to overwrite the appwidgethostview control to process the widget's long press. At the same time, rewrite appwidgethost so that the created appwidgethostview is customized, not the system. These two classes are relatively simple. No code is pasted here. You can refer to the launcher'sLauncherappwidgethostview and launcherappwidgethost.

At this point, our desktop can perfectly accommodate all kinds of small parts. At the same time, we also converted the recursive processing method of the system launcher, using non-recursive methods, this prevents performance problems when you need to adjust the number of cells in celllayout.

 

After that:

Our launcher is almost the same as the system's launcher function. But there are still a lot of content and details that we haven't considered yet. These are well handled in the system launcher. For example, the launcher is stable when the status is saved at some time and an exception occurs. However, these are all completed through continuous debugging and improvement. Although the desktop seems simple, launcher is a complicated application and the perfect implementation requires excellent talents and time. This series of analysis articles only simulates the development process from nothing to launcher. If you really need the launcher application, you can make some modifications and extensions on the basis of the system launcher, there is no need to start from scratch completely. However, the premise of modification and expansion is to have a comprehensive and profound research on launcher.

So far, this series of analysis is over. New Year's Day is coming soon. I hope the new year will show me a dream...

 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.