Google Maps Android No Cluster/DeCluster on Zoom In/Out - google-maps-markers

I have implemented Google Maps Clustering on the iOS version of my app and it works fine. I am now attempting to implement Clustering on the Android version using the android maps utility library and it is not Declustering/Clustering when I zoom in or out.
Here is the cluster item class:
public class MyClusterItem implements ClusterItem {
private final LatLng position;
private final BitmapDescriptor icon;
private final String tag;
public MyClusterItem(double lat, double lng, BitmapDescriptor icon, String tag) {
this.position = new LatLng(lat, lng);
this.icon = icon;
this.tag = tag;
}
#Override
public LatLng getPosition() {
return position;
}
#Override
public String getTitle() {
return null;
}
#Override
public String getSnippet() {
return null;
}
public BitmapDescriptor getIcon() {
return icon;
}
public String getTag() {
return tag;
}
}
Here is how the cluster manager is implemented:
public void onMapReady(GoogleMap googleMap) {
//Log.i ( TAG, "Google Map Ready Callback" );
mMap = googleMap;
//mMap.setOnMarkerClickListener(this);
// Point the map's listeners at the listeners implemented by the cluster
// manager.
mMap.setOnCameraIdleListener(mClusterManager);
mMap.getUiSettings().setMapToolbarEnabled(false);
mMap.getUiSettings().setMyLocationButtonEnabled(false);
mMap.getUiSettings().setRotateGesturesEnabled(false);
if (ActivityCompat.checkSelfPermission(MapsActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED && isLocationEnabled) {
mMap.setMyLocationEnabled(true);
}
float mCurrentZoom = googleMap.getCameraPosition().zoom;
// Initialize the manager with the context and the map.
// (Activity extends context, so we can pass 'this' in the constructor.)
mClusterManager = new ClusterManager<MyClusterItem>(this, mMap);
mClusterManager.setOnClusterItemClickListener(mClusterItemClickListener);
mMap.setOnMarkerClickListener(mClusterManager);
mClusterManager.setOnClusterClickListener(new ClusterManager.OnClusterClickListener<MyClusterItem>() {
#Override
public boolean onClusterClick(final Cluster<MyClusterItem> cluster) {
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
cluster.getPosition(), (float) Math.floor(mMap
.getCameraPosition().zoom + 1)), 300,
null);
return true;
}
});
}
Cluster Items are added from a Firebase database as follows:
markerRef.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
try {
tableCount = tableCount+1;
markerKey = dataSnapshot.getKey();
markerData = dataSnapshot.getValue(Markers.class);
MyClusterItem markerItem = new PicNickClusterItem(markerData.y, markerData.x, BitmapDescriptorFactory.fromResource(R.drawable.verified), markerKey);
mClusterItemMap.put(markerKey, markerItem);
mClusterManager.addItem(markerItem);
if(tableCount >= numTables) {
//cluster markers
mClusterManager.cluster();
}
} catch (Error error) {
noFirebaseAccessAlert();
}
}
}
Screenshot of the initial map:
Screenshot when zoomed out:
Screenshot when zoomed in:
Cluster Items are not being clustered/declustered when zooming out/in and there is no animation. As I zoom out I would expect the cluster items to move into clusters and as I zoom in Cluster Items to separate from clusters as they do in the iOS version of the app. Also when I tap on a cluster marker, the map zooms in, but the cluster does not expand into cluster items. None of the cluster items have the same lat/lng and are separated by significant distances.
In the iOS version I was able to set the algorithm (non hierarchical distance based) & several "buckets" for clustering (and specify custom icons for each). I need to have both versions looks and respond the same so that the user has the same experience on both iOS and Android devices.

Related

Android - How to make google Maps display a polyline that animates sequenctial flashing dots

I am searching for a way to animate the dots between two markers on a google map in android device.
So what i want in the end is the following line between the two images:
and it would be used like this typical google polyline implementation:
lets say there is a point A and a Point B. if im directing the user to point B, then the line animates to from point A to point B so the user knows to walk in this direction.
to achieve this i thought i could get the points out of the polyLine and remove them and add them back
rapidily. so lets say i had 5 points in the polyLine, i would remove position 1 , then put it back, then remove position 2, and put it back, to simulate this animation.
but it does not work . once hte polyline is set it seems i cannot alter it. you have any suggestions ?
val dotPattern = Arrays.asList(Dot(), Gap(convertDpToPixel(7).toFloat()))
val polyLineOptions: PolylineOptions = PolylineOptions()
.add(usersLocation)
.add(users_destination)
.pattern(dotPattern)
.width(convertDpToPixel(6).toFloat())
dottedPolyLine = googleMap.addPolyline(polyLineOptions)
dottedPolyLine?.points?.removeAt(1) // here as a test if my idea i try removing a point but it looks like a point here means current location or destination so there always 2. i thought a point would be one of the dots.
You can use MapView-based custom view View Canvas animationlike in this answer:
This approach requires
MapView-based
custom
view,
that implements:
drawing over the MapView canvas;
customizing line styles (circles instead of a simple line);
binding path to Lat/Lon coordinates of map
performing animation.
Drawing over the MapView needs to override dispatchDraw().
Customizing line styles needs
setPathEffect()
method of
Paint
class that allows to create create path for "circle stamp" (in
pixels), which will repeated every "advance" (in pixels too),
something like that:
mCircleStampPath = new Path(); mCircleStampPath.addCircle(0,0,
CIRCLE_RADIUS, Path.Direction.CCW); mCircleStampPath.close();
For binding path on screen to Lat/Lon coordinates
Projection.toScreenLocation()
needed, that requires
GoogleMap
object so custom view should implements OnMapReadyCallback for
receive it. For continuous animation
postInvalidateDelayed()
can be used.
but not draw path directly from point A to point B, but from point A to point C that animated from A to B. To get current position of point C you can use SphericalUtil.interpolate() from Google Maps Android API Utility Library. Something like that:
public class EnhancedMapView extends MapView implements OnMapReadyCallback {
private static final float CIRCLE_RADIUS = 10;
private static final float CIRCLE_ADVANCE = 3.5f * CIRCLE_RADIUS; // spacing between each circle stamp
private static final int FRAMES_PER_SECOND = 30;
private static final int ANIMATION_DURATION = 10000;
private OnMapReadyCallback mMapReadyCallback;
private GoogleMap mGoogleMap;
private LatLng mPointA;
private LatLng mPointB;
private LatLng mPointC;
private float mCirclePhase = 0; // amount to offset before the first circle is stamped
private Path mCircleStampPath;
private Paint mPaintLine;
private final Path mPathFromAtoC = new Path();
private long mStartTime;
private long mElapsedTime;
public EnhancedMapView(#NonNull Context context) {
super(context);
init();
}
public EnhancedMapView(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public EnhancedMapView(#NonNull Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public EnhancedMapView(#NonNull Context context, #Nullable GoogleMapOptions options) {
super(context, options);
init();
}
#Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
canvas.save();
drawLineFomAtoB(canvas);
canvas.restore();
// perform one shot animation
mElapsedTime = System.currentTimeMillis() - mStartTime;
if (mElapsedTime < ANIMATION_DURATION) {
postInvalidateDelayed(1000 / FRAMES_PER_SECOND);
}
}
private void drawLineFomAtoB(Canvas canvas) {
if (mGoogleMap == null || mPointA == null || mPointB == null) {
return;
}
// interpolate current position
mPointC = SphericalUtil.interpolate(mPointA, mPointB, (float) mElapsedTime / (float)ANIMATION_DURATION);
final Projection mapProjection = mGoogleMap.getProjection();
final Point pointA = mapProjection.toScreenLocation(mPointA);
final Point pointC = mapProjection.toScreenLocation(mPointC);
mPathFromAtoC.rewind();
mPathFromAtoC.moveTo(pointC.x, pointC.y);
mPathFromAtoC.lineTo(pointA.x, pointA.y);
// change phase for circles shift
mCirclePhase = (mCirclePhase < CIRCLE_ADVANCE)
? mCirclePhase + 1.0f
: 0;
mPaintLine.setPathEffect(new PathDashPathEffect(mCircleStampPath, CIRCLE_ADVANCE, mCirclePhase, PathDashPathEffect.Style.ROTATE));
canvas.drawPath(mPathFromAtoC, mPaintLine);
}
private void init() {
setWillNotDraw(false);
mCircleStampPath = new Path();
mCircleStampPath.addCircle(0,0, CIRCLE_RADIUS, Path.Direction.CCW);
mCircleStampPath.close();
mPaintLine = new Paint();
mPaintLine.setColor(Color.BLACK);
mPaintLine.setStrokeWidth(1);
mPaintLine.setStyle(Paint.Style.STROKE);
mPaintLine.setPathEffect(new PathDashPathEffect(mCircleStampPath, CIRCLE_ADVANCE, mCirclePhase, PathDashPathEffect.Style.ROTATE));
// start animation
mStartTime = System.currentTimeMillis();
postInvalidate();
}
#Override
public void getMapAsync(OnMapReadyCallback callback) {
mMapReadyCallback = callback;
super.getMapAsync(this);
}
#Override
public void onMapReady(GoogleMap googleMap) {
mGoogleMap = googleMap;
mGoogleMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() {
#Override
public void onCameraMove() {
invalidate();
}
});
if (mMapReadyCallback != null) {
mMapReadyCallback.onMapReady(googleMap);
}
}
public void setPoints(LatLng pointA, LatLng pointB) {
mPointA = pointA;
mPointB = pointB;
}
}
NB! This is just idea, not full tested code.

Xamarin: Detect device rotation when orientation is locked? For iOS and/or Android?

I am working on a Xamarin.Forms app that plays videos similar to a YouTube type app. I want the video to go full screen when the device rotates (like youtube does) but I also want the orientation to be locked in to portrait. Every post or tutorial I've found points to using custom renders for detecting orientation change to determine when the device rotates, but when orientation is locked those events do not fire.
Is there a way to detect device rotation without depending on orientation changing?
On iOS you would get device orientation with:
var orientation = UIDevice.CurrentDevice.Orientation;
On Android you need to ask the Window Manager:
var windowManager = ApplicationContext.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();
var orientation = windowManager.DefaultDisplay.Rotation;
You don't need a custom renderer, but you could suffice with a service you register in the service locator. This could looks something like.
In shared code:
public enum Orientation
{
None,
PortraitUp,
PortraitDown,
LandscapeLeft,
LandscapeRight
}
public interface IOrientationService
{
Orientation GetCurrentOrientation();
}
On Android:
[assembly: Dependency(typeof(AndroidOrientationService))]
public class AndroidOrientationService : IOrientationService
{
private readonly IWindowManager _windowManager;
public AndroidOrientationService()
{
_windowManager = ApplicationContext.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();
}
public Orientation GetCurrentOrientation()
{
switch (_windowManager.DefaultDisplay.Rotation)
{
case SurfaceOrientation.Rotation0:
return Orientation.PortraitUp;
case SurfaceOrientation.Rotation180:
return Orientation.PortraitDown;
case SurfaceOrientation.Rotation90:
return Orientation.LandscapeLeft;
case SurfaceOrientation.Rotation270:
return Orientation.LandscapeRight;
default:
return Orientation.None;
}
}
}
Similarly on iOS:
[assembly: Dependency(typeof(IosOrientationService))]
public class IosOrientationService : IOrientationService
{
public Orientation GetCurrentOrientation()
{
switch (UIDevice.CurrentDevice.Orientation)
{
case UIDeviceOrientation.LandscapeLeft:
return Orientation.LandscapeLeft;
case UIDeviceOrientation.LandscapeRight:
return Orientation.LandscapeRight;
case UIDeviceOrientation.Portrait:
return Orientation.PortraitUp;
case UIDeviceOrientation.PortraitUpsideDown:
return Orientation.PortraitDown;
default:
return Orientation.None;
}
}
}
Then in your code you should be able to get the orientation like:
var orientationService = DependencyService.Get<IOrientationService>();
var orientation = orientationService.GetCurrentOrientation();
EDIT: detecting orientation changes
If you want to detect orientation changes on iOS you can do that by adding an observer for UIDeviceOrientation.
UIDevice.Notifications.ObserveOrientationDidChange(OnOrientationChanged);
Similarly on Android you can use SensorManager to listen to SensorType.Orientation changes. It has a bit more moving parts but looks something like follows.
You need to create a ISensorEventListener class:
class MyOrientationListner : Java.Lang.Object, ISensorEventListener
{
public event EventHandler OrientationChanged;
public void OnAccuracyChanged(Sensor sensor, SensorStatus accuracy)
{
}
public void OnSensorChanged(SensorEvent e)
{
OrientationChanged?.Invoke(this, EventArgs.Empty);
}
}
Then you need to get the sensor manager from the current Context and start listening to orientation change events:
_sensorManager = context.GetSystemService(Context.SensorService).JavaCast<SensorManager>();
var sensor = _sensorManager.GetDefaultSensor(SensorType.Orientation);
var listener = new MyOrientationListner();
listener.OrientationChanged += OnOrientationChanged;
_sensorManager.RegisterListener(listener, sensor, SensorDelay.Normal);
private void OnOrientationChanged(object sender, EventArgs e)
{
OrientationChanged?.Invoke(this, GetCurrentOrientation());
}
Where OrientationChanged is a event in the IOrientationService:
event EventHandler<Orientation> OrientationChanged;
Then you can listen to that event where needed.
For iOS
In AppDelegate.cs override the below method
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations(UIApplication application,UIWindow forWindow)
{
if (Xamarin.Forms.Application.Current == null || Xamarin.Forms.Application.Current.MainPage == null)
{
return UIInterfaceOrientationMask.Portrait;
}
var mainPage = Xamarin.Forms.Application.Current.MainPage;
if (mainPage is YourPage || (mainPage is NavigationPage &&
((NavigationPage)mainPage).CurrentPage is YourPage) || (mainPage.Navigation != null &&
mainPage.Navigation.ModalStack.LastOrDefault() is YourPage))
{
if (Configuration.IsFullScreen)
{
return UIInterfaceOrientationMask.Landscape;
}
}
return UIInterfaceOrientationMask.Portrait;
}
In a Dependency Service write the below method
public void ChangeLandscapeOrientation()
{
UIDevice.CurrentDevice.SetValueForKey(new NSNumber((int)UIInterfaceOrientation.LandscapeLeft), new NSString("orientation"));
UINavigationController.AttemptRotationToDeviceOrientation();
}
Call the ChangeLandscapeOrientation method wherever you need it.
For Android
In a Dependency Service write the below method to change the orientation to Landscape
public void ChangeLandscapeOrientation()
{
var activity = (Activity)Xamarin.Forms.Forms.Context;
{
activity.RequestedOrientation = ScreenOrientation.Landscape;
var attrs = activity.Window.Attributes;
_originalFlags = attrs.Flags;
attrs.Flags |= Android.Views.WindowManagerFlags.Fullscreen;
activity.Window.Attributes = attrs;
}
}
Below code to change the orientation to Portrait
public void ChangePortraitOrientation()
{
var activity = (Activity)Xamarin.Forms.Forms.Context;
{
activity.RequestedOrientation = ScreenOrientation.Portrait;
var attrs = activity.Window.Attributes;
attrs.Flags = _originalFlags;
activity.Window.Attributes = attrs;
}
}
Hope it helps!

Gluon Mobile Charm 5.0 Cannot Hide Layer

I have a loading gif for all backend requests. Prior to Charm 5.0.0, it worked fine in which the loading gif would show, backend would finish what it needed to, then the loading gif would be hidden. Now, the loading gif shows, but it doesn't hide.
addLayerFactory(LOADING_GIF, () -> new Layer() {
private final Node root;
private final double sizeX = getGlassPane().getWidth();
private final double sizeY = getGlassPane().getHeight();
{
ProgressIndicator loading = new ProgressIndicator();
loading.setRadius(50);
loading.setStyle("-fx-text-fill:white");
root = new StackPane(loading);
root.setStyle("-fx-background-color: rgba(0,0,0,0);");
getChildren().add(root);
this.setStyle("-fx-background-color:rgba(255,255,255,0.7)");
this.setShowTransitionFactory(v -> {
FadeInTransition ft = new FadeInTransition(v);
ft.setRate(2);
return ft;
});
}
#Override
public void show() {
this.setBackgroundFade(0.0);
super.show();
Layer pane = this;
Task<Integer> task = new Task<Integer>() {
#Override
protected Integer call() throws Exception {
int iterations = 0;
int max = DataService.readOutTime / 1000;
while (iterations <= max) {
Thread.sleep(1000);
iterations++;
}
Platform.runLater(new Runnable() {
#Override
public void run() {
if (pane.isVisible()) {
pane.setShowTransitionFactory(v -> {
FadeOutTransition ft = new FadeOutTransition(v);
ft.setRate(2);
return ft;
});
pane.hide();
MobileApplication.getInstance().showMessage("There was an error in sending your data.");
}
}
});
return iterations;
}
};
Thread thread = new Thread(task);
thread.start();
}
#Override
public void hide() {
this.setBackgroundFade(0.0);
super.hide();
}
#Override
public void layoutChildren() {
root.setVisible(isShowing());
if (!isShowing()) {
return;
}
root.resize(sizeX, sizeY);
resizeRelocate((getGlassPane().getWidth() - sizeX) / 2, (getGlassPane().getHeight() - sizeY) / 2, sizeX, sizeY);
}
});
I have a couple of utility methods that show and hide the loader:
public void showLoader() {
MobileApplication.getInstance().showLayer(App.LOADING_GIF);
}
public void hideLoader() {
MobileApplication.getInstance().hideLayer(App.LOADING_GIF);
}
Interestingly, the custom timeout I created (to hide the loader in case there is a stall in the backend) doesn't hide the layer either.
There is an issue with your code: you are overriding Layer::layoutChildren, but you are not calling super.layoutChildren().
If you check the JavaDoc:
Override this method to add the layout logic for your layer. Care should be taken to call this method in overriden methods for proper functioning of the Layer.
This means that you are getting rid of some important parts of the Layer control, such as animations, events and visibility control.
This should work:
#Override
public void layoutChildren() {
super.layoutChildren();
root.setVisible(isShowing());
if (!isShowing()) {
return;
}
root.resize(sizeX, sizeY);
resizeRelocate(getGlassPane().getWidth() - sizeX) / 2, getGlassPane().getHeight() - sizeY) / 2, sizeX, sizeY);
}
On a side note, for the hide transition, you should use setHideTransitionFactory.
So this is what I have done to solve this. From the Gluon Docs on the hide() method:
If this layer is showing, calling this method will hide it. If a hide transition is present, it is played before hiding the Layer. Care should be taken to call this only once LifecycleEvent.SHOWN has been fired.
Thus, I was realizing that the response from the backend was coming before the layer was fully shown. Thus, I modified the overridden hide() method as follows:
#Override
public void hide() {
if (this.isShowing()) {
this.setOnShown(e -> {
this.setBackgroundFade(0.0);
super.hide();
});
} else {
super.hide();
}
}
So if the layer is still in LifecycleEvent.SHOWING mode when being told to hide, make sure that it hides when it is shown. Otherwise it is already shown so hide it.

How to populate a tableview cell with an image from Sqlite database in JavaFX?

I am trying to populate a tableView cell with an image stored on a Sqlite database using JavaFX. I have found some really good information on here and feel like I am getting pretty close. If there is no image I would like it to keep the constraints to make the cells the same size always. The images stored on the database are 300x300, but the rows will be much smaller. So far the code I have is :
public void buildDataAseptic(){
listNum = 1;
data = FXCollections.observableArrayList();
try{
String SQL = "Select * from aseptic_parts_list"; //Order By id
ResultSet rs = con.createStatement().executeQuery(SQL);
while(rs.next()){
Part cm = new Part();
cm.id.set(listNum++);
if (rs.getBlob("image") != null ) {
Blob blob = rs.getBlob("image");
byte[] ndata = blob.getBytes(1, (int) blob.length());
image = new Image(new ByteArrayInputStream(ndata));
ImageView imageView = new ImageView();
imageView.setImage(image);
imageView.setFitWidth(70);
imageView.setFitHeight(80);
cm.image.set(image);
}
cm.vendor_part_number.set(rs.getString("vendor_part_number"));
cm.description.set(rs.getString("description"));
cm.quantity.set(rs.getInt("quantity"));
cm.vendor_name.set(rs.getString("vendor_name"));
cm.model_number.set(rs.getString("model_number"));
cm.equipment_id.set(rs.getString("equipment_id"));
data.add(cm);
}
tableView.setItems(data);
}
catch(Exception e){
e.printStackTrace();
System.out.println("Error on Building Data" + e.getMessage());
}
filterData();
//filterEquipIDData();
}
this is just one particular method that is called from a comboBox. All of the other data is populating fine in the table except the image. In the initialize() method I have the columns set up as:
assert tableView != null;
idCol.setCellValueFactory(
new PropertyValueFactory<Part, Integer>("id"));
imgCol.setCellValueFactory(
new PropertyValueFactory<Object,ImageView>("image"));
pnCol.setCellValueFactory(
new PropertyValueFactory<Part,String>("vendor_part_number"));
descCol.setCellValueFactory(
new PropertyValueFactory<Part,String>("description"));
quantityCol.setCellValueFactory(
new PropertyValueFactory<Part,Integer>("quantity"));
venCol.setCellValueFactory(
new PropertyValueFactory<Part,String>("vendor_name"));
mnCol.setCellValueFactory(
new PropertyValueFactory<Part,String>("model_number"));
equipmentIDCol.setCellValueFactory(
new PropertyValueFactory<Part,String>("equipment_id"));
The Part class where the image object is stored is:
public SimpleObjectProperty<Image> image = new SimpleObjectProperty<>();
public Object getImage() {
return image.get();
}
I have been fiddling with this for a few days now and feel pretty close, just no cigar, please help and thanks!
Here is an example for populating TableView cells with images.
The key to the solution is to set the cell value factory and the cell factory appropriately:
TableColumn<Fish, Image> imageColumn = new TableColumn<>("Picture");
imageColumn.setCellValueFactory(new PropertyValueFactory<>("image"));
imageColumn.setCellFactory(param -> new ImageTableCell<>());
Where the ImageTableCell class contains an ImageView as the graphic for the cell and updates the ImageView as the underlying image data changes:
private class ImageTableCell<S> extends TableCell<S, Image> {
final ImageView imageView = new ImageView();
ImageTableCell() {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
protected void updateItem(Image item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
imageView.setImage(null);
setText(null);
setGraphic(null);
}
imageView.setImage(item);
setGraphic(imageView);
}
}
One thing to note about this implementation is that it assumes that all images are loaded up into the underlying data structure for the table. This means that if you had a lot of rows in the table, you would be consuming massive amounts of data as all images would be loaded into memory. An alternate solution would be for the underlying data structure just to store the address (url) of the image rather than the image data itself, then load up the image in the cell factory (possibly via an LRU cache mechanism). The trade-off between the different approaches, is speed of operation of the GUI and resources consumed as the user interacts (which is what the all in-memory approach here optimizes for), versus a slower GUI but reduced memory footprint (which is what dymanically loading images in the updateItem call would optimize for). In general, I feel it is best to try to keep the speed of the updateItem call very quick, which is why the solution is presented as is.
I won't supply a sample which also integrates SQLLite as I have no knowledge of that technology, so further integration of the solution with a database system is left up to the reader.
Complete Code
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.*;
import javafx.stage.Stage;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class ImageTable extends Application {
#Override
public void start(Stage stage) {
List<Fish> fish = Arrays.stream(fishData)
.map(data -> new Fish(data[0], new Image(data[1])))
.collect(Collectors.toList());
TableView<Fish> tableView = new TableView<>(FXCollections.observableList(fish));
TableColumn<Fish, String> nameColumn = new TableColumn<>("Name");
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
tableView.getColumns().add(nameColumn);
TableColumn<Fish, Image> imageColumn = new TableColumn<>("Picture");
imageColumn.setCellValueFactory(new PropertyValueFactory<>("image"));
imageColumn.setCellFactory(param -> new ImageTableCell<>());
tableView.getColumns().add(imageColumn);
stage.setScene(new Scene(tableView));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
// image license: linkware - backlink to http://www.fasticon.com
private static final String[][] fishData = {
{ "Blue Fish", "http://icons.iconarchive.com/icons/fasticon/fish-toys/128/Blue-Fish-icon.png" },
{ "Red Fish", "http://icons.iconarchive.com/icons/fasticon/fish-toys/128/Red-Fish-icon.png" },
{ "Yellow Fish", "http://icons.iconarchive.com/icons/fasticon/fish-toys/128/Yellow-Fish-icon.png" },
{ "Green FIsh", "http://icons.iconarchive.com/icons/fasticon/fish-toys/128/Green-Fish-icon.png" }
};
private class ImageTableCell<S> extends TableCell<S, Image> {
final ImageView imageView = new ImageView();
ImageTableCell() {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
protected void updateItem(Image item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
imageView.setImage(null);
setText(null);
setGraphic(null);
}
imageView.setImage(item);
setGraphic(imageView);
}
}
public static final class Fish {
private ReadOnlyStringWrapper name;
private ReadOnlyObjectWrapper<Image> image;
public Fish(String name, Image image) {
this.name = new ReadOnlyStringWrapper(name);
this.image = new ReadOnlyObjectWrapper<>(image);
}
public String getName() {
return name.get();
}
public ReadOnlyStringProperty nameProperty() {
return name.getReadOnlyProperty();
}
public Image getImage() {
return image.get();
}
public ReadOnlyObjectProperty<Image> imageProperty() {
return image.getReadOnlyProperty();
}
}
}
Thank you very much jewelsea! I ended up doing what you said, creating another folder to hold the images and using a url to reference the pictures in the folder. The code I used is:
if(rs.getString("image") != null) {
Image img = new Image(rs.getString("image"));
ImageView imageView = new ImageView();
imageView.setImage(img);
cm.image.set(imageView);
imageView.setFitWidth(130);
imageView.setFitHeight(100);
} else {
Image img = new Image("/img/NoImageFound.png");
ImageView imageView = new ImageView();
imageView.setImage(img);
cm.image.set(imageView);
imageView.setFitWidth(130);
imageView.setFitHeight(100);
}
This code queries the database for the image and if there is none there it uses an image i created for image not found. I did this because it was shortening my database results to only queries that had an image, the else shows all results from the database now. It is good to know that this will make it faster. I am building a parts database for my work with listeners for filtering parts, and that might have been too slow once the database gets really big to have all the parts directly stored on the database. Your help has sent me in the right direction, thanks a million!

Cluster Marker Click Event Android

I am using Google Maps Marker Clustering available in the utils library. On clicking a Cluster, the below onClusterClick method is not called. Is there a Cluster click event?
#Override
public boolean onClusterClick(Cluster<MyItem> cluster)
{
return true;
}
you need add this line before:
map.setOnMarkerClickListener(yourClusterManager);
Here is the way that how can achieve your requirement i.e. to click on cluster or cluster item.
mapSupportFragment.getMapAsync(new OnMapReadyCallback() {
#Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap; //(Here mMap is my GoogleMap object declared & was initialized).
// Initialize the manager with the context and the map.
mClusterManager = new ClusterManager<AppClusterItem>(MFragmentActivity.this, mMap);
mClusterManager.setRenderer(new MyCustomRender(MFragmentActivity.this, mMap, mClusterManager));
// Here which I used is my custom rendering class
// Point the map's listeners at the listeners implemented by the cluster manager.
mMap.setOnCameraChangeListener(mClusterManager);
mMap.setOnMarkerClickListener(mClusterManager);
mClusterManager.setOnClusterClickListener(new ClusterManager.OnClusterClickListener<AppClusterItem>() {
#Override
public boolean onClusterClick(Cluster<AppClusterItem> cluster) {
Log.e("I clicked # ", "Cluster which consumes whole list of ClusterItems");
return false;
}
});
mClusterManager.setOnClusterItemClickListener(new ClusterManager.OnClusterItemClickListener<AppClusterItem>() {
#Override
public boolean onClusterItemClick(AppClusterItem item) {
Log.e("I clicked # ", "Cluster Item");
return false;
}
});
}
});
This solution will then not let you to use GoogleMap.setOnMarkerClickListener method, then so for it, you can read this answer.
maby its too late but if there any one want to know :
just set OnClusterItemClcikListener on your CkusterManger :
mClusterManager.setOnClusterItemClickListener(this);
then make you class implement ClusterManager.OnClusterItemClickListener
activity a implements ClusterManager.OnClusterItemClickListener ....
then finaly call
#Override
public boolean onClusterItemClick(ClusterItem clusterItem) {
Toast.makeText(getActivity(), "clciked", Toast.LENGTH_SHORT).show();
return true;
}

Resources