[Wish] StackView support

Inman

Well-Known Member
Licensed User
Longtime User
Introduced in Android 3.0, stackView lets you create a set of stacked views that look like a deck of cards, which allows the user to flip off the item in the front, to the back of the stack. It is mostly used to create a type of widgets known as StackWidgets. Here is a screenshot of the StackWidget of the official YouTube app.

stacks.jpg


It was originally available on tablets only but with Android 4.0's release, it works on phones as well. Since over 56% of Android users are on 4.0+, I hope B4A will add this view soon.
 

warwound

Expert
Licensed User
Longtime User
How about this for a quick first attempt...

StackView
Version: 0.01 (alpha)
  • StackView
    Events:
    • GetView (Panel1 As Panel, Position As Int) As Panel
    Methods:
    • BringToFront
    • GetDisplayedChild As Int
    • GetStackViewAdapter As StackViewAdapter
    • Initialize (EventName As String)
    • Invalidate
    • Invalidate2 (arg0 As Rect)
    • Invalidate3 (arg0 As Int, arg1 As Int, arg2 As Int, arg3 As Int)
    • IsInitialized As Boolean
    • RemoveView
    • RequestFocus As Boolean
    • SendToBack
    • SetBackgroundImage (arg0 As Bitmap)
    • SetDisplayedChild (Position As Int)
    • SetLayout (arg0 As Int, arg1 As Int, arg2 As Int, arg3 As Int)
    • ShowNext
    • ShowPrevious
    Properties:
    • Background As Drawable
    • Color As Int [write only]
    • Enabled As Boolean
    • Height As Int
    • Left As Int
    • Tag As Object
    • Top As Int
    • Visible As Boolean
    • Width As Int
  • StackViewAdapter
    Methods:
    • GetCount As Int
    • IsInitialized As Boolean
    • SetCount (Count As Int)

B4X:
Sub Process_Globals
   Dim DisplayedChild As Int=0
End Sub

Sub Globals
   Dim StackView1 As StackView
End Sub

Sub Activity_Create(FirstTime As Boolean)

   Activity.AddMenuItem("Previous", "MenuItem")
   Activity.AddMenuItem("Next", "MenuItem")
   Activity.Color=Colors.Blue
   
   StackView1.Initialize("StackView1")
   StackView1.GetStackViewAdapter.SetCount(5)
   StackView1.SetDisplayedChild(DisplayedChild)
   
   Activity.AddView(StackView1, 20dip, 20dip, 260dip, 140dip)
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)
   DisplayedChild=StackView1.GetDisplayedChild
End Sub

Sub MenuItem_Click
   Dim Action As String=Sender
   Select Action
      Case "Next"
         StackView1.ShowNext
      Case "Previous"
         StackView1.ShowPrevious
   End Select
End Sub

Sub StackView1_GetView(Panel1 As Panel, Position As Int) As Panel
   Log("StackView1_GetView")
   If Panel1.IsInitialized=False Then
      Log("Initializing Panel")
      Panel1.Initialize("")
      Panel1.Color=Colors.Gray
   End If
   Dim Label1 As Label
   Label1.Initialize("")
   Label1.Color=Colors.White
   Label1.Text="This is StackView item #"&Position
   Label1.TextColor=Colors.Black
   Panel1.AddView(Label1, 10dip, 10dip, 240dip, 120dip)
   Return Panel1
End Sub

Creates a StackView with 5 child Views.
The Sub StackView1_GetView is called when the StackView needs to draw a child View.
This Sub is passed either:
  • An initialized Panel to recycle.
  • An un-initiaized Panel.
So the Sub must modify an initialized Panel (recycle it) or create a new Panel.

On orientation change the DisplayedChild variable enables the last displayed child to be saved and restored.

Martin.
 
Last edited:

Inman

Well-Known Member
Licensed User
Longtime User
This is great, Martin. You came up with it in no time!

It is working well on my Galaxy Note. One thing I would love to see is that the item that is flipped down (i.e going from item 0 to item 1) going back to the end of the stack so that it continues in a loop. Right now the items go down and you can reach the end of the stack.

Also will it be possible to use it in a widget like in the screenshot above?
 

warwound

Expert
Licensed User
Longtime User
Well after much searching i came to the same conclusion as many other developers - the parameter that defines whether the views are looped in a continuous cycle can only be set if the StackView is defined using an XML layout file.
It seems there is no method to tell the StackView to loop through all views if the StackView is created in code.

So i've created a new method:

Initialize2(EventName As String, LayoutName As String)

An XML layout file named my_stack_view.xml has been created and saved in the Objects\res\layout folder:

B4X:
 <StackView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:animateFirstView="true"
    android:animateLayoutChanges="false"
    android:loopViews="true">
 </StackView>

Just needed to comment out the previous Initialize method and add the new Initialize2 method:

B4X:
'   StackView1.Initialize("StackView1")

StackView1.Initialize2("StackView1", "my_stack_view")

And it works as expected.
I did set android:animateLayoutChanges to "true" and found that the animation on a low power ICS tablet was stuttering, so i changed the attribute to "false".

Then i updated the Sub StackView1_GetView:

B4X:
Sub StackView1_GetView(Panel1 As Panel, Position As Int) As Panel
   Log("StackView1_GetView")
   Log(Panel1)
   Dim Label1 As Label
   If Panel1.IsInitialized=False Then
      Log("Initializing new Panel")
      Panel1.Initialize("")
      Panel1.Color=Colors.Gray
      Label1.Initialize("")
      Label1.Color=Colors.White
      Label1.TextColor=Colors.Black
      Panel1.AddView(Label1, 10dip, 10dip, 240dip, 120dip)
   Else
      Log("Recycling Panel")   '   never gets logged
      Label1=Panel1.GetView(0)
   End If
   Label1.Text="This is StackView item #"&Position
   Return Panel1
End Sub

The previous Sub was not re-using the Label on a recycled Panel, instead it was always adding a new Label (on top of the old Label).
That's the theory - in practice looking at the log i have not seen a Panel being recycled once.
Each time the StackView call's it's getView method it does not pass an existing view to that method to be recycled.
Increasing the number of 'DisplayedChild' from 5 to 25 did not cause any recycling of views.

If the StackView is not going to attempt to recycle any views then i could make the GetView event Sub so that it does not get passed a view to recycle (and the view is always Null or not initialized) and the Sub could return any type of View instead of being hardcoded to always return a Panel.
The GetView Sub could return a WebView, ImageView or any other type of View - much more flexible.
I'll have a look at the source code and try to establish why this view recycling does not happen before i make any changes to the GetView Sub.

As for using a StackView in a homescreen widget.
I've not worked much with homescreen widgets so had to have a look at the tutorials - the ConfigureHomeWidget command requires the name of a B4A layout file in order to create the widget UI.
So unless a StackView can be added to a B4A layout file it will not be possible to use a StackView in a widget - sounds like i should find the time to read up on the latest B4A updates where a custom View can now be used in the B4A Designer!
BUT i am pushed for time over the next few days so will have to hope Erel or someone else is following this thread and can comment on that.

Martin.
 
Last edited:

Inman

Well-Known Member
Licensed User
Longtime User
Cool! I love it. Thank you Martin for fulfilling my request so soon.

I hope someone can make it compatible with widgets.
 

warwound

Expert
Licensed User
Longtime User
I've made some updates to the StackView...

Take a look at this example, it's equivalent of the previously posted example:

B4X:
Sub Process_Globals
   Dim DisplayedChild As Int=0
   Dim ItemCount As Int=0
End Sub

Sub Globals
   Dim StackView1 As StackView
   Dim StackViewPanelAdapter1 As StackViewPanelAdapter
End Sub

Sub Activity_Create(FirstTime As Boolean)
   
   Activity.AddMenuItem3("Add items", "MenuItem", Null, True)
   Activity.AddMenuItem3("Clear items", "MenuItem", Null, True)
   Activity.AddMenuItem3("Previous item", "MenuItem", Null, True)
   Activity.AddMenuItem3("Next item", "MenuItem", Null, True)
   Activity.Color=Colors.Blue
   
   StackView1.Initialize
   '   StackView1.Initialize2("my_stack_view")   '   xml method not used in this example
   
   StackViewPanelAdapter1.Initialize("StackViewPanelAdapter1")
   StackView1.SetAdapter(StackViewPanelAdapter1)
   
   If ItemCount<>0 Then
      StackViewPanelAdapter1.SetCount(ItemCount)
      StackView1.SetDisplayedChild(DisplayedChild)
   End If
   
   '   the new SetEmptyView method seems NOT to work!
   Dim Panel1 As Panel
   Panel1.Initialize("")
   Panel1.Color=Colors.Red
   Dim Label1 As Label
   Label1.Initialize("")
   Label1.Color=Colors.Green
   Label1.Text="Adapter is empty"
   Panel1.AddView(Label1, 0, 0, 240dip, 120dip)
   StackView1.SetEmptyView(Panel1)
   
   Activity.AddView(StackView1, 20dip, 20dip, 260dip, 140dip)
   
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)
   DisplayedChild=StackView1.GetDisplayedChild
End Sub

Sub MenuItem_Click
   Dim Action As String=Sender
   Select Action
      Case "Add items"
         ItemCount=12
         StackViewPanelAdapter1.SetCount(ItemCount)
      Case "Clear items"
         StackViewPanelAdapter1.Clear
         ItemCount=0
      Case "Next item"
         StackView1.ShowNext
      Case "Previous item"
         StackView1.ShowPrevious
   End Select
End Sub

Sub StackViewPanelAdapter1_GetView(Panel1 As Panel, Position As Int) As Panel
   Log("StackView1_GetView")
   Log(Panel1)
   Dim Label1 As Label
   If Panel1.IsInitialized=False Then
      Log("Initializing new Panel")
      Panel1.Initialize("")
      Panel1.Color=Colors.Gray
      Label1.Initialize("")
      Label1.Color=Colors.White
      Label1.TextColor=Colors.Black
      Panel1.AddView(Label1, 10dip, 10dip, 240dip, 120dip)
   Else
      Log("Recycling Panel")
      Label1=Panel1.GetView(0)
   End If
   Label1.Text="This is StackView item #"&Position
   Return Panel1
End Sub

The adapter is now named StackViewPanelAdapter and is an object that needs to be created and initialized with an EventName.
The StackView no longer requires an EventName.

I've introduced a new method SetEmptyView - looks promising and useful BUT so far does not work!

All the logging has shown that not once does the adapter pass a View to it's getView method so that the getView method can recycle that View instead of creating a new View.

So as an experiment i created a new adapter, the StackViewViewAdapter.
If we are not going to waste time writing code to recycle Views when the recycling never happens we can have a much more versatile adapter.
StackViewViewAdapter raises the event GetView(Position As Int) As View.
What's the point is passing a Panel, checking if it is initialized and recycling the Panel if it is initialized when so far not a single Panel is ever passed for recycling?

Look at this example that uses the new StackViewViewAdapter:

B4X:
Sub Process_Globals
   Dim DisplayedChild As Int=0
   Dim ItemCount As Int=0
End Sub

Sub Globals
   Dim StackView1 As StackView
   Dim StackViewViewAdapter1 As StackViewViewAdapter
End Sub

Sub Activity_Create(FirstTime As Boolean)
   
   Activity.AddMenuItem3("Add items", "MenuItem", Null, True)
   Activity.AddMenuItem3("Clear items", "MenuItem", Null, True)
   Activity.AddMenuItem3("Previous item", "MenuItem", Null, True)
   Activity.AddMenuItem3("Next item", "MenuItem", Null, True)
   Activity.Color=Colors.Blue
   
   StackView1.Initialize
   '   StackView1.Initialize2("my_stack_view")   '   xml method not used in this example
   
   StackViewViewAdapter1.Initialize("StackViewViewAdapter1")
   StackView1.SetAdapter(StackViewViewAdapter1)
   
   If ItemCount<>0 Then
      StackViewViewAdapter1.SetCount(ItemCount)
      StackView1.SetDisplayedChild(DisplayedChild)
   End If
   
   '   the new SetEmptyView method seems NOT to work!
   Dim Panel1 As Panel
   Panel1.Initialize("")
   Panel1.Color=Colors.Red
   Dim Label1 As Label
   Label1.Initialize("")
   Label1.Color=Colors.Green
   Label1.Text="Adapter is empty"
   Panel1.AddView(Label1, 0, 0, 240dip, 120dip)
   StackView1.SetEmptyView(Panel1)
   
   Activity.AddView(StackView1, 20dip, 20dip, 260dip, 140dip)
   
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)
   DisplayedChild=StackView1.GetDisplayedChild
End Sub

Sub MenuItem_Click
   Dim Action As String=Sender
   Select Action
      Case "Add items"
         ItemCount=4
         StackViewViewAdapter1.SetCount(ItemCount)
      Case "Clear items"
         StackViewViewAdapter1.Clear
         ItemCount=0
      Case "Next item"
         StackView1.ShowNext
      Case "Previous item"
         StackView1.ShowPrevious
   End Select
End Sub

Sub StackViewViewAdapter1_GetView(Position As Int) As View
   Log("StackView1_GetView")
   Select Position
      Case 0
         Dim Label1 As Label
         Label1.Initialize("")
         Label1.Color=Colors.White
         Label1.TextColor=Colors.Black
         Label1.TextSize=20dip
         Label1.Text="* This is StackView item #0 *"
         Return Label1
      Case 1
         Dim WebView1 As WebView
         WebView1.Initialize("")
         WebView1.LoadUrl("http://google.co.uk/")
         Return WebView1
      Case 2
         Dim EditText1 As EditText
         EditText1.Initialize("")
         EditText1.Text="* This is StackView item #3 *"
         Return EditText1
      Case 3
         Dim ImageView1 As ImageView
         ImageView1.Initialize("")
         ImageView1.Bitmap=LoadBitmap(File.DirAssets, "stacks.jpg")
         ImageView1.Gravity=Gravity.FILL
         Return ImageView1
      Case Else
         '   keep  the compiler happy lol!
         Return Null
   End Select
End Sub

See how the Sub StackViewViewAdapter1_GetView can create an return any View*?
In this example the adapter provides 4 Views to the StacKView: a Label, a WebView, an EditText and an ImageView.

*Any View that extends the B4A ViewWrapper class can be returned by the GetView event Sub.

Both examples and the updated library files are attached.

Martin.
 

Attachments

  • StackView_20130521.zip
    64.5 KB · Views: 291

Inman

Well-Known Member
Licensed User
Longtime User
Hey Martin, just wanted to remind you. When you get time, please work on implementing StackView as a widget.
 
Top