French Comment font-ils ? #4

Informatix

Expert
Licensed User
Longtime User
Comment font-ils ? #4

Ce quatrième tutoriel est consacré à l'animation et au mouvement. Je vais reproduire tout d'abord les deux animations principales de Catch Notes de Catch.com, puis le fonctionnement de l'interface de l'écran d'accueil d'Android version Gingerbread.

Ce tutoriel suppose que vous maîtrisez les bases de la programmation avec B4A et que vous savez installer une bibliothèque ou une classe. Il suppose également que vous avez lu les tutoriels précédents.

1ère partie : Catch Notes

attachment.php


Cette application a deux animations principales :
  1. Lorsque le bouton + est pressé, une roue avec des boutons jaillit (voir les copies d'écran ci-dessus). Le téléphone vibre brièvement et la liste est assombrie. L’animation de la roue se termine par un léger rebond.
  2. Les items de la liste s'écartent lorsque l'un d'eux est sélectionné. L'interface change progressivement de couleur pour adopter celle de l'item :
attachment.php


Je commence par recréer l'interface principale de l'application. Je vous ai montré comment faire dans les tutoriels précédents. Je passe donc rapidement cette étape. J'utilise la classe ActionBar pour les barres de boutons du haut et du bas et la fonction drawArc de la bibliothèque ABExtDrawing pour dessiner la partie arrondie au dessus du bouton +.

Le contenu de Globals :
B4X:
' Icons
Dim dIconSettings, dIconPeople As BitmapDrawable
Dim dIconSearch, dIconPlus As BitmapDrawable

' Colors
Dim gdAB As GradientDrawable
Dim cdPressed As ColorDrawable
Dim abGray As Int: abGray = 225
Dim GrayBackground As Int

' Top bar
Dim BarSize As Int: BarSize = 48dip
Dim abTop As ClsActionBar
Dim btnTop(2) As View
Dim ivLogo As ImageView

' ScrollView
Dim svList As ScrollView
Dim pnlDarken As Panel

' Bottom bar
Dim pnlBottom As Panel
Dim abBottom As ClsActionBar
Dim btnBottom(2) As View

' Button +
Dim btnOpenMenu As Button
Dim C As Canvas
Dim ABExtDrw As ABExtDrawing
Dim edRectF As ABRectF
Dim edPaint As ABPaint

' Wheel
Dim pnlMenuWheel As Panel

Le contenu de Activity_Create:
B4X:
' Icons
dIconSettings.Initialize(LoadBitmap(File.DirAssets, "btn_settings.png"))
dIconPeople.Initialize(LoadBitmap(File.DirAssets, "btn_people.png"))
dIconSearch.Initialize(LoadBitmap(File.DirAssets, "btn_search.png"))
dIconPlus.Initialize(LoadBitmap(File.DirAssets, "btn_plus.png"))

' Colors
Dim gdColors(2) As Int
gdColors(0) = Colors.RGB(abGray, abGray, abGray) 'Light gray
gdColors(1) = Colors.RGB(abGray - 20, abGray - 20, abGray - 20)
gdAB.Initialize("TOP_BOTTOM", gdColors)
cdPressed.Initialize(Colors.RGB(174, 174, 174), 0) 'Gray
GrayBackground = Colors.RGB(215, 215, 215) 'Light gray
Activity.Color = GrayBackground

' Top bar
abTop.Initialize(Activity, True, False, BarSize, Me)
ivLogo.Initialize("")
ivLogo.Gravity = Gravity.FILL
ivLogo.SetBackgroundImage(LoadBitmap(File.DirAssets, "logo.png"))
abTop.AsPanel.AddView(ivLogo, 11dip, 11dip, 75dip, 26dip)
FillMainTopBar

' ScrollView
svList.Initialize2(0, "")
svList.Color = GrayBackground
Activity.AddView(svList, 0, BarSize, 100%x, 100%y - (BarSize * 2))

' Darkening panel above the ScrollView
pnlDarken.Initialize("pnlDarken")
pnlDarken.Color = Colors.ARGB(128, 128, 128, 128)
pnlDarken.Visible = False
Activity.AddView(pnlDarken, svList.Left, svList.Top, svList.Width, svList.Height)

' Bottom bar
pnlBottom.Initialize("")
pnlBottom.Color = Colors.Transparent
Activity.AddView(pnlBottom, 0, 100%y - 72dip, 100%x, 72dip)
abBottom.Initialize(pnlBottom, False, False, BarSize, Me)
FillMainBottomBar

' Button +
C.Initialize(pnlBottom)
edRectF.Initialize(50%x - 43dip, pnlBottom.Height - 69dip, 50%x + 43dip, pnlBottom.Height + 17dip)
edPaint.Initialize
edPaint.SetAntiAlias(True) 'Smooths out the edges
edPaint.SetStyle(edPaint.Style_FILL_AND_STROKE)
DrawArcAboveButton(gdColors(0))
btnOpenMenu.Initialize("btnOpenMenu")
btnOpenMenu.Background = dIconPlus
pnlBottom.AddView(btnOpenMenu, 50%x - 40dip, pnlBottom.Height - 66dip, 80dip, 80dip)

' Buttons wheel
pnlMenuWheel.Initialize("")
pnlMenuWheel.SetBackgroundImage(LoadBitmap(File.DirAssets, "btn_wheel.png"))
pnlMenuWheel.Visible = False
Activity.AddView(pnlMenuWheel, 50%x - 157dip, 100%y - 184dip, 314dip, 314dip)

Ce code fait appel aux fonctions suivantes pour ajouter les boutons et dessiner la partie arrondie :
B4X:
Sub FillMainTopBar
   ' Sets the background and adds the buttons
   abTop.SetBackground(gdAB)
   abTop.ReplacePressedDrawable(cdPressed)
   btnTop(0) = abTop.AddButton(dIconPeople, "", 5, -1, "btnPeople_Click", "")
   btnTop(1) = abTop.AddButton(dIconSettings, "", 5, -2, "btnSettings_Click", "")
   abTop.SetFixedWidth(btnTop(0), BarSize + 6dip)
   abTop.SetFixedWidth(btnTop(1), BarSize + 6dip)
End Sub
Sub FillMainBottomBar
   ' Sets the background and adds the Search button
   abBottom.SetBackground(gdAB)
   abBottom.ReplacePressedDrawable(cdPressed)
   btnBottom(0) = abBottom.AddButton(dIconSearch, "", 5, 1, "btnSearch_Click", "")
   abBottom.SetFixedWidth(btnBottom(0), BarSize + 6dip)
End Sub
Sub DrawArcAboveButton(Color As Int)
   C.DrawColor(Colors.Transparent) 'Erases the canvas
   edPaint.SetColor(Color)
   ABExtDrw.drawArc(C, edRectF, -150, 120, True, edPaint)
End Sub

Résultat :
attachment.php


Pour assombrir le ScrollView central, j’ai choisi une méthode très simple : le recouvrir d’un Panel gris semi-transparent (pnlDarken). L’avantage du Panel, c’est qu’il peut intercepter les actions de l’utilisateur quand il est visible et donc bloquer les clics sur le contenu du ScrollView.

Pour faire surgir la roue, je vais utiliser une tween animation. Dans le jargon des spécialistes, c’est une animation dont les images sont calculées et dessinées par le système en fonction d’un état initial et d’un état d’arrivée. C’est la forme d’animation la plus simple à mettre en œuvre avec Android. Dans le cas présent, l’état initial va être une petite roue. On va la faire grossir jusqu’à sa taille normale avec une animation de type Scale, puis on va créer un léger rebond avec un interpolateur de type Overshoot. Un interpolateur est une fonction qui modifie la progression linéaire d’une animation. On s’en sert pour faire accélérer ou décélérer les animations, pour les faire rebondir, pour les faire osciller, etc. Pour avoir accès aux interpolateurs, il faut utiliser la bibliothèque AnimationPlus.
J’ajoute dans Globals :
B4X:
Dim AnimOpenMenu As AnimationPlus
Puis dans Activity_Create :
B4X:
AnimOpenMenu.InitializeScaleCenter("", 0.6, 0.6, 1, 1, pnlMenuWheel) '60% -> 100%
AnimOpenMenu.Duration = 130 'milliseconds
AnimOpenMenu.RepeatCount = 0
AnimOpenMenu.SetInterpolatorWithParam(AnimOpenMenu.INTERPOLATOR_OVERSHOOT, 5)

Pour faire vibrer le téléphone lors de l’appui sur le bouton +, je fais appel à la bibliothèque Phone. J’ajoute dans Globals :
B4X:
Dim PV As PhoneVibrate

Il ne reste plus qu’à écrire le gestionnaire de l’événement onDown du bouton +:
B4X:
Sub btnOpenMenu_Down
   ' Shows the buttons wheel
   pnlMenuWheel.Visible = True
   PV.Vibrate(40) 'Short vibration
   AnimOpenMenu.Start(pnlMenuWheel)

   ' Shows the darkening panel
   pnlDarken.Visible = True
End Sub

Dès que l'ordre Start sera exécuté, l'animation va créer un thread séparé et afficher ses images en parallèle du thread principal du programme. Ma fonction n'attendra donc pas la fin de l'animation pour continuer son exécution après le Start. En règle générale, il vaut mieux éviter de faire quoi que ce soit après le lancement d’une animation car cela pourrait perturber sa fluidité, mais ici la modification de la visibilité de pnlDarken est sans incidence. Je l'ai placé après pour être sûr que le Panel apparaît bien après le début de l'animation. Normalement, je devrais utiliser l’événement AnimationEnd de l’animation pour que cette apparition survienne à la fin, mais j'ai constaté une certaine latence qui n'était pas du meilleur effet; j'ai préféré anticiper plutôt qu'avoir du retard.

Résultat final :
attachment.php


Pour cette animation, je me suis simplifié la tâche en utilisant une image combinant la roue et ses boutons. Dans Catch Notes, il va sans dire que les boutons sont des objets séparés. Quand on clique sur l’un d’eux, leur portion de cercle s’illumine. Pour créer cet effet, vous pouvez utiliser la fonction drawArc que nous venons de voir ou faire une image colorée de la portion de cercle, le reste de l’image devant être transparent, et charger l’image dans une StateListDrawable que vous affecterez au background du bouton. En fonction de l’action de l’utilisateur, le système se chargera de faire apparaître ou disparaître ce fond coloré.

Passons à la deuxième animation. Elle est un peu plus compliquée à réaliser car il n’est pas possible de recourir à une tween animation. C'est une bonne occasion de voir ce qu’est vraiment une animation et comment on exécute du code dans un temps imparti.
Pour réaliser l’animation de Catch Notes, il faut simultanément changer progressivement la couleur des deux barres d’actions et du cercle autour du bouton +, et faire glisser les items de la liste du ScrollView vers le haut ou vers le bas. Pour compliquer un peu plus les choses, chaque item doit glisser à son propre rythme (il n’y a pas de déplacement en bloc).

Plusieurs approches sont possibles. La plus évidente pour un débutant est de créer une boucle de 1 à n et, à chaque tour de boucle, d’afficher une nouvelle phase de l’animation. Inconvénient : la boucle va tourner plus ou moins vite selon les appareils. Votre animation sera beaucoup trop lente sur les téléphones premier prix et beaucoup trop rapide sur les quadri-cœurs haut de gamme. Je déconseille donc cette approche. Il en existe une bien meilleure et pourtant très simple :
Je crée une boucle qui tourne jusqu’à épuisement du temps imparti. En mesurant le temps écoulé à chaque tour de boucle, je connais la proportion de temps restant et, par là, je sais quelle proportion appliquer à la transformation de mes vues pour les animer. Si quelque chose vient perturber la fluidité de mon animation, je vais sauter automatiquement certaines étapes (mon animation sera, au pire, gelée ou saccadée l’espace d’un instant), mais j’atteindrai à coup sûr la dernière phase dans la limite du temps imparti.
Le code de la boucle:
B4X:
Dim Duration As Int: Duration = 250
Dim EndTime, DeltaTime As Long
EndTime = DateTime.Now + Duration
Do While DateTime.Now < EndTime
   DeltaTime = EndTime - DateTime.Now
   Animate(1 - (DeltaTime / Duration))
   DoEvents 'Processes the draw messages and keeps the UI responsive
Loop
If DeltaTime <> 0 Then Animate(1)
Cette boucle appelle une fonction Animate avec un paramètre décimal entre 0 (début de l’animation) et 1 (fin de l’animation). Pour que son déroulement soit le plus fluide possible, voici quelques conseils :
  • évitez au maximum les créations et les initialisations d’objets dans la boucle (notamment les vues et les tableaux);
  • évitez les calculs inutiles (ceux qui peuvent être faits avant d’entrer dans la boucle);
  • évitez les conditions qui font varier le nombre d’instructions à exécuter à chaque tour de boucle;
  • évitez absolument de faire des opérations de lecture ou d’écriture sur le média de stockage (pré-chargez en mémoire toutes les images dont vous avez besoin).
Il ne faut pas oublier de mettre un DoEvents à la fin de la boucle sinon l’affichage ne sera pas rafraîchi, l’utilisateur sera bloqué et le système risquera de croire que l’application est plantée.

Les concepteurs de jeux utilisent une approche plus complexe qui privilégie la synchronisation des objets en mouvement et des effets sonores ou graphiques, ce qui nous importe peu ici. Dans une animation classique, c’est la notion de durée d’exécution qui prime.

Pour pouvoir réaliser mon animation, il me faut des Panels dans le ScrollView. J’en crée quelques-uns de couleurs différentes et d’une hauteur de 70dip. Détail important : dans la propriété Tag de chacun d’eux, je stocke la valeur des trois canaux de la couleur choisie. Pour cela, j’ai besoin d’un type déclaré dans Process_Globals :
B4X:
Type typColor(Red As Int, Green As Int, Blue As Int)
Exemple de mémorisation de la couleur:
B4X:
Dim pnlColor As typColor
pnlColor.Red = 250
pnlColor.Green = 80
pnlColor.Blue = 80
pnl.Tag = pnlColor

attachment.php


Je peux écrire maintenant mon gestionnaire de l’événement onClick (événement déclenché par un clic sur un des Panels du ScrollView) :
B4X:
Sub pnlItem_Click
   ' Prepares the animations
   Dim pnlPressed As Panel
   pnlPressed = Sender

   pnlColorValues = pnlPressed.Tag
   ColorValues.Red = abGray
   ColorValues.Green = abGray
   ColorValues.Blue = abGray

   PressedPanelIndex = pnlPressed.Top / 70dip
   FirstVisiblePanel = Floor(svList.ScrollPosition / 70dip)
   LastVisiblePanel = Min(Floor((svList.ScrollPosition + svList.Height) / 70dip), svList.Panel.NumberOfViews - 1)
   Dim PnlMove(LastVisiblePanel - FirstVisiblePanel + 1) As typPnlMove
   Dim pnl As Panel
   For i = FirstVisiblePanel To LastVisiblePanel
      ' This supposes that the apparent order of panels is the same as their z-order
      ' (that's always true with the CheckList class, but not with the CustomListView class)
      pnl = svList.Panel.GetView(i)
      PnlMove(i - FirstVisiblePanel).Top = pnl.Top
      If i > PressedPanelIndex Then
         PnlMove(i - FirstVisiblePanel).MoveQty = svList.ScrollPosition + svList.Height - pnl.Top
      Else
         PnlMove(i - FirstVisiblePanel).MoveQty = pnl.Top + pnl.Height - svList.ScrollPosition
      End If
   Next

   ' Runs the animations
   Dim Duration As Int: Duration = 250
   Dim EndTime, DeltaTime As Long
   EndTime = DateTime.Now + Duration
   Do While DateTime.Now < EndTime
      DeltaTime = EndTime - DateTime.Now
      Animate(1 - (DeltaTime / Duration))
      DoEvents 'Processes the draw messages and keeps the UI responsive
   Loop
   If DeltaTime <> 0 Then Animate(1)
End Sub

Dans cette fonction, j’ai préparé toutes les valeurs utiles à mon animation avant d’entrer dans la boucle. Je n’aurai donc à faire qu’un minimum de calcul à chaque tour de boucle. Certaines variables sont déclarées dans Globals car ma fonction Animate va les utiliser :
B4X:
Dim pnlColorValues As typColor
Dim ColorValues As typColor
Dim PressedPanelIndex, FirstVisiblePanel, LastVisiblePanel As Int
Dim PnlMove() As typPnlMove
J’utilise un type déclaré dans Process_Globals qui me permet de stocker la position de départ de chaque panel et la quantité du mouvement à effectuer :
B4X:
Type typPnlMove(Top As Int, MoveQty As Int)

Il manque encore une chose à ma fonction. Puisque DoEvents permet à l’utilisateur d’interagir avec mon application, celui-ci risque de cliquer à nouveau sur un Panel du ScrollView. Je n’ai pas envie qu’il redéclenche l’animation. D’autre part, j’ai besoin de savoir dans mes autres fonctions si cette animation est en cours ou terminée. Je rajoute donc au début de la fonction :
B4X:
' Animation flag (it prevents from triggering the anim again while the anim is running)
If AnimInProgress Then Return
AnimInProgress = True
Et à la fin:
B4X:
AnimInProgress = False
J’ai, bien sûr, déclaré cette valeur booléenne dans Globals.

J’écris ma fonction Animate :
B4X:
Sub Animate(Percent As Float)
   If Percent < 0.01 Then Return

   ' Color animation
   Dim NewRed, NewGreen, NewBlue As Int
   NewRed = ColorValues.Red - ((ColorValues.Red - pnlColorValues.Red) * Percent)
   NewGreen = ColorValues.Green - ((ColorValues.Green - pnlColorValues.Green) * Percent)
   NewBlue = ColorValues.Blue - ((ColorValues.Blue - pnlColorValues.Blue) * Percent)
   Dim gd(2) As Int
   Dim NewGD As GradientDrawable
   gd(0) = Colors.RGB(NewRed, NewGreen, NewBlue)
   gd(1) = Colors.RGB(NewRed - 20, NewGreen - 20, NewBlue - 20)
   NewGD.Initialize("TOP_BOTTOM", gd)
   abTop.SetBackground(NewGD)
   abBottom.SetBackground(NewGD)
   DrawArcAboveButton(gd(0))

   ' Panel animation
   Dim pnl As Panel
   For i = FirstVisiblePanel To PressedPanelIndex
      pnl = svList.Panel.GetView(i)
      pnl.Top = PnlMove(i - FirstVisiblePanel).Top - (PnlMove(i - FirstVisiblePanel).MoveQty * Percent)
   Next
   For i = PressedPanelIndex + 1 To LastVisiblePanel
      pnl = svList.Panel.GetView(i)
      pnl.Top = PnlMove(i - FirstVisiblePanel).Top + (PnlMove(i - FirstVisiblePanel).MoveQty * Percent)
   Next
End Sub
Il est inutile d’exécuter la fonction tant que le pourcentage de progression (Percent) est inférieur à 1%. Si c’est le cas, je sors immédiatement.

Bien que cette fonction ne fasse pas appel aux routines de dessin les plus performantes de l’API d’Android, elle se montre particulièrement véloce. Chaque tour de boucle ne nécessite qu’une quinzaine de millisecondes sur un Huawei Honor (mono-cœur 1,4 Ghz). Cela donne un FPS (frame per second) moyen supérieur à 60, ce qui est excellent. C’est plus du double de la vitesse des images sur une télévision. Je suis donc tranquille : même sur un vieil appareil poussif, mon animation sera fluide.

Quand mon animation se termine, l’interface doit arborer de nouveaux boutons. Je rajoute donc une ligne à la fin du gestionnaire pnlItem_Click :
B4X:
TransformUI
Puis j’ajoute les fonctions appelées :
B4X:
Sub TransformUI
   ivLogo.RemoveView
   abTop.RemoveAllButtons
   FillTopBar_Note
   abBottom.RemoveAllButtons
   FillBottomBar_Note
End Sub
Sub FillTopBar_Note
   ' Adds the buttons in the top action bar for the Note layout
   btnTop(0) = abTop.AddButton(LoadBitmap(File.DirAssets, "btn_c_orange.png"), "", 5, 1, "btnBack_Click", "")
   abTop.SetFixedWidth(btnTop(0), BarSize + 6dip)
   btnTop(1) = abTop.AddButton(LoadBitmap(File.DirAssets, "btn_hexagon.png"), "", 5, -1, "btnHexagon_Click", "")
   abTop.SetFixedWidth(btnTop(1), BarSize + 6dip)
End Sub
Sub FillBottomBar_Note
   ' Adds the buttons in the bottom action bar for the Note layout
   btnBottom(0) = abBottom.AddButton(LoadBitmap(File.DirAssets, "btn_paperclip.png"), "", 5, 1, "btnJoin_Click", "")
   abBottom.SetFixedWidth(btnBottom(0), BarSize + 6dip)
   btnBottom(1) = abBottom.AddButton(LoadBitmap(File.DirAssets, "btn_delete.png"), "", 5, -1, "btnDelete_Click", "")
   abBottom.SetFixedWidth(btnBottom(1), BarSize + 6dip)
End Sub

Voilà le résultat après un clic sur le 4e Panel (j’ai volontairement omis certaines icônes) :
attachment.php


2e et 3e parties dans le post suivant...
 

Attachments

  • Catch1.jpg
    Catch1.jpg
    80.7 KB · Views: 1,567
  • Catch2.png
    Catch2.png
    46.6 KB · Views: 1,487
  • Catch3.png
    Catch3.png
    17.2 KB · Views: 1,446
  • Catch4.png
    Catch4.png
    62.8 KB · Views: 1,458
  • Catch5.png
    Catch5.png
    17.4 KB · Views: 1,439
  • Catch6.png
    Catch6.png
    19.7 KB · Views: 1,421
Last edited:

Informatix

Expert
Licensed User
Longtime User
2ème partie : l'écran d'accueil d’Android et les puzzles

Qu’y a-t-il de commun entre l'écran d'accueil d’Android version Gingerbread et « Animal sounds puzzle for kids » de Thas Studios ?

attachment.php


  1. On peut déplacer des objets visuels avec le doigt (icône ou pièce du puzzle);
  2. Certaines zones de l’écran agissent comme des aimants. Les objets se retrouvent « ancrés » quand ils sont déplacés sur ces zones.
Concernant l'écran d'accueil, je pourrais reproduire son fonctionnement avec la classe Floating Windows, qui permet de spécifier des zones d’ancrage (docking areas), mais ce ne serait pas une solution très satisfaisante. J'ai donc choisi d'écrire une classe spécifique pour cet écran et d'utiliser Floating Windows pour le puzzle. Vous pourrez télécharger tout ce code à la fin du tutoriel.

Commençons par l'écran d'accueil. Contrairement au puzzle où chaque zone d'ancrage est propre à une pièce, ici toutes les zones sont partagées. Il me faut donc une liste commune déclarée dans Globals :
B4X:
Dim lstDockingAreas As List

Dans Activity_Create, je remplis ma liste avec 17 zones (la grille 4x4 + la corbeille) :
B4X:
lstDockingAreas.Initialize
lstDockingAreas.Add(rctTrashCan)
IconWidth = 100%x / 5
IconHeight = (100%y - 70dip) / 5
Dim Left, Top As Int
For i = 3 To 0 Step -1
   For j = 3 To 0 Step -1
      Left = j * (IconWidth * 1.2) + (IconWidth / 5)
      Top = i * (IconHeight * 1.2) + (IconHeight / 5)
      Dim daRect As Rect
      daRect.Initialize(Left, Top, Left + IconWidth, Top + IconHeight - 5dip)
      lstDockingAreas.Add(daRect)
   Next
Next

rctTrashCan est le rectangle englobant la corbeille; IconWidth et IconHeight définissent la taille de mes icônes dans la grille.
Ma liste est créée de la base au sommet et de la droite vers la gauche. Cet ordre inversé permet de conserver la corbeille en tête de la liste et de faciliter les opérations sur la liste. Quand une icône viendra s'ancrer sur une zone, celle-ci sera retirée de la liste. Inversement, quand une icône quittera une zone, celle-ci sera ajoutée à la fin de la liste. Ma liste est donc une liste dynamique des zones d'ancrage disponibles.

Les 17 zones :
attachment.php


Pour les icônes, le plus pratique est de créer une classe que je vais appeler ClsMovableIcon. Cette classe a quatre objectifs :
  • créer l’icône avec un contenu personnalisé ;
  • gérer le déplacement de l’icône avec le doigt ;
  • détecter la zone d’ancrage la plus proche de l’icône ;
  • ancrer ou libérer l’icône.

La création de l’icône se fait dans la fonction Initialize. On crée un Panel (Window) qui contient une ImageView (Icon) et un Label (IconText). Seul le Panel reçoit et traite les actions de l’utilisateur. Le drapeau booléen wDocked indique si l’icône est ancrée ou non.

Première difficulté : la classe doit pouvoir recevoir à la fois l’événement LongClick (clic long) pour activer le déplacement de l’icône et l’événement Touch pour réaliser le déplacement, or B4A ne permet plus cela depuis la version 2 car l’événement Touch est « consommé » lors de sa réception. La routine du clic long ignore donc que l’écran a été touché. Solution : en utilisant la bibliothèque Reflection, on peut déclarer un listener de l’événement Touch qui ne bloque pas la propagation de l’événement :
B4X:
Dim r As Reflector
r.Target = Window
r.SetOnTouchListener("Window_onTouch")

Je peux écrire mes deux gestionnaires d’événements :
B4X:
Private Sub Window_LongClick
   ' Brings the window to the front
   Window.BringToFront

   ' Removes the "pressed" background
   Dim cd As ColorDrawable
   cd.Initialize(Colors.Transparent, 0)
   Window.Background = cd

   ' Undocks the window
   wDocked = False
   If SubExists(wModule, OnUndockWinSub) Then CallSub2(wModule, OnUndockWinSub, Me)

   ' Enlarges the window
   Enlarge
End Sub
Private Sub Window_onTouch(ViewTag As Object, Action As Int, X As Float, Y As Float, MotionEvent As Object) As Boolean
   If Action = 0 Then ' DOWN
      wStartX = X
      wStartY = Y
      Window.Background = wPressedBackground
   End If
   If wDocked Then
      If Action = 1 Then
         Dim cd As ColorDrawable
         cd.Initialize(Colors.Transparent, 0)
         Window.Background = cd
      End If
      Return False
   End If
   If Action = 2 Then ' MOVE
      MoveWin(getLeft + X - wStartX, getTop + Y - wStartY)
      CheckDockingAreas(False, 0)
   Else If Action = 1 Then ' UP
      CheckDockingAreas(True, 100)
   End If
   Return True
End Sub

Quand l’utilisateur touche une icône, il déclenche l’événement Touch avec la valeur d’action 0 (down). Je mémorise la position de départ et je change le fond du Panel. Tant qu’un clic long n’a pas été détecté, je propage l’événement (return false). Quand le clic long a été détecté, j’amène l’icône au premier plan (elle pourra glisser par-dessus les autres icônes), je supprime le fond du Panel, je libère l’icône de sa zone d’ancrage (wDocked devient faux) et j’agrandis l’icône. Je n’ai plus besoin de recevoir d’autres événements que Touch donc le gestionnaire de cet événement va pouvoir le consommer (return true). Il ne reste plus que deux actions possibles : le déplacement du doigt (valeur d’action 2 : je déplace l’icône à l’identique) ou le relâchement du doigt (valeur d’action 1 : je dois trouver une zone pour ancrer mon icône).

Deuxième difficulté : comment détecter la zone d’ancrage se trouvant la plus proche de l’icône ? J’ai résolu le problème par un calcul de surface. Si l’icône se trouve en partie au-dessus d’une zone d’ancrage, je calcule la surface couverte. À la fin, je choisis la zone avec la surface la plus couverte :
B4X:
Dim wRect As Rect
Dim Surface, BiggestSurface As Long
Dim SelectedArea As Int
SelectedArea = -1
For i = 0 To wDockingAreas.Size - 1
   wRect = wDockingAreas.Get(i)
   If (getLeft <= wRect.Right AND getLeft + getWidth >= wRect.Left) AND _
         (getTop <= wRect.Bottom AND getTop + getHeight >= wRect.Top) Then
      ' The icon is over a docking area
      ' We calculate the covered area
      Dim SurfaceW, SurfaceH As Int
      If getLeft > wRect.Left Then
         SurfaceW = Min(wRect.Right - getLeft, getWidth)
      Else
         SurfaceW = getLeft + getWidth - wRect.Left
      End If
      If getTop > wRect.Top Then
         SurfaceH = Min(wRect.Bottom - getTop, getHeight)
      Else
         SurfaceH = getTop + getHeight - wRect.Top
      End If
      Surface = SurfaceW * SurfaceH
      If Surface > BiggestSurface Then
         ' We select the docking area with the most covered surface
         SelectedArea = i
         BiggestSurface = Surface
      End If
   End If
Next

Si l’icône ne se trouve pas au-dessus d’une zone d’ancrage, je choisis d’office la dernière zone de la liste :
B4X:
If SelectedArea = -1 Then
   ' The icon isn't over a docking area
   ' We select the last available docking area
   SelectedArea = wDockingAreas.Size - 1
End If

Troisième difficulté : quand l’icône est libérée de sa zone d’ancrage, elle est agrandie afin de faciliter sa manipulation et de créer une impression de rapprochement. Comment réaliser ce zoom ? J'ai deux possibilités : soit agrandir l’ImageView et le Label en faisant des calculs d’échelle, soit transformer mon Panel en bitmap, masquer les vues et peindre le fond du Panel avec ce bitmap. En agrandissant le Panel, je vais agrandir le fond automatiquement. C'est la solution que j'ai choisie :
B4X:
Private Sub Enlarge
   ' Enlarges the window to ease its handling
   Dim bd As BitmapDrawable
   bd.Initialize(ViewToBmp(Window))
   Window.Background = bd

   Icon.Visible = False
   IconText.Visible = False

   Dim Factor As Float: Factor = 1.3
   Window.Width = Round(getWidth * Factor)
   Window.Height = Round(getHeight * Factor)
Comme l'utilisateur a le doigt posé sur l'icône, il faut que l'icône donne l'impression de grandir autour du doigt et pas seulement sur un côté. J'ajuste donc la position du Panel et les coordonnées du doigt :
B4X:
   Window.Left = getLeft - ((getWidth - wStartWidth) / 2)
   wStartX = wStartX + ((getWidth - wStartWidth) / 2)
   Window.Top = getTop - ((getHeight - wStartHeight) / 2)
   wStartY = wStartY + ((getHeight - wStartHeight) / 2)
End Sub

Ma fonction fait appel à ViewToBmp qui transforme en bitmap n'importe quelle vue avec ses enfants :
B4X:
Sub ViewToBmp(MyView As View) As Bitmap
   ' Returns a screenshot of the given view
   Dim bmp As Bitmap
   bmp.InitializeMutable(MyView.Width, MyView.Height)
   C.Initialize2(bmp)

   Dim r1 As Reflector
   r1.Target = C
   r1.Target = r1.GetField("canvas")

   Dim r2 As Reflector
   Dim args(1) As Object
   Dim types(1) As String
   args(0) = r1.Target
   types(0) = "android.graphics.Canvas"
   r2.Target = MyView
   r2.RunMethod4("draw", args, types) ' "Draw" renders a view and its children to the given Canvas

   Return C.Bitmap
End Sub

Lorsque le déplacement est terminé, l'icône doit retrouver sa taille normale. Le fond redevient transparent et les vues sont de nouveau visibles. J'ajoute une petite animation pour être fidèle à l'original :
B4X:
Private Sub RestoreNormalSize
   ' Restores the normal size
   Window.Width = wStartWidth
   Window.Height = wStartHeight
   Window.Color = Colors.Transparent

   Icon.Visible = True
   IconText.Visible = True

   Anim.InitializeScaleCenter("", 1, 1, 0.8, 0.8, Window)
   Anim.Duration = 70
   Anim.RepeatMode = Anim.REPEAT_REVERSE
   Anim.RepeatCount = 1
   Anim.Start(Window)
End Sub

Pour tester ma classe, je crée un gestionnaire d'événements pour le panneau gris en bas de l'écran. En appuyant dessus, je vais générer automatiquement une icône et l'ancrer sur la première zone disponible :
B4X:
Sub pnlBottom_Click
   'Creates a new movable icon
   For i = 0 To Icons.Length - 1
      If Icons(i) = Null Then
         Dim NewIcon As ClsMovableIcon
         NewIcon.Initialize(Activity, 0, 0, IconWidth, IconHeight, LoadBitmap(File.DirAssets, "icon_" & Rnd(1, 11) & ".png"), "Icon #" & i, lstDockingAreas, Me)
         NewIcon.SetOnEnterDockAreaListener("Icon_onEnterDockArea")
         NewIcon.SetOnLeaveDockAreaListener("Icon_onLeaveDockArea")
         NewIcon.SetOnDockListener("Icon_onDock")
         NewIcon.SetOnUndockListener("Icon_onUndock")
         NewIcon.ForceDock
         Icons(i) = NewIcon
         Exit
      End If
   Next
End Sub
Avec les fonctions SetOn...Listener, j'ai indiqué à la classe qu'elle devait me renvoyer certains événements afin que je puisse mettre à jour ma liste des zones d'ancrage disponibles et gérer le cas particulier de la corbeille. La classe va appeler les fonctions passées en paramètre avec un CallSub. Exemple :
B4X:
Public Sub SetOnDockListener(SubName As String)
   OnDockWinSub = SubName
End Sub
...
If SubExists(wModule, OnDockWinSub) Then CallSub3(wModule, OnDockWinSub, Me, wLastRect)
...

L'événement onEnterDockArea, déclenché quand l'icône survole une zone d'ancrage, me sert à colorier la corbeille en rouge (je rends visible une image rouge de la corbeille) et à signifier à l'utilisateur que l'icône déplacée est bien dans la zone de suppression :
B4X:
Sub Icon_onEnterDockArea(Icon As ClsMovableIcon, DockRect As Rect)
   If DockRect = rctTrashCan Then
      ivTrashCan.Visible = True
   End If
End Sub

Résultat :
attachment.php


Quand l'icône cesse de survoler la corbeille (événement onLeaveDockArea), je lui restitue son aspect normal (je rends invisible la corbeille rouge) :
B4X:
Sub Icon_onLeaveDockArea(Icon As ClsMovableIcon, DockRect As Rect)
   If DockRect = rctTrashCan Then
      ivTrashCan.Visible = False
   End If
End Sub

Quand l'icône est ancrée (événement onDock), je détruis l'icône si sa zone d'ancrage est la corbeille, sinon je cherche la zone d'ancrage dans la liste afin de la supprimer :
B4X:
Sub Icon_onDock(Icon As ClsMovableIcon, DockRect As Rect)
   pnlBottom.Visible = True

   If DockRect = rctTrashCan Then
      'Destroys the trashed icon
      For i = 0 To Icons.Length - 1
         If Icon = Icons(i) Then
            Icons(i) = Null
            Exit
         End If
      Next
      Icon.Destroy
      ivTrashCan.Visible = False
      Return
   End If

   'Removes the docking area from the list of available DA
   Dim daRect As Rect
   For i = 0 To lstDockingAreas.Size - 1
      daRect = lstDockingAreas.Get(i)
      If daRect.Left = DockRect.Left AND daRect.Top = DockRect.Top Then
         lstDockingAreas.RemoveAt(i)
         Exit
      End If
   Next
End Sub

Enfin, quand l'icône quitte sa zone d'ancrage (événement onUndock), j'ajoute cette zone à la fin de ma liste des zones disponibles :
B4X:
Sub Icon_onUndock(Icon As ClsMovableIcon)
   'Adds the quitted area to the list of available DA
   pnlBottom.Visible = False
   Dim daRect As Rect
   daRect.Initialize(Icon.getLeft, Icon.getTop, Icon.getLeft + IconWidth, Icon.getTop + IconHeight)
   lstDockingAreas.Add(daRect)
End Sub

Voilà, c'est fini. Cliquez ici pour le code source.

Le puzzle a une problématique différente, même s'il s'agit de déplacer des pièces et de les ancrer quelque part. Tout d'abord, les zones d'ancrage sont propres à chaque pièce. Ensuite, les pièces sont sans ancrage au départ. Elles sont autour du plateau de jeu. Si l'utilisateur en déplace une, mais ne trouve pas sa zone d'ancrage, la pièce doit revenir toute seule sur les bords du plateau. La classe Floating Windows est tout à fait adaptée à ce mode de fonctionnement.

attachment.php


Pour que les fenêtres flottantes se transforment en pièces de puzzle, il faut enlever la barre de titre et le cadre :
B4X:
Tiles(i).EnableTitleBar(False)
Tiles(i).SetBorder(0, 0)
Découper un morceau de l'image complète et l'appliquer sur le fond :
B4X:
bd(i).Initialize(BP.Crop(bmp, SliceWidth * col, SliceHeight * lig, SliceWidth, SliceHeight))
Tiles(i).SetBackground(bd(i))
Rendre le déplacement immédiat après un évènement Touch (en temps normal, la classe essaie de détecter si l'action de l'utilisateur concerne la fenêtre elle-même ou son contenu) :
B4X:
Tiles(i).TouchSlop = 0
Ajouter des bords collants (la pièce viendra se coller au bord si elle est lâchée en dehors de sa zone d'ancrage) :
B4X:
Tiles(i).AddStickyEdge(0, 50%x - Round(SliceWidthDip / 2)) ' Left
Tiles(i).AddStickyEdge(2, 50%x - Round(SliceWidthDip / 2)) ' Right
Tiles(i).StickyAreaColor = Colors.Transparent
Ajouter la zone d'ancrage (avec un minimum de 60% de surface couverte pour valider l'ancrage) :
B4X:
wRect.Initialize(X, Y, X + SliceWidthDip, Y + SliceHeightDip)
Tiles(i).AddDockingArea(wRect)
Tiles(i).DockingAreaColor = Colors.Transparent
Tiles(i).MinimumCoveredSurfaceForDocking = 60
Éviter que la fenêtre soit déplacée en dehors de l'écran :
B4X:
Tiles(i).StillVisible = Min(SliceWidthDip * EnlargeFactor, SliceHeightDip * EnlargeFactor) + 2dip
Éviter que la fenêtre soit trop petite et difficile à manipuler (taille minimum : 20dip) :
B4X:
Tiles(i).MinimumWidthAfterResize = 20dip
Tiles(i).MinimumHeightAfterResize = 20dip
Demander la réception des événements onDock et onUndock :
B4X:
Tiles(i).SetOnDockListener("Win_Dock")
Tiles(i).SetOnUndockListener("Win_Undock")

Il ne reste plus qu'à faire deux choses :
- disperser les pièces ;
- vérifier à chaque ancrage de pièce si le jeu est terminé (toutes les pièces ont été ancrées).

La dispersion des pièces va être activée par un Timer. Je détermine ainsi combien de temps l'image complète peut être observée avant d'être découpée en pièces et répartie sur les bords de l'écran. Lorsque l'événement Tick se déclenche, j'arrête le Timer, je choisis un emplacement d'arrivée pour chaque pièce (en évitant au maximum le chevauchement des pièces), je les agrandis légèrement pour faciliter leur manipulation (elles seront automatiquement redimensionnées lorsqu'elles s'ancreront) et j'anime leur mouvement vers le bord :
B4X:
Sub Timer_Tick
   Timer1.Enabled = False

   ' Makes the tiles bigger to ease their handling (they will be resized when they dock)
   ' then prepares the animations to scatter the tiles
   Dim NbPositions, NewPosition, DeltaY As Int
   NbPositions = Ceil(NbOfTiles / 2)
   For i = 0 To NbOfTiles - 1
      Tiles(i).Resize(Tiles(i).getWidth * EnlargeFactor, Tiles(i).getHeight * EnlargeFactor)
      Dim PositionIsFree As Boolean
      Do Until PositionIsFree
         NewPosition = (Rnd(0, NbPositions) * (90%y - Tiles(i).getHeight) / (NbPositions - 1)) + 5%y
         PositionIsFree = True
         For j = 0 To i - 1
            ' The tile position must be unique (avoids tiles completely hidden by other tiles)
            If j Mod 2 = i Mod 2 Then
               If NewPosition = Tiles(j).getTag + Tiles(j).getTop Then
                  PositionIsFree = False
                  Exit
               End If
            End If
         Next
      Loop
      DeltaY = NewPosition - Tiles(i).getTop
      Tiles(i).SetTag(DeltaY)
      If i Mod 2 = 0 Then
         Anim(i).InitializeTranslate("Anim", 0, 0, -Tiles(i).getLeft, DeltaY)
      Else
         Anim(i).InitializeTranslate("Anim", 0, 0, 100%x - Tiles(i).getWidth - Tiles(i).getLeft, DeltaY)
      End If
   Next

   ' Starts the animations
   For i = 0 To NbOfTiles - 1
      Anim(i).Duration = 700
      Anim(i).Start(Tiles(i).AsPanel)
   Next
End Sub
J'ai stocké la quantité de déplacement vertical de chaque pièce (DeltaY) dans sa propriété Tag car le problème de toutes les tween animations, c'est qu'elles ne déplacent pas vraiment la vue qu'elles animent. En fin d'animation, si on ne positionne pas la vue là où elle se trouve visuellement, elle réapparaît à sa position de départ (en réalité, elle n'a pas bougé). Avec ma valeur stockée, je peux positionner ma vue au bon endroit :
B4X:
Sub Anim_AnimationEnd
   ' Moves the tiles to their final position (sticked to an edge)
   For i = 0 To NbOfTiles - 1
      If Sender = Anim(i) Then
         If i Mod 2 = 0 Then
            Tiles(i).Move(0, Tiles(i).getTop + Tiles(i).getTag, True)
         Else
            Tiles(i).Move(100%x - Tiles(i).getWidth, Tiles(i).getTop + Tiles(i).getTag, True)
         End If
         Exit
      End If
   Next
End Sub
Je finis mon programme en écrivant les gestionnaires des événements onDock et onUndock. Si la pièce est ancrée, je vérifie si le jeu est terminé. Si c'est le cas, je redéclenche le Timer de dispersion. Si la pièce quitte sa zone d'ancrage (ça n'a pas d'intérêt pour le jeu et ça pourrait être interdit, mais ici j'ai prévu le cas), la pièce est agrandie à nouveau :
B4X:
Sub Win_Dock(Window As ClsFloatingWindow, DockRect As Rect)
   ' Checks if all tiles are docked
   For i = 0 To NbOfTiles - 1
      If Not(Tiles(i).IsDocked) Then Return
   Next
   ToastMessageShow("Well done!", False)
   Timer1.Interval = TimeBeforeScattering * 2
   Timer1.Enabled = True
End Sub
Sub Win_Undock(Window As ClsFloatingWindow)
   Window.Resize(Window.getWidth * EnlargeFactor, Window.getHeight * EnlargeFactor)
End Sub

Cliquez ici pour le code source, et là pour une variante qui vous permettra de jouer avec vos propres photos.

3ème partie : quelques liens pour mettre du mouvement dans vos interfaces

Le tutoriel sur l'animation des vues :
http://www.b4x.com/forum/basic4andr...ls/6967-android-views-animation-tutorial.html

La bibliothèque AnimationPlus :
http://www.b4x.com/forum/additional-libraries-classes-official-updates/21313-lib-animationplus.html

L'animation de vues avec l'Universal Tween Engine :
http://www.b4x.com/forum/additional...niversal-tween-engine-animate-your-views.html

La bibliothèque ICOSImageAnimator :
http://www.b4x.com/forum/additional...e-my-new-library-icosimageanimator-1-0-a.html

La bibliothèque GifDecoder:
Basic4android - GifDecoder

Une bibliothèque d'animation pour simuler une page que l'on tourne :
http://www.b4x.com/forum/additional...ew-library-providing-page-turn-animation.html

Un exemple de transition personnalisée entre activités :
http://www.b4x.com/forum/basic4andr...65-custom-transitions-between-activities.html

Le tutoriel pour faire des Live Wallpapers :
http://www.b4x.com/forum/basic4andr...ls/12605-android-live-wallpaper-tutorial.html

La bibliothèque TouchImageView :
http://www.b4x.com/forum/additional-libraries-classes-official-updates/15616-touchimageview.html

Les bibliothèques et classes d'OpenGL:
http://www.b4x.com/forum/additional-libraries-classes-official-updates/9036-opengl-library.html
http://www.b4x.com/forum/additional-libraries-classes-official-updates/9262-opengl-2-0-library.html
http://www.b4x.com/forum/additional...s/19324-class-opengl-es-2d-image-library.html

La bibliothèque ABPhysicsEngine :
http://www.b4x.com/forum/additional...es/10406-abphysicsengine-library-v0-10-a.html

L'auteur: Frédéric Leneuf-Magaud. Je suis développeur professionnel depuis le début des années 90 et j'ai conçu ou participé à une centaine d'applications. Je travaille actuellement pour l'administration française dans une équipe de supervision de serveurs. Le développement sous Android fait partie de mes loisirs.
 

Attachments

  • Ginger+Puzzle.jpg
    Ginger+Puzzle.jpg
    81.6 KB · Views: 1,432
  • Ginger2.jpg
    Ginger2.jpg
    38 KB · Views: 1,383
  • Ginger3.jpg
    Ginger3.jpg
    67.1 KB · Views: 1,444
  • Puzzle2.png
    Puzzle2.png
    82.6 KB · Views: 1,397
  • GingerHomeScreen.zip
    67 KB · Views: 773
  • Puzzle.zip
    193.8 KB · Views: 728
  • Puzzle2.zip
    271.1 KB · Views: 792
Last edited:

Roger Garstang

Well-Known Member
Licensed User
Longtime User
1) Catch Notes is an application protected by a copyright.

If that is the case then even doing the tutorial infringed on the copyright and was illegal...not to mention posting pictures of it proving it was done. I'm not saying you are a criminal or anything, but Forums and Social Media have blurred the lines a little lately. They are still there though and I'm pretty sure if the developer came here and saw their work copied they'd be pretty ticked. Probably would have been best to post the app picture as an example then do something different in looks and function. Even the use of a circular popup button may be copyrighted/patented by them (If Apple can get round squares who knows). I would have made an app with maybe a Triangle Button and different color/shape of panels.
 

Informatix

Expert
Licensed User
Longtime User
If that is the case then even doing the tutorial infringed on the copyright and was illegal...not to mention posting pictures of it proving it was done. I'm not saying you are a criminal or anything, but Forums and Social Media have blurred the lines a little lately. They are still there though and I'm pretty sure if the developer came here and saw their work copied they'd be pretty ticked. Probably would have been best to post the app picture as an example then do something different in looks and function. Even the use of a circular popup button may be copyrighted/patented by them (If Apple can get round squares who knows). I would have made an app with maybe a Triangle Button and different color/shape of panels.

Nothing is illegal here. I don't publish one line of the original code (it's in Java, so it's useless for most of us). I don't use any decompiled pictures (and I can prove it to the original authors if needed), I simply copy/paste them from screenshots in hi-res. I don't reuse these pictures in another software and their origin is clearly stated; I don't pretend to be their author. I can't see what's the prejudice for the authors and why they would sue me. I don't ask for money, I don't sell a similar app, I don't blame their work, I don't prevent customers from buying their software, I don't steal anything coming from the APK or another source, their pictures are not used in another piece of art, with another design, I don't reveal patented algorithms, etc.
My goal is to recreate the same layout and functioning with B4A for educational purposes. I thought it was clear since the first tutorial.
Now, if Erel asks for it, I can stop.
 

Roger Garstang

Well-Known Member
Licensed User
Longtime User
Don't take it the wrong way. I love the tutorials, I was just clarifying the crazy laws. If the developer was a jerk like Apple and others that are sue happy they could. Electronic Media like images, video, and applications get even crazier with the laws. Monetary gain isn't always a requirement either-

1. If I have a photographer take my picture and I scan the prints to digital (much like you say you get a screenshot) and take them up to a photo lab in town and make prints I just violated the photographer's copyright. I didn't make any money...in fact I had to pay to get them printed. I personally think this is dumb even though I do photography and video on the side. Half the time when I need prints of my own work it looks too good and places won't develop telling me I need copyright release. I also feel that if I pay a photographer to do work which is often $100+ minimum for them to push a button that I own those photos. When I do photos or video for people I give them all rights to it, but others don't work that way.

2. The other common thing is movies/audio. You can copy/pirate videos or CDs for your own use only and not sell them and still violate copyright laws. Now if you actually own the video/CD you are sometimes able to make a backup copy in case yours breaks, but often the act of circumventing any protection is illegal. And you are supposed to only have rights for private/family viewing too, not for the whole neighborhood, etc. They even take this as far as not being able to play radio in retail establishments like you are required to pay for that and the music was used to sell your product. Many places have subscription music just because of this.

Many early TV shows had live bands and are not released to DVD because it would be too expensive to pay every band member (Even the loser that played the triangle or slapped the cymbals together twice in the whole performance) their royalties.

All the laws are dumb, but all I was saying is you can't have one without the other. If giving the example out is violating copyright then so was making it. It is no different than someone pirating a movie then someone asking for a copy and being told it was wrong...one may seem worse than the other, but with the crazy laws they are all the same to money hungry lawyers.
 

Informatix

Expert
Licensed User
Longtime User
I think there's nothing in the copyright laws that could be used against my work. I said that because I read them a few years ago. And to sue someone, a prejudice must exist. Where's the prejudice here ? On the contrary, in your examples, the prejudice is obvious.
You should read this article on the Fair Use.
 

Roger Garstang

Well-Known Member
Licensed User
Longtime User
Ok, under fair use (Assuming Wikipedia got it all right) you are teaching/researching/commenting in your tutorial. Posting Example code/project files would also be doing that same thing. Anyone like myself getting it would be doing the same thing as well as possibly archiving (Which is why I asked since I like having a complete example I can store for reference later and not have to piece together clips or visit the forum all day long searching for something I saw weeks ago that does exactly what I need). You also use English in your code which would be easier than translating and I learn more by example than reading a description. You had requested the same thing When I published my View Manager class here and I kept with it and update both the code file and the code in the text.

In this scenerio it would then be up to the user getting the files to honor copyrights and use it only for example/teaching and not put the files in their app. In telling me it would be breaking the copyright to give me an example it was either saying I wasn't responsible enough to do this or that the tutorial itself was breaking the copyright too...that is why I commented.

Now I just saw in your other thread that you released your last free work. Hopefully it wasn't due to this, but that shows that you find your time of value. If that is why you didn't want to release the full code for this it would have been better to just say that to not cause confusion with copyrights.
 
Last edited:

Informatix

Expert
Licensed User
Longtime User
Posting Example code/project files would also be doing that same thing.

I don't think so because this implies the distribution of parts of a copyrighted work, and this distribution is absolutely not needed by the aim pursued. First, the original author could think that I encourage to make clones of his work. Second, the file to download would be separate from the tutorial, and thus would lose its meaning.
Illustrating how to do some things by reproducing a known work (as many teachers do around the world) is very different from giving all the tools to reproduce that thing. And frankly, on this matter, I don't want to explore the fringes of the law.

In this scenerio it would then be up to the user getting the files to honor copyrights and use it only for example/teaching and not put the files in their app.

I'm not sure that I want to take this risk.

Hopefully it wasn't due to this, but that shows that you find your time of value.

Was it not the case before ? ;-)

If that is why you didn't want to release the full code for this it would have been better to just say that to not cause confusion with copyrights.

??? That's not related at all.
 
Top