vendredi 28 mai 2010

ScrollableView et ScrollView

Aujourd'hui, on passe franchement dans le bricolage un peu foireux, mais qui marche (en tout cas sur l'iPhone, je n'ai pas de téléphone sous Android pour tester)

Pour faire simple, je vais de nouveau me baser sur un exemple de KitchenSink un peu modifié, à savoir scroll_views_scrollable.js

Pour faire simple, voici le code un peu nettoyé...

var win = Titanium.UI.currentWindow;

var view1 = Ti.UI.createView({
  backgroundColor:'green'
});
var l1 = Ti.UI.createLabel({
  text:'Click Me (View 1)',
  color:'#fff',
  width:'auto',
  height:'auto'
});
view1.add(l1);

var view2 = Ti.UI.createView({
  backgroundColor:'blue'
});
var l2 = Ti.UI.createLabel({
  text:'Click Me (View 2)',
  color:'#fff',
  width:'auto',
  height:'auto'
});
view2.add(l2);


var scrollView = Titanium.UI.createScrollableView({
  views:[view1,view2],
  showPagingControl:true,
  pagingControlHeight:30,
  currentPage:0
});

win.add(scrollView);

Le résultat est simple : 2 vues côté à côte, et on peut passer de l'une à l'autre en glissant avec le doigt.
Mais, on complique un peu les choses, j'ai finalement besoin d'un grand texte sur ma première vue (je vous laisse le soin de générer le Lorem Ipsum de votre choix...). Pour que le texte s'affiche complètement, vous savez déjà qu'il faut une ScrollView, sinon un petit tour par les exemples de base s'impose.
Du coup, votre code ressemble à peu près à ça :

var view1 = Ti.UI.createScrollView({
 backgroundColor:'green',
 contentHeight:'auto',
 showHorizontalScrollIndicator: false, // le scroll que je veux est uniquement vertical
 horizontalBounce: false, // idem
 showVerticalScrollIndicator:true
});

var l1 = Ti.UI.createLabel({
 text:"Lorem ipsum [...] ligula venenatis.", // long texte, en fait...
 color:'#fff',
 width:'auto',
 height:'auto'
});
view1.add(l1);

...


On essaie, et... ça marche, où est le problème ? D'accord, ça marche. Donc, pour votre application, pas de souci, vous mettez une ScrollView dans une ScrollableView.
Oui, mais finalement, votre contenu est dynamique, il vient de votre base de données. Et parfois, le long texte est finalement très court.
Ré-essayez le même code qu'avant avec un texte très court à la place du long "Lorem Ipsum"...
Impossible de passer à la vue de droite. Ne me demandez pas pourquoi exactement, mais en gros l'événement scroll doit être intercepté par la ScrollView qui n'a rien à faire (son contenu est entièrement visible), et elle ne le transmet pas au dessus d'elle.
Voilà qui est beaucoup plus gênant.
Rassurez vous, si j'écris ce billet, c'est pour vous éviter de passer des heures à tâtonner comme je l'ai fait.

Récapitulons. On sait qu'une ScrollView est acceptable dans une ScrollableView uniquement quand son contenu est plus grand que ce qu'on peut voir. Bien. Il nous reste donc à rendre cette ScrollView toujours  trop grande. Et là, on rentre dans le bricolage un peu foireux, mais visiblement efficace.

Pour que notre contenu soit plus grand, rien de tel que de lui ajouter... un long texte. Oui, vous avez bien lu. Bon, je vous rassure, on n'a pas besoin de le rendre visible.

En gros, on obtient ça :

var view1 = Ti.UI.createScrollView({
 backgroundColor:'green',
 contentHeight:'auto',
 showHorizontalScrollIndicator: false,
 horizontalBounce: false,
 showVerticalScrollIndicator:true
});

var spacer = Ti.UI.createLabel({
 text:" ",
 width: 1,
 height:400,
 visible: false,
 top: 0,
 left: 0
});
view1.add(spacer);

var l1 = Ti.UI.createLabel({
 text:"Lorem ipsum rat...",
 color:'#fff',
 width:'auto',
 height:'auto'
});
view1.add(l1);

...

Et ça marche ! Bon, je vous laisse le soin d'adapter la hauteur de votre spacer à la taille de votre vue. D'après mes tests totalement empiriques, il suffit d'un pixel en plus pour que cela fonctionne.

Dans un prochain billet, on adaptera cette technique avec un layout vertical, car cela rajoute un peu de complexité...

jeudi 27 mai 2010

Animation pour zoomer sur une image

En regardant un peu l'application de démo (KitchenSink), j'ai trouvé l'exemple image_scaling.js
Génial, justement ce qu'il me fallait ! Des images en petites taille, et quand on clique dessus, un effet de zoome simple et efficace. Voyons ce code...

var image1 = Titanium.UI.createView({
  backgroundImage:'../images/smallpic1.jpg',
  height:75,
  width:75,
  top:40,
  left:50,
  borderWidth:3,
  borderColor:'#fff',
  zIndex:1
});

win.add(image1);

var center1 = null;

var scaled1 = false;
image1.addEventListener('click', function() {
  var t = Titanium.UI.create2DMatrix();
  
  if (!scaled1) {
    t = t.scale(4.0);
    center1 = image1.center;
    image1.animate({transform:t,center:win.center,zIndex:10,duration:500});
    scaled1 = true;
  }
  else {
    image1.animate({transform:t,center:center1,zIndex:1,duration:500});
    scaled1 = false;
  }
});

Bon, on peu discuter de la multiplication des variables (center1, scaled1...), surtout si on fait ça dans une boucle où tout est dynamique. Mais ce n'est pas le plus important.

Je me suis empressé d'adapter cela à mon code. Qui ressemblait à peu près ça :

// Chercher l'image sauvegardé auparavant (download, appareil photo...)
var f = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, "photo_1.jpg");

var image1 = Titanium.UI.createImageView({
  image: f,
  height:75,
  width:75,
  top:40,
  left:50,
  zIndex:1
});

win.add(image1);

image1.addEventListener('click', function(e) {
  var t = Titanium.UI.create2DMatrix(), im = e.source;
  
  if (! im.isScaled) {
    t = t.scale(4.0);
    im.initialCenter = image1.center;
    im.animate({transform:t,center:win.center, zIndex:10, duration:500});
    im.isScaled = true;
  }
  else {
    im.animate({transform:t,center: im.initialCenter,zIndex:1,duration:500});
    im.isScaled = false;
  }
});

Résultat : j'avais bien un effet de zoom, mais mon image était totalement pixelisée. En fait, même si l'image a des dimensions largement supérieures à celle de l'écran, la transformation (et donc la mise à l'échelle) semble se baser sur la vue présente. Mais ce n'est pas du tout ce qui se passait dans la démo pourtant !
On observe un peu plus en détail... Tiens, ils ont en fait créé une simple vue avec une image de fond : backgroundImage.
OK. On essaie à nouveau.

var f = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, "photo_1.jpg");

var image1 = Titanium.UI.createView({
  backgroundImage: f,
  height:75,
  width:75,
  top:40,
  left:50,
  zIndex:1
});

Roulements de tambour... Et crash de l'application. Aïe ! Et oui, la propriété backgroundImage attend une chaîne de caractères, et non un objet File.
Il reste alors une idée : récupérer l'adresse de ce fichier. On va voir la documentation. Ah, aucune doc sur l'objet File...
On se replonge dans l'appli KitchenSink... Miracle, il existe une propriété nativePath sur l'objet File...
Dernier essai :

var win = Titanium.UI.currentWindow;

var f = Titanium.Filesystem.getFile(Titanium.Filesystem.applicationDataDirectory, "photo_1.jpg");

var image1 = Titanium.UI.createView({
  backgroundImage: f.nativePath,
  height:80,
  width:60,
  top:40,
  left:50,
  borderWidth:3,
  borderColor:'#fff',
  zIndex:1
});

win.add(image1);

image1.addEventListener('click', function(e) {
  var t = Titanium.UI.create2DMatrix(), im = e.source;
   
  if (! im.isScaled) {
    t = t.scale(4.5);
    im.initialCenter = image1.center;
    im.animate({transform:t,center:win.center, zIndex:10, duration:350});
    im.isScaled = true;
  }
  else {
    im.animate({transform:t,center: im.initialCenter,zIndex:1,duration:350});
    im.isScaled = false;
  }
});

Et là, enfin, on obtient ce que l'on attendait... Maintenant que je le sais, cela me semble évident, mais ce n'était pas le cas tout de suite...

Update : au lieu de passer par la création d'un objet File, on peut aussi faire directement comme ça...

...
  backgroundImage: Titanium.Filesystem.applicationDataDirectory + "/photo_1.jpg",
  ...

mercredi 26 mai 2010

Redimensionner une image

C'est un problème soulevé quelques fois dans le forum, et que je me suis moi-même posé... Le truc classique, c'est de prendre une photo puis de la stocker ou l'envoyer à un serveur web. Oui, mais le problème, c'est que la photo est grosse, très grosse. Enfin, pas non plus plus du format RAW, mais assez grosse pour mettre 3 heures à partir avec une petite connection 3G. Surtout que bien souvent, la première chose que va faire le serveur, c'est justement de redimensionner l'image...
Bref, tout ça pour dire qu'on veut redimensionner l'image directement sur le téléphone. Oui, mais la documentation (très légère) ne semble pas indiquer d'option pour ça. En tâtonnant, on découvre que l'image a des propriétés height et width, mais non modifiables... Et là, on se met râler...

Sauf que Titanium permet de transformer toutes les vues en images. Et oui, la méthode toImage()...

Donc, en gros, voilà le code qui fonctionne (sous iPhone en tout cas, pas testé sous Android)

var imageView = Titanium.UI.createImageView({
   image:image,
   width:480,
   height:640
});

image = imageView.toImage();

Voilà la première astuce de ce blog...