Category Archives: mobile

Set App Scaling in PhoneGap, Android

10 Feb 2012

One of the very first things any Android PhoneGap developer will run into is setting the appview scale to set the proper view size and prevent “zooming” within your app. The “zoom” could happen if the user taps a text field or pinches the screen. We want the scale set appropriately and zooming disabled.

Open up your main activity.java (located in your “src” directory) and compare it with what I have below:

package com.rickluna.myapp;

import com.phonegap.*;
import android.os.Bundle;
import android.view.WindowManager;
import android.webkit.WebSettings;

public class myAppActivity extends DroidGap {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.loadUrl("file:///android_asset/www/index.html");

        /* lock scaling in android 2.2 code to be pasted right here */

    }
}

The fix for this is easy and tested on Android 2.2, Android 2.3.3, and Android 3.1:

...
        /* lock scaling in android 2.2 */
        appView.getSettings().setSupportZoom(false);
        appView.getSettings().setUseWideViewPort(false);
        WebSettings ws = appView.getSettings();
        ws.setDefaultZoom(WebSettings.ZoomDensity.MEDIUM);
        appView.getSettings().setDefaultZoom(WebSettings.ZoomDensity.MEDIUM);
        appView.setInitialScale(0);
        ws.setSupportZoom(false);
        ws.setBuiltInZoomControls(false);
        ws.setUseWideViewPort(false);
...

The thing that you may find good to know is that you can specify the zoom density to whatever you like. I find that MEDIUM and initialScale of “0” gives me something that looks great across Android phones and looks consistent with what happens on iPhones. That being said I have used FAR on one occasion with initialScale set to “100”.

According to the android docs FAR should have an initial scale of “100” and MEDIUM should be “150”, however, according to my testing, you want an initialScale of 0 for MEDIUM. The reason is that in Android 2.3.6 (which many current phones are running such as the popular Samsung Galaxy S2)  the webview zooms in when tapping a text field. To prevent that and get consistent behavior across the range of android versions I have to set the intialScale to “0”, so the final setting is initialscale 0 and ZoomDensity MEDIUM.

 

 

Ext.Picker Slots with A Single Value: showing a Decimal Point

08 Feb 2012

A case has come up where I need a picker with multiple slots that represent a number to two decimal places. A decimal point will be necessary to delineate between whole numbers and the decimals. To achieve this I will need to have a 5 slot Ext.Picker where the middle slot is a decimal point, as shown in the image below:

Sencha Picker with 5 slots, the center having a single value.

At first glance this might seem to be fairly easy. As slots are simple to define we then assume that the center slot just needs to be a single value, right? Actually, that’s not quite true. A few things make this not so clear-cut, among them is that a slot cannot be disabled. There *is* an option to set “draggable” to false but it doesn’t work (Sencha 1.1.1). As a result the slot will always be subject to being dragged by the user. Also an Ext.Picker with a slot consisting of only a single value will actualy throw errors if the slot is dragged up or down – trial and error reveals that a slot must consist of at least two values.

So then, we will create our center slot with two values. Also, I think its pretty tedious to manualy create all of the values for the other slots, so I wrote a function that builds an array of configuration objects that I’ll use to populate the slot data. Here’s what we have at this point:

function returnNumbers(){
	var num = [];
	for (i=0;i<10;i++){
		num[num.length] = {text:i,value:i};
	}
	return num;
}

var taxPicker = new Ext.Picker({
	slots: [
		{
			name : 'whole1',
			title: 'col 1',
			data : returnNumbers()
		},
		{
			name : 'whole2',
			title: 'col 2',
			data : returnNumbers()
		},
		{
			name : 'dot',
			title: 'decimal',
			draggable:false, // doesn't work!!
			data : [{text:'.',value:'.'},{text: '', value: ''}]
		},
		{
			name : 'decimal1',
			title: 'col 3',
			data :  returnNumbers()
		},
		{
			name : 'decimal2',
			title: 'col 4',
			data :  returnNumbers()
		}
	]
});

As you can see the first two slots are a range of numbers, 0 through 9. The center slot is the decimal and an empty string, and then the last two slots like the first pair are also setup to display 0 thru 9.

When the picker is shown via the show() method it appears the way we want - the decimal point is the first selection. Recall though that we have two values for the center slot, and that the user can play with the slot as they wish. If the decimal is dragged down Sencha handily animates it back into position. However if the decimal is dragged up - we get the empty-string as the selection for the slot. Note that we actual don't care what the selection of the center slot is - I don't use it - but visually this looks wrong so it needs to be addressed. If we can't prevent the user from dragging the decimal then we should at least find a way to put it back where we want it.

They key here is that Slots fire "pick" events when they are changed. So that's where we'll start; by adding a listener to the picker. Next, we need to set its value and there is a method that does just that - setSelectedNode(). Lets create the listener:

	...
	listeners:{
		pick:function(pickerO,o,pickerS){
			pickerS.setSelectedNode(0,null);
		}
	}
	...

This is great except for one thing - all of the slots are being animated back to their initial values. We need a way of being more selectful in our approach as this code doesn't discriminate and attacks all of the slots. The answer here is to look at the value of the slot responsible for the event call and do a comparison against it to see if its the slot we're looking for.

The slot object is being passed as "pickerS", so lets grab that and compare the name given to the slot against what we know is the name of the decimal slot (refer to the picker code above - all of the slots have a "name"):

	...
	listeners:{
		pick:function(pickerO,o,pickerS){
			if (pickerS.name == 'dot'){ // send back to the decimal...
				pickerS.setSelectedNode(0,null);
			}
		}
	}
	...

On line 4 above you can see that I have an if statement comparing the passed slot's name to "dot" which is the name I gave to the decimal-having slot. This ensures that we're working with the correct slot when the pick event is fired.

Sweet, that should do it - however, of course there is one last detail... there always is! The above behaves unpredictably. Sometimes the decimal slides back to being selected, sometimes not. In these cases I always wonder if too much is happening at the same time. A simple setTimeout should make all the difference and it does.

        setTimeout(function(){pickerS.setSelectedNode(0,null);},20);

As you can see I've delayed the setSelectedNode() call by 20 milliseconds. Thats all that was needed - if the user drags the center slot to the second "empty" slot we return it to the desired decimal-having slot 20 ms later. Its the best that we can do. Here's the complete source:

function returnNumbers(){
	var num = [];
	for (i=0;i<10;i++){
		num[num.length] = {text:i,value:i};
	}
	return num;
}

var taxPicker = new Ext.Picker({
	slots: [
		{
			name : 'whole1',
			title: 'col 1',
			data : returnNumbers()
		},
		{
			name : 'whole2',
			title: 'col 2',
			data : returnNumbers()
		},
		{
			name : 'dot',
			title: 'decimal',
			draggable:false, // doesn't work!!
			data : [{text:'.',value:'.'},{text: '', value: ''}]
		},
		{
			name : 'decimal1',
			title: 'col 3',
			data :  returnNumbers()
		},
		{
			name : 'decimal2',
			title: 'col 4',
			data :  returnNumbers()
		}
	],
	listeners:{
		pick:function(pickerO,o,pickerS){
			if (pickerS.name == 'dot'){ // send back to the decimal...
				setTimeout(function(){pickerS.setSelectedNode(0,null);},20);
			}
		}
	}
});

Reset Ext.Panel Y Coord to 0

08 Feb 2012

Doubtless there is a way to do this with Ext, but this is my own method of returning a scrolling panel within Sencha Touch to the “top” of the view. I did spend some time reviewing the Sencha Touch docs but the only thing that came close to the desired result was the setPosition() method which only works on floating panels. I did spend some time playing with setting the floating property of the panel and then applying setPosition() but quickly ran into undesired results.

So, as always, time is short and a solution needs to be had quickly. A few minutes later I came up with this:

// setPositionToTop(cmp):void
// This function takes the scrolling area of a panel and sets its y position to 0;
// arguments: cmp:Object, an Ext.Panel
// returns: nothing
function setPositionToTop(cmp){
	var cmpId = cmp.getId();
	var sStr = document.getElementById(cmpId).firstChild.firstChild.getAttribute('style');
	if (sStr && sStr.indexOf('translate3d') != -1){
		var temp = sStr.split(',');
		temp[1] = ' 0px';
		var newStr = temp.join(',');
		//console.log('before: ' + sStr + ', after: ' + newStr);
	document.getElementById(cmpId).firstChild.firstChild.setAttribute('style',newStr)
	cmp.doComponentLayout();
	}
}

The above is based on the following source that Sencha Touch generates:

...

It should be plain that the top-most div is the component, and that I’m traversing the DOM to the third DIV and manipulating the style attribute. In this example the webkit-transform is responsible for the position of the content div and so I’m setting the y value back to 0px thus placing it where I want – back to the top.

The interesting thing here is that without calling doComponentLayout() on the component the space that the scrolling content area occupied before changing its position is still present resulting in a scrolling area larger than desired. In a worse-case scenario you can actually scroll the content area right off the screen – never to get it back. doComponentLayout() fixes this. Simply comment that line out to see this for yourself – scroll your panel to its most extreme y position, nav away, nav back, scroll your panel up as far as you can go…. gone.

To use this function I add it to a beforecardswitch listener for the desired panel and I add an if statement to check if the panel has been rendered yet, which it won’t unless you’ve visited it previously:

beforecardswitch:function(e,o){
	if (transactionsCardDeck.getActiveItem()){
		setPositionToTop(transactionsCardDeck.getActiveItem());
        }
}

The result is that the panel’s y coordinate is reset before you return to it.