Android Tutorial EXAMPLE - Drag a panel of buttons or EditText

Here is a simple example of how to support dragging a panel of buttons or other views like EditText around the screen.

I extracted the code from one of my apps and added detailed comments. It works by placing a transparent panel over the screen and then manipulating the views underneath. The demo screen format was taken from one of Erel's examples.

It uses the Reflection library to send events to other views and the IME library for the EditText box to show the SoftKeyboard.
I have tested the example on Android 2.2, 2.3 and 4.03.

I did not include the LongClick code as it makes the example less clear. Other types of views can also be easily added.

B4X:
' Demo program to show a draggable panel with buttons and edittext
' bluejay July,2012
' uses Reflection library and IME library

Sub Process_Globals 'These global variables will be declared once when the application starts.
'These variables can be accessed from all modules.
End Sub
Sub Globals 'These global variables will be redeclared each time the activity is created.
'These variables can only be accessed from this module.
    Dim TransparentPanelOnTop As Panel
   
   Dim BottomPanel As Panel                           ' views that will be made draggable
    Dim Button1     As Button
    Dim Button2     As Button
    Dim Label1      As Label
   Dim TxtEdit     As EditText
   
    Dim StartX      As Float                           ' used only for panel dragging
    Dim StartY      As Float
   Dim LastX       As Float
   Dim LastY       As Float
   Dim MoveDelta   As Float
   Dim pressed     As Boolean
   
   Dim SoftKey      As IME                              ' only required if using EditText
End Sub
Sub Activity_Create(FirstTime As Boolean)
    Label1.Initialize("")                              ' define some views to interact with
    Button1.Initialize("Button1")
    Button2.Initialize("Button2")
   TxtEdit.Initialize("dummy")                           ' make sure the EditText gets an event listener
    BottomPanel.Initialize("")
   
   SoftKey.Initialize("")                              ' IME library used to force showing of soft keyboard for the EditText
   
    Label1.Text  = "Waiting for click or drag..."            ' some user instructions
    Button1.Text = "I am a Button1 CLICK ME"
    Button2.Text = "I am a Button2 CLICK ME"
   TxtEdit.Hint = "type something here"
   
   Activity.AddView(BottomPanel,0,0,100%x,100%y)               ' build the User Interface
   BottomPanel.AddView(Label1,10,10,Activity.Width - 20,60)
    BottomPanel.AddView(Button1,10,100,Activity.Width - 20,60)
    BottomPanel.AddView(Button2,10,180,Activity.Width - 20,60)
    BottomPanel.AddView(TxtEdit,10,260,Activity.Width - 20,60)

    TransparentPanelOnTop.Initialize("TransparentPanelOnTop")   ' now put a transparent panel on top of everything
    Activity.AddView(TransparentPanelOnTop,0,0,100%x,100%y)     
    TransparentPanelOnTop.Color = Colors.Transparent
    TransparentPanelOnTop.BringToFront
End Sub
Sub Activity_Resume

End Sub
Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub DeltaXY(x As Float, y As Float) As Float               ' support routine for dragging
  Return Abs(StartX - x) + Abs(StartY - y)                 ' does not need to be the actual distance moved
End Sub

Sub TransparentPanelOnTop_Touch (Action As Int, x As Float, y As Float)
  'Note: always sends the touch action to TransparentPanelOnTop
  'Note: x and y are not integers
   
   Select Action
   Case Activity.ACTION_DOWN                                       ' everything starts with this action
      SendAction(BottomPanel,x,y,"DOWN")                          ' PRESS a view   eg highlight a button
        StartX    = x
        StartY    = y
      LastX     = x
      LastY     = y
      MoveDelta = 0
      pressed   = True
   
   Case Activity.ACTION_MOVE
       If pressed Then                                          ' check to see if we are really moving
        MoveDelta = Max(MoveDelta, DeltaXY(x,y))   
        If MoveDelta > 1dip Then
          SendAction(BottomPanel,StartX,StartY,"UP")                   ' un-press the view since we are now moving
         pressed = False                                       ' skip this test for subsequent moves
        End If
      End If
      BottomPanel.Left = BottomPanel.Left + Round(x) - Round(LastX)       ' DRAG the panel left or right
      BottomPanel.Top  = BottomPanel.Top  + Round(y) - Round(LastY)       ' DRAG the panel up or down
      LastX  = x                                               ' these MUST be floating point values
      LastY  = y
      
   Case Activity.ACTION_UP
      SendAction(BottomPanel,x,y,"UP")                             ' make sure view not left in pressed state
      pressed   = False      
      If MoveDelta <= 1dip Then SendAction(BottomPanel,x,y,"CLICK")        ' CLICK if we have not moved

   End Select
End Sub

Sub SendAction(P As Panel, x As Int, y As Int, ActionToSend As String)
' Send a Press or Unpress or Click or Focus to a child view on a given panel
' uses reflection library (and IME library for the EditText)
' Note: x,y cordinates automatically converted to integers 

    Dim i, newx, newy As Int
   
   newx = x - P.Left                                   ' translate Top panel x,y coords to Bottom panel coords 
   newy = y - P.Top                                    ' assumes the top panel does not move and positioned at 0,0
   
   For i = P.NumberOfViews - 1 To 0 Step -1                ' start with last view added to the parent panel in case there are overlapping views
      Dim v As View       
      v = P.GetView(i) 
      If (v.Left < newx) AND ((v.Left + v.Width) > newx) AND (v.top < newy) AND ((v.top + v.Height) > newy) Then     
         Dim r As Reflector          
         r.Target = v 
         
         Select ActionToSend
           Case "DOWN"
             r.runmethod2("setPressed","True","java.lang.boolean")
           Case "UP"
             r.runmethod2("setPressed","False","java.lang.boolean")
           Case "CLICK"
             If v Is EditText Then
              r.runmethod2("setFocusableInTouchMode","True","java.lang.boolean")  ' only required on some devices
'              r.RunMethod("requestFocusFromTouch")                          ' SoftKey below will also request focus
              SoftKey.ShowKeyboard(v)                                   ' force the SoftKeyboard to show
            Else
              r.RunMethod("performClick")
            End If
         End Select   
         
         Exit                                      ' exit the for loop - only apply the action to one view on the panel      
      End If   
   Next
End Sub

'These buttons and labels are on the BottomPanel underneath the TransparentPanelOnTop
Sub Button1_Click
    Label1.Text = "You Clicked Button1 with positions : X = " & StartX & " Y = " & StartY
End Sub
Sub Button2_Click
    Label1.Text = "You Clicked Button2 with positions : X = " & StartX & " Y = " & StartY
End Sub

I hope this will prove useful to both newcomers and existing forum users.

bluejay
 

Attachments

  • PanelTouchDrag.zip
    7.3 KB · Views: 1,186
  • PanelTouchDrag.png
    PanelTouchDrag.png
    23.1 KB · Views: 1,581

Informatix

Expert
Licensed User
Longtime User
I added two things to my code to improve it a little bit:
MoveInProgress (becomes true when the move begins so we have not to check again the move size; the move becomes smoother)
MarginStillVisible (defines the margin that cannot disappear; thus the panel has always a part of it visible).
 

Attachments

  • PTD2.ZIP
    8.1 KB · Views: 837

bluejay

Active Member
Licensed User
Longtime User
Thanks Informatix for your interesting example, I will give that approach a try.

A side effect of your current solution is that if a drag starts over the edittext then softkeyboard always pops up. It should not popup unless you want type something. Especially if there are lots of edittext boxes on the screen.

Getting the softkeyboard to pop up when required was the opposite problem in my solution due to issue with getfocus. Could you be more specific about the difficulties you had entering text?

I found that detecting when a moved has started proved tricky to make reliable as it is affected by both device resolution and how noisy the device touch electronics is and hence seems very device dependent.

My example was actually from a Sliding Panel app so there were lots and lots of views that had to be moveable. So I am concerned about how many SetListeners they may be required.

bluejay
 

Informatix

Expert
Licensed User
Longtime User
A side effect of your current solution is that if a drag starts over the edittext then softkeyboard always pops up. It should not popup unless you want type something.

It doesn't pop up on my device if not wanted to. On the emulator, it doesn't pop up either. But I found with the emulator that it could happen if the EditText is already focused (not strange, after all). OK, I won't explain to you how you can easily circumvent this issue. Your code proves you know it.

Could you be more specific about the difficulties you had entering text?

I have to click on buttons two or three times to really do a click. Same thing for entering text. It's even worse with the EditText because the keyboard does not always show, even when it should be. For the buttons, it's because your minimal move is too small (1dip is not enough). For the EditText, it's just a part of the explanation. The other part is the used method. Let the system do its job.

I found that detecting when a moved has started proved tricky to make reliable as it is affected by both device resolution and how noisy the device touch electronics is and hence seems very device dependent.

To keep my code simple, I didn't want to put this in it, but there's a value set on your device that you should read and set as the minimal move size: TouchSlop. To get it, call getScaledTouchSlop with the reflector library.

So I am concerned about how many SetListeners they may be required.

As many as you need and as many as the memory can support them. I never put more than 200 objects on the same page, so I cannot say where's the limit (it depends also on the views content). Every view has many listeners, so you will face the issue anyway. In my solution, I just redirect the touch event, I don't create something new.
 

Informatix

Expert
Licensed User
Longtime User
Your intent was educational, so I tried to be educational also and I created four versions of PanelTouchDrag (commented) to show how one can handle the issue with focused views. Each version is a different solution. From very simple to less simple.
If there are other solutions (without labyrinthine code), I'm interested.

EDIT : I forgot to mention that my preferred solution is the third because it is universal. It works with any focused view, not only EditText, and you can choose the view receiving the focus after the move (the former focused view or the new touched view).
 

Attachments

  • PTD3.zip
    19.4 KB · Views: 1,186
Last edited:

bluejay

Active Member
Licensed User
Longtime User
This is really good. Thanks for taking the time to explain this approach and sharing your knowledge of Android.

I did not know about 'getScaledTouchSlop'. As you say the Android OS itself has probably encountered most of the issues, just need to find out how use Android to do the work.

I knew 1dip was on the low side but it worked well on an ASUS Transformer, Galaxy S and my emulator as does the softkeyboard popup. However it is clear my approach is too device dependent for commercial app.

bluejay
 

bluejay

Active Member
Licensed User
Longtime User
Ok, I have now had a chance to try the four versions and this is my feedback:

Solution 1: I agree this is not a good user experience
Solution 2: Not preferred - clears the focus - an action not expected by user
Solution 3: Not preferred - changes the focused view - an action not expected by user
Solution 4: this has the correct behavior ie dragging does not change focus

The design principal here should be that a drag does not change the state of the underlying views. For example if I am editing and want to move the screen slightly then the drag should not affect the edit.

Changing the focus when the user just want the reposition the screen is an un-intended action. It would also be annoying if the edittext has OnFocusChanged events in use.

My preference if solution 4 although it is a bit convoluted in the code.

bluejay
 

Informatix

Expert
Licensed User
Longtime User
Solution 3: Not preferred - changes the focused view - an action not expected by user
Since you give the focus to a child at the end of the move, you can give it to the former focused view. You can also memorize the SelectionStart of the EditText when the move begins and reset it to the memorized value when the move ends. So the user does not see any difference between before and after.

My preference if solution 4 although it is a bit convoluted in the code.
If you read the source code of TextView, you will see that's the "recommended" method.
The code is quite simple. You memorize the InputType value when Action = 0 (DOWN). During the move, it is set to None. You reset it when Action = 1 (UP).
 

bluejay

Active Member
Licensed User
Longtime User
Sorry, I thought one of your previous posts said you preferred solution 3. My mistake.

I had a quick look at the source code of TextView (8,530 lines!). I see they also included ACTION.CANCEL in the OnTouchEvent. Is this something we should include here or is only for gestures?

bluejay
 

Informatix

Expert
Licensed User
Longtime User
Sorry, I thought one of your previous posts said you preferred solution 3. My mistake.

I had a quick look at the source code of TextView (8,530 lines!). I see they also included ACTION.CANCEL in the OnTouchEvent. Is this something we should include here or is only for gestures?

bluejay

I still prefer the solution 3.

ACTION_CANCEL : this is generated when a parent view intercepts the touch event and traps it. The children are informed of the event, but with this action code.
Put your panel in a ScrollView, touch it and move your finger out of the panel, you should receive this code. You can block an interception with requestDisallowInterceptTouchEvent.
 

bluejay

Active Member
Licensed User
Longtime User
Thanks Informatix. Sorry for the delayed response, my day job has been getting in the way.

bluejay
 
Top