Wednesday, April 4, 2012

Android: screen densities and sizes. How to calculate them

For me it has always be complex to find out, for each android device, what is its density and its screen size. I'll try to explain in this article what i've discovered so far.
It is really very important to find out these 2 parameters, since Android has a way to distinguish which resources to use (both xml layout files and drawables) based on such parameters.

In detail, Android allows you to specify your drawable resources based on the screen density of your phone. The 4 screen densities available are:


  • ldpi - 120 dpi
  • mdpi - 160 dpi
  • hdpi - 240dpi
  • xhdpi - 320 dpi
Each specifies a different screen density, i.e. a different amount of pixels for square inch of screen. These are called dpi or dots per inch.

For example, a HTC Desire has a density of 240dpi, i.e there are 240 pixels for each square inch of screen.
A LG Optimus One has a density of 160dpi, i.e. there are 160 pixels for each square inch of screen. Obviously, if we provide the same icon on these devices, it will show itself larger or the LG than on the HTC phone. A way to cope with that is to specify a layout that uses density independent pixels. But that's another story.

Let's focus on the dpi. How can we calculate this value? Let's take the two parameters that we always know of a device: diagonal in inches and screen size in pixels.
The HTC Desire has: 800 x 480 and 3.7''.
dpi = sqrt(w^2 + h^2)/d = sqrt(800^2 + 480^2)/3.7 = 252,1

So the screen density of HTC Desire is HDPI.

the display size is important because usually we associate different layouts to different screen sizes. A device can have 4 screen sizes:

  • small-screen
  • normal-screen
  • large-screen
  • xlarge-screen
On the Android dev website, they tell us that a medium screen is more or less between 3 and 5''. But is there a scientific way to calculate it?
They also tell us that:


  • small-screens must have a screen size of 426 x 320 dp = 136320 dp
  • normal-screens must have a screen size of 470 x 320 dp = 150400 dp
  • large-screens must have a screen size of 640 x 480 dp = 326400 dp
  • xlarge-screens must have a screen size of 960 x 720 dp = 691200 dp
Usually phones are in the small and normal category, while tablets are in the large and xlarge category, although there are some important exceptions to this rule.



How to calculate the display size for a device? Let's take the HTC Desire as example.
The HTC Desire has: 800 x 480 and 3.7'' and is a HDPI device with a scale of 1,57, so each virtual pixel counts as 1.57 physical pixels. This is known as scale and can be determined as: 252/160. A device with 160 dpi has a scale of 1 (1 physical pixel counts a s1 virtual pixel). This was the density of the first Android phone.

So: 800/1,57 = 510 dp; 480/1,57 = 305 dp
520 * 305 = 155550 dp which is higher than 150400 but lower than 326400. So the HTC Desire has  a normal screen.

A particular case is give by the recent Galaxy Note. This device has: 1280x800 5.3'' display.
So the DPI is 284,8. Its scale ratio is 1.77. It is between 1.5 and 2. Aaagh.. So in what screen density folder should we put the drawables? A search on stackoverflow suggest that the Note si considered as XHDPI screen.
But here's the problem. We said that phones have a small or medium size screen. This is WRONG for the Galaxy Note.
Infact: 1280/1,77 = 724 dp;  800/1,77 = 451
724 * 451 = 326524 which is larger than 326400

So if you want to filter your app based on the screen size, be careful. Some phones, like the Galaxy Note, belongs to the Large screens device family!



Monday, March 12, 2012

Tables and cell selections: iOS UITableView vs Android ListView



One of the first things every dev learns when he/she starts developing on a mobile platform is how to present sets of data. They could be a list of shops, an array of products and so on.
Both Android and iOS have a component to manage this kind of data: ListView for the former, and UITableView for the latter.

These classes surprisingly offer a similar interface, and have similar ways to provide the datasource to it.

Both platforms have a component to show the elements on screen and a datasource (or adapter) that manages the presentation of the tableview cells, the cells reuse and how data are presented (in which order).

iOS has a protocol, UITableViewDataSource, that every class that wants to provide some data to a UITableView must implement. The key method used to provide the tableView cells is

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

On the other side, Android datatsource is a class that extends ListAdapter.
Also this class has a method that must be always implemented:

public View getView (int position, View convertView, ViewGroup parent) 


In both platforms it is very important to reuse the cells: with iOS 5 the cell recycle is managed by the system, in case the cell is declared in the .xib file as the default subview of the UITableView. It is very important though to specify a cell-id in code that matches the one declared in the .xib file.


Android use a system quite similar to that used by iOS 4.x and lower. You fetch a view inflating it from an xml file only if the view provided by the framework is null (i.e. there isn't currently a reusable cell).


This is a typical iOS 5.0 implementation:



- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CellId"];
    cell.textLabel.text = [_list objectAtIndex:indexPath.row];
    cell.selectionStyle = UITableViewCellSelectionStyleGray;
   
    return cell;
}


While this one is an Android implementation of getView():



public View getView (int position, View convertView, ViewGroup parent){
    LinearLayout layout;
    if(convertView == null){
    LayoutInflater inflater = (LayoutInflater)context.getSystemService(LAYOUT_INFLATER_SERVICE);
    layout = (LinearLayout)inflater.inflate(R.layout.cellnull);
    }
    else{
    layout = (LinearLayout)convertView;
    }
    
   ((TextView)layout.findViewById(R.id.cell_title)).setText(dataSource.get(position));
    
    return layout;
    }

The highlithed row shows the comparision made to establish if a convertView is available. If not, it is inflated from the XML layout file.

Another important issue to face is how to customize the list selector.
In iOS it is a very straightforward procedure: just specify the selection style, or if you want a different color, create a background view with the color you want as its background and assign it to the selectedBackgroundView property (see: http://stackoverflow.com/questions/1998775/uitableview-cell-selected-color).

On Android, a premise is necessary. Every component (button, tetview, and also cell views) has different states that define how the rendering engine should show them on screen. The states are: pressed, focused, selected, enabled.
Concerning the ListView component, the system provides a way to select cells that is specified by a property called listSelector. The listSelector is an xml file with a set of rules, similar to the rules you specify on a css web page, that define how an item is shown in a set of possible states.
I've found very difficult to customize this behavior. Every time I tried to customize the cell selection appeareance, I came across many different issues: some times the selection changed the appeareance of all the tableview items, some other times the cell divider disappeared from screen.

In the official samples app there isn't a single example on how to customize such property, and also watching the World of ListView Google IO Session didn't helped me a lot (see link here : http://www.youtube.com/watch?v=wDBM6wVEO70 )

So I came up with a simple yet effective solution: get rid of the listSelector property by imposing a transparent view for every possible state, and by modifying the background of the cell items instead.

This is the selector I've used:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<!--    <item android:drawable="@color/test_color" android:state_pressed="true" android:state_selected="true"></item> -->
<!--     <item android:drawable="@color/test_color" android:state_pressed="false" android:state_selected="true"></item> -->
    <item android:drawable="@color/test_color" android:state_pressed="true" android:state_selected="false"></item>
    <item android:drawable="@color/black" android:state_pressed="false" android:state_selected="false"></item>
</selector> 

You can also uncomment the first two rows if you want to manage the selected state (it is useful if your device has a D-Pad).









Sunday, March 4, 2012

Eclipse: Java compiler compliance

If you want to avoid build errors on the @Override annotation, you must change the Java compiler compliance.

The steps to do so:

Preferences -> Java -> Compiler -> JDK Compliance

and set it to 1.6

Eclipse and Android: projects build order

When you work on a project it may happen that at a certain point you include other projects in your workspace and you want to include them as dependencies.

The build order is very important and you have to specify it correctly otherwise the Eclipse IDE will generate compile errors almost every time the project is re-built (i.e every time you open Eclipse or you get the latest version from a repo).

To customize the build order in Eclipse:

General -> Workspace -> Build Order

Saturday, February 25, 2012

NFC, Android, accelerometer and Node.JS can transform your mobile in a brush!

Last saturday I went to Bologna with two colleagues (@robb_casanova as frontend artisan/dev and @emme_giii as mobile UX guru), and together we participated in a contest called HackReality. http://www.whymca.org/evento/whymca-hack-reality-bologna-04-02-2012

Our intent was to mix up some native and web based technologies to transform a mobile phone into a brush, and a wall into a virtual canvas where a user, with its Android phone, could draw multi colored traits.

We used some NFC tags sticked on a paper-made palette:



We used my Galaxy Nexus as a "brush": by tapping the phone on a NFC tag on the palette we changed the paint color. Then we used the accelerometer data to detect accelerations on X and Y axis.
Data were sent to a server running socket.io on top of Node.js. We finally implemented a canvas and we used Processing language to perform the actual drawing.



The source code for the client-side part of project is available at github: https://github.com/nalitzis/hackday_client

If I'll have time, in next posts I will explain how the NFC part works.

Wednesday, December 28, 2011

Debug with Galaxy Nexus on Windows7

In case your adb doesn't show your new shiny Galaxy Nexus, it is probably because your OS doesn't have the required USB driver already installed.
On MacOSX everything went fine from the beginning, but on Windows 7 I couldn't manage to make it appear under the devices window (in DDMS perspective).

Then I found this thread on stackoverflow:

http://stackoverflow.com/questions/8371787/eclipse-doesnt-show-my-galaxy-nexus-with-android-4

Just install the 26MB driver (eek!) and everything should work fine.

Sunday, December 25, 2011

How to use Bitbucket to remotely control your git projects


Bitbucket is an interesting service that allows to remotely store git and mercurial projects. It is reliable, easy to configure and free. Most important, you can have as many private repositories as you want.

In this post I'll explain how to commit and init a remote git repository on bitbucket starting from a local git repository. This is what I usually do in my projects: I start them locally, then commit locally via git and finally backup a remote copy via bitbucket.

These are the steps necessary to do that:
  • create a remote empty git repository on your account at bitbucket.org
  • if you haven't already a local git repo, initialize your project locally by typing git init 
  • copy your standard .gitignore file in the project root directory
  • commit locally (using the commit command or with a GUI tool like gitx, gitc, or gitk)
At this point you're ready to commit remotely. First of all your local git environment must be setup to point to the remote repository:

git remote add origin https://<username>@bitbucket.org/<username>/sampleproject.git


Here I assume that your project is named sampleproject; you must substitute the <username> key with your bitbucket account name.


If everything goes fine, you're ready to do your first push on your bitbucket repo:


git push -u origin master




To fetch the remote project from another machine just type:


git clone https://<username>@bitbucket.org/<username>/sampleproject.git