FloatingActionButton Flicker

110 views
Skip to first unread message

nick.kr...@gmail.com

unread,
May 1, 2017, 7:43:03 PM5/1/17
to CodenameOne Discussions
On iOS/Android, clicking the FAB causes the screen to flicker for every action taken. It only happens when the FAB is overlaying a MapContainer. I have the FAB bound to the content pane and the MapContainer is in a center BorderLayout. 

Any ideas?

Shai Almog

unread,
May 2, 2017, 12:37:03 AM5/2/17
to CodenameOne Discussions, nick.kr...@gmail.com
That might be related to misbehavior of the z-ordering support. You are building against the latest and not using something like versioned build right?

nick.kr...@gmail.com

unread,
May 2, 2017, 12:46:06 PM5/2/17
to CodenameOne Discussions, nick.kr...@gmail.com
I guess my maps lib was outdated. However, I'm still getting a quick screen flicker every time I click on the FAB or sub-FAB. I'm assuming I use the Google Maps Android API and Google Maps SDK for iOS and set those appropriate build hints.

Steve had mentioned in a different thread that I was initializing the MapContainer without the static maps api key but trying both ways still produces the screen flicker: https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!msg/codenameone-discussions/clQ-3bQAGJk/IZrLU4JfEAAJ

Here is my init code (which now I can at least debug the map in the simulator, which is great):
if (Display.getInstance().getPlatformName().equals("and")) {
            cnt = new MapContainer(Constants.ANDROID_GMAPS_API_KEY);
        } 
        else if(Display.getInstance().getPlatformName().equals("os")){
            cnt = new MapContainer(Constants.APPLE_GMAPS_API_KEY);
        }
        //for debugging in simulator
        else{
            cnt = new MapContainer(Constants.JS_GMAPS_API_KEY);
        }

I have also tried initializing with cnt = new MapContainer(new GoogleMapsProvider(Constants.STATIC_GMAPS_API_KEY));

Steve Hannah

unread,
May 2, 2017, 12:56:18 PM5/2/17
to codenameone...@googlegroups.com, nick.kr...@gmail.com
Passing those keys at runtime has no effect on the device.  They are used in the simulator only.  The devise keys are set in build hints per the cn1lib instructions.

Post some code and something might jump out at me.

Steve
--
You received this message because you are subscribed to the Google Groups "CodenameOne Discussions" group.
To unsubscribe from this group and stop receiving emails from it, send an email to codenameone-discu...@googlegroups.com.
Visit this group at https://groups.google.com/group/codenameone-discussions.
To view this discussion on the web visit https://groups.google.com/d/msgid/codenameone-discussions/f28295ec-5171-4aa5-83a8-1e69a1e8f249%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
--
Steve Hannah
Web Lite Solutions Corp.

nick.kr...@gmail.com

unread,
May 2, 2017, 1:36:59 PM5/2/17
to CodenameOne Discussions, nick.kr...@gmail.com

MapContainer container = new MapContainer(new GoogleMapsProvider(Constants.ANDROID_GMAPS_API_KEY));

Coord myLocation = new Coord(lat, lon);

container.setCameraPosition(myLocation);

container.zoom(myLocation, 15);

container.setShowMyLocation(true);

FloatingActionButton fab = FloatingActionButton.createFAB(FontImage.MATERIAL_SORT);

fab.createSubFAB(FontImage.MATERIAL_FLIGHT, "");

Container root = LayeredLayout.encloseIn(BorderLayout.center(container), BorderLayout.north(new AutoCompleteTextField()), BorderLayout.south(FlowLayout.encloseBottom(fab)));

this.add(BorderLayout.CENTER, root);

Display.getInstance().callSerially(() -> {

mapContainer.repaint();

});


I do a lot more with the AutoCompleteTextField and FAB, but for the sake of brevity, just using this still produces the screen flicker.


You're saying I don't need to pass in a GoogleMapsProvider with the API string param to the MapContainer for use with Android/iOS devices? If I don't, doesn't that default to OpenMapsStreetProvider then?


On a side note, right now,  I have a few network intensive api calls to my own db to fetch custom markers, which I believe should go in a scheduleBackgroundTask(). So, do I need to use callSerially() for when I add/remove markers to the map container or only when I repaint() or revalidate()? And is repaint() or revalidate() even the correct use for after I add/remove markers?


I'm also wondering if you can move the location of the icon that snaps to your location or if I need to make my own "button."


nick.kr...@gmail.com

unread,
May 2, 2017, 2:03:09 PM5/2/17
to CodenameOne Discussions, nick.kr...@gmail.com
I even downloaded your GoogleMapsTest app from: https://github.com/codenameone/codenameone-google-maps

Netbeans didn't like the import statement "import static com.codename1.ui.ComponentSelector.$;" so I removed it and refactored the code, removed the action listener from the FAB so that clicking the FAB doesn't take you to another form, changed the build hint "codename1.arg.android.xapplication" to match my Android API key, sent it to your build server, and the screen flicker still occurs.

Here's the code for the class:

/*
 * Copyright (c) 2014, Codename One LTD. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.codename1.test.googlemaps;

import com.codename1.components.FloatingActionButton;
import com.codename1.components.InteractionDialog;
import com.codename1.components.ToastBar;
import com.codename1.googlemaps.MapContainer;
import com.codename1.googlemaps.MapContainer.MapObject;
import com.codename1.io.Util;
import com.codename1.maps.BoundingBox;
import com.codename1.maps.Coord;
import com.codename1.maps.MapListener;
import com.codename1.ui.Button;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.EncodedImage;
import com.codename1.ui.FontImage;
import com.codename1.ui.Form;
import com.codename1.ui.Label;
import com.codename1.ui.SideMenuBar;
import com.codename1.ui.TextField;
import com.codename1.ui.events.ActionEvent;
import com.codename1.ui.events.ActionListener;
import com.codename1.ui.geom.Rectangle;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.ui.layouts.FlowLayout;
import com.codename1.ui.layouts.LayeredLayout;
import com.codename1.ui.plaf.Style;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.util.Resources;
import java.io.IOException;

public class GoogleMapsTestApp {

    private static final String HTML_API_KEY = "AIzaSyA4DAd_ErkelZkFeblFHpB-c-jnQwZ151Q";
    private Form current;

    public void init(Object context) {
        try {
            Resources theme = Resources.openLayered("/theme");
            UIManager.getInstance().setThemeProps(theme.getTheme(theme.getThemeResourceNames()[0]));
            Display.getInstance().setCommandBehavior(Display.COMMAND_BEHAVIOR_SIDE_NAVIGATION);
            UIManager.getInstance().getLookAndFeel().setMenuBarClass(SideMenuBar.class);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    MapObject sydney;
    public void start() {
        if (current != null) {
            current.show();
            return;
        }
        Form hi = new Form("Native Maps Test");
        hi.setLayout(new BorderLayout());
        final MapContainer cnt = new MapContainer();
        //final MapContainer cnt = new MapContainer();
        cnt.setCameraPosition(new Coord(-26.1486233, 28.67401229999996));//this breaks the code //because the Google map is not loaded yet
        cnt.addMapListener(new MapListener() {

            @Override
            public void mapPositionUpdated(Component source, int zoom, Coord center) {
                System.out.println("Map position updated: zoom="+zoom+", Center="+center);
            }
            
        });
        
        cnt.addLongPressListener(e->{
            System.out.println("Long press");
            ToastBar.showMessage("Received longPress at "+e.getX()+", "+e.getY(), FontImage.MATERIAL_3D_ROTATION);
        });
        cnt.addTapListener(e->{
            ToastBar.showMessage("Received tap at "+e.getX()+", "+e.getY(), FontImage.MATERIAL_3D_ROTATION);
        });
        
        int maxZoom = cnt.getMaxZoom();
        System.out.println("Max zoom is "+maxZoom);
        Button btnMoveCamera = new Button("Move Camera");
        btnMoveCamera.addActionListener(e->{
            cnt.setCameraPosition(new Coord(-33.867, 151.206));
        });
        Style s = new Style();
        s.setFgColor(0xff0000);
        s.setBgTransparency(0);
        FontImage markerImg = FontImage.createMaterial(FontImage.MATERIAL_PLACE, s, 3);
        
        Button btnAddMarker = new Button("Add Marker");
        btnAddMarker.addActionListener(e->{
           
            cnt.setCameraPosition(new Coord(41.889, -87.622));
            cnt.addMarker(EncodedImage.createFromImage(markerImg, false), cnt.getCameraPosition(), "Hi marker", "Optional long description", new ActionListener() {
                public void actionPerformed(ActionEvent evt) {
                    System.out.println("Bounding box is "+cnt.getBoundingBox());
                    ToastBar.showMessage("You clicked the marker", FontImage.MATERIAL_PLACE);
                }
            });
            
        });
        
        Button btnAddPath = new Button("Add Path");
        btnAddPath.addActionListener(e->{
            
            cnt.addPath(
                    cnt.getCameraPosition(),
                    new Coord(-33.866, 151.195), // Sydney
                    new Coord(-18.142, 178.431),  // Fiji
                    new Coord(21.291, -157.821),  // Hawaii
                    new Coord(37.423, -122.091)  // Mountain View
            );
        });
        
        Button panTo = new Button("Pan To");
        panTo.addActionListener(e->{
            //bounds.extend(new google.maps.LatLng('66.057878', '-22.579047')); // Iceland
            //bounds.extend(new google.maps.LatLng('37.961952', '43.878878')); // Turkey
            Coord c1 = new Coord(49.0986192, -122.6764454);
            Coord c2 = new Coord(49.2577142, -123.1941149);
            //Coord center = new Coord(c1.getLatitude()/2 +  c2.getLatitude() / 2, c1.getLongitude()/2 + c2.getLongitude()/2 );
            Coord center = new Coord(49.1110928, -122.9414646);
            
            float zoom = cnt.getZoom();
            
            boolean[] finished = new boolean[1];
            cnt.addMapListener(new MapListener() {

                @Override
                public void mapPositionUpdated(Component source, int zoom, Coord c) {
                    
                    if (Math.abs(c.getLatitude() - center.getLatitude()) > .001 || Math.abs(c.getLongitude() - center.getLongitude()) > .001) {
                        return;
                    }
                    finished[0] = true;
                    synchronized(finished) {
                        final MapListener fthis = this;
                        Display.getInstance().callSerially(()->{
                            cnt.removeMapListener(fthis);
                        });
                        finished.notify();
                    }
                    
                }
                
            });
            cnt.zoom(center, (int)zoom);
            while (!finished[0]) {
                Display.getInstance().invokeAndBlock(()->{
                    while (!finished[0]) {
                        Util.wait(finished, 100);
                    }
                });
            }
            BoundingBox box = cnt.getBoundingBox();
            if (!box.contains(c1) || !box.contains(c2)) {
                while (!box.contains(c1) || !box.contains(c2)) {
                    if (!box.contains(c1)) {
                        System.out.println("Box "+box+" doesn't contain "+c1);
                    }
                    if (!box.contains(c1)) {
                        System.out.println("Box "+box+" doesn't contain "+c2);
                    }
                    zoom -= 1;
                    final boolean[] done = new boolean[1];
                    
                    final int fzoom = (int)zoom;
                    cnt.addMapListener(new MapListener() {

                        @Override
                        public void mapPositionUpdated(Component source, int zm, Coord center) {
                            
                            if (zm == fzoom) {
                                final MapListener fthis = this;
                                Display.getInstance().callSerially(()->{
                                    cnt.removeMapListener(fthis);
                                });
                                
                                done[0] = true;
                                synchronized(done) {
                                    done.notify();
                                }
                            }
                        }
                        
                    });
                    cnt.zoom(center, (int)zoom);
                    while (!done[0]) {
                        Display.getInstance().invokeAndBlock(()->{
                            while (!done[0]) {
                                Util.wait(done, 100);
                            }
                        });
                    }
                    box = cnt.getBoundingBox();
                    System.out.println("Zoom now "+zoom);
                    
                }
            } else if (box.contains(c1) && box.contains(c2)) {
                while (box.contains(c1) && box.contains(c2)) {
                    zoom += 1;
                    final boolean[] done = new boolean[1];
                    
                    final int fzoom = (int)zoom;
                    cnt.addMapListener(new MapListener() {
                        public void mapPositionUpdated(Component source, int zm, Coord center)  {
                            if (zm == fzoom) {
                                final MapListener fthis = this;
                                Display.getInstance().callSerially(()->{
                                    cnt.removeMapListener(fthis);
                                });
                                done[0] = true;
                                synchronized(done) {
                                    done.notify();
                                }
                            }
                        }
                    });
                    cnt.zoom(center, (int)zoom);
                    while (!done[0]) {
                        Display.getInstance().invokeAndBlock(()->{
                            while (!done[0]) {
                                Util.wait(done, 100);
                            }
                        });
                    }
                    box = cnt.getBoundingBox();
                    
                }
                zoom -= 1;
                cnt.zoom(center, (int)zoom);
                cnt.addTapListener(null);
            }
            
        });
        
        Button testCoordPositions = new Button("Test Coords");
        testCoordPositions
                .addActionListener(e->{
                    Coord topLeft = cnt.getCoordAtPosition(0, 0);
                    System.out.println("Top Left is "+topLeft+" -> "+cnt.getScreenCoordinate(topLeft) +" Should be (0,0)");
                    Coord bottomRight = cnt.getCoordAtPosition(cnt.getWidth(), cnt.getHeight());
                    System.out.println("Bottom right is "+bottomRight+" -> "+cnt.getScreenCoordinate(bottomRight) + " Should be "+cnt.getWidth()+", "+cnt.getHeight());
                    Coord bottomLeft = cnt.getCoordAtPosition(0, cnt.getHeight());
                    System.out.println("Bottom Left is "+bottomLeft+" -> "+cnt.getScreenCoordinate(bottomLeft) + " Should be 0, "+cnt.getHeight());
                    Coord topRight = cnt.getCoordAtPosition(cnt.getWidth(), 0);
                    System.out.println("Top right is "+topRight + " -> "+cnt.getScreenCoordinate(topRight)+ " Should be "+cnt.getWidth()+", 0");
                    Coord center = cnt.getCoordAtPosition(cnt.getWidth()/2, cnt.getHeight()/2);
                    System.out.println("Center is "+center+" -> "+cnt.getScreenCoordinate(center)+", should be "+(cnt.getWidth()/2)+", "+(cnt.getHeight()/2));
                    EncodedImage encImg = EncodedImage.createFromImage(markerImg, false);
                    cnt.addMarker(encImg, topLeft,"Top Left", "Top Left", null);
                    cnt.addMarker(encImg, topRight, "Top Right", "Top Right", null);
                    cnt.addMarker(encImg, bottomRight, "Bottom Right", "Bottom Right", null);
                    cnt.addMarker(encImg, bottomLeft, "Bottom Left", "Bottom Left", null);
                    cnt.addMarker(encImg, center, "Center", "Center", null);
                    
                    
                })
                ;
        
       
        
        
        Button btnClearAll = new Button("Clear All");
        btnClearAll.addActionListener(e->{
            cnt.clearMapLayers();
        });
        
        MapObject mo = cnt.addMarker(EncodedImage.createFromImage(markerImg, false), new Coord(-33.866, 151.195), "test", "test",e->{
            System.out.println("Marker clicked");
            cnt.removeMapObject(sydney);
        });
        sydney = mo;
        System.out.println("MO is "+mo);
        mo = cnt.addMarker(EncodedImage.createFromImage(markerImg, false), new Coord(-18.142, 178.431), "test", "test",e->{
            System.out.println("Marker clicked");
        });
        System.out.println("MO is "+mo);
        cnt.addTapListener(e->{
            if (tapDisabled) {
                return;
            }
            tapDisabled = true;
            TextField enterName = new TextField();
            Container wrapper = BoxLayout.encloseY(new Label("Name:"), enterName);
            InteractionDialog dlg = new InteractionDialog("Add Marker");
            dlg.getContentPane().add(wrapper);
            enterName.setDoneListener(e2->{
                String txt = enterName.getText();
                cnt.addMarker(EncodedImage.createFromImage(markerImg, false), cnt.getCoordAtPosition(e.getX(), e.getY()), enterName.getText(), "", e3->{
                    ToastBar.showMessage("You clicked "+txt, FontImage.MATERIAL_PLACE);
                });
                dlg.dispose();
                tapDisabled = false;
            });
            dlg.showPopupDialog(new Rectangle(e.getX(), e.getY(), 10, 10));
            enterName.startEditingAsync();
        });
        
        Button showNextForm = new Button("Next Form");
                showNextForm.addActionListener(e->{
                    Form form = new Form("Hello World");
                    Button b1 = new Button("B1");
                    b1
                            .addActionListener(e2->{
                                ToastBar.showMessage("B1 was pressed", FontImage.MATERIAL_3D_ROTATION);
                            })
                            ;
                    
                    Button back = new Button("Back");
                    back
                            .addActionListener(e2->{
                                hi.showBack();
                            })
                            ;
                    form.add(b1);
                })
                ;
        
        FloatingActionButton fab = FloatingActionButton.createFAB(FontImage.MATERIAL_ACCESS_ALARM);
        fab.createSubFAB(FontImage.MATERIAL_FLIGHT, "");
        
        
        
        Container root = LayeredLayout.encloseIn(
                BorderLayout.center(fab.bindFabToContainer(cnt)),
                BorderLayout.south(
                        FlowLayout.encloseBottom(panTo, testCoordPositions, btnMoveCamera, btnAddMarker, btnAddPath, btnClearAll )
                )
        );
        
        hi.add(BorderLayout.CENTER, root);
        hi.show();
        
    }
    boolean tapDisabled = false;

    public void stop() {
        current = Display.getInstance().getCurrent();
    }

    public void destroy() {
    }

    

}

Steve Hannah

unread,
May 2, 2017, 3:34:12 PM5/2/17
to codenameone...@googlegroups.com
All you needed to do was share the code relating to the FAB.

I see from this that you are using a subFab.  This opens a Dialog which (for legacy technical reasons) displays a new form, but shows the existing form in behind the dialog.  Showing the existing form behind the dialog is done by creating an image snapshot of the form and rendering it behind.  This is what causes the flicker.

This is done for historical reasons, some of which are no longer relevant.  It is on our list of things to improve, but the list is long and I don't know when we'll get around to it.  For the case of subFabs, a relatively simple fix would be to change it to use InteractionDialog instead of Dialog which doesn't have the same overhead (it is essentially just a container that is drawn over the existing form).  However, even though that change is also relatively simple, I don't know an ETA on that.  Please file an RFE in the issue tracker so it doesn't get lost.

Regarding some of your other comments:

Netbeans didn't like the import statement "import static com.codename1.ui.ComponentSelector.$;"

Your project libs must be out of date.  You should update your project libs (in project properties).

 You're saying I don't need to pass in a GoogleMapsProvider with the API string param to the MapContainer for use with Android/iOS devices? If I don't, doesn't that default to OpenMapsStreetProvider then?

No.  Only in the simulator.  And I recommend you use the String version of the MapContainer constructor (passing a Javascript API key) instead of MapContainer(MapProvider) so that the simulator will use a webview rather than the static maps.  This will yield an experience closer to the device.
 
So, do I need to use callSerially() for when I add/remove markers to the map container or only when I repaint() or revalidate()? And is repaint() or revalidate() even the correct use for after I add/remove markers?

All accesses to the MapContainer are assumed to occur on the EDT, so if you need to call a method on the MapContainer, you need to make sure you're on the EDT.  (i.e. if you're in a different thread due to scheduleBackgroundTask(), you do need to wrap your calls in callSerially().

Steve 


--
You received this message because you are subscribed to the Google Groups "CodenameOne Discussions" group.
To unsubscribe from this group and stop receiving emails from it, send an email to codenameone-discussions+unsub...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Steve Hannah
Software Developer
Codename One
Reply all
Reply to author
Forward
0 new messages