I am attempting to animate markers on a Google Map. The issue that I am running into is how the markers are behaving when it is time for them to animate. The markers are moving along a given set of points, but they start shaking rather violently, or they will move along a set path and appear to be rubberbanding, or jumping back to the original start position.
Has anyone dealt with this issue before? and if so, how did you fix it? I am currently using a somewhat modified version of code that was provided by Google devs to handle animation.
UPDATE: I believe the issue is caused by trying to run many animations simultaneously on a given Marker. This is causing the marker to bounce back and forth between all the new/old positions.
Here is the code that handles the Animation calls, the method is passed a list of LatLngs which represent the path the marker should follow.
public void animateMarker(String key, List<LatLng> latlngList) {
AnimateCarObj animateCarObj = animateCarObjMap.get(key);
Marker marker = markersHashMap.get(key);
if (marker != null) {
LatLng prev = new LatLng(0, 0);
for (LatLng latlng : latlngList) {
if (!(latlng.equals(prev))) {
try {
LatLngInterpolator latlonInter = new LinearFixed();
latlonInter.interpolate(1, animateCarObj.getGps1(), latlng);
MarkerAnimation.animateMarker(latlngList.size(), marker, latlng, latlonInter);
prev = latlng;
} catch (Exception e) {
Log.e(TAG, "EXCEPTION: " + e.getMessage());
e.printStackTrace();
}
}
}
}
}
MarkerAnimation Class, I modified this class to take an Integer value called "steps", this is so that the animation will pass through all the points at an even pace regardless of how many points are returned through a request. In this example, it uses a default value of 3, multiplies it by 10000 ms, and than divides it by the step count.
public class MarkerAnimation {
public static void animateMarker(int steps, final Marker marker, final LatLng finalPosition,
final LatLngInterpolator latLngInterpolator) {
if (marker == null || finalPosition == null || latLngInterpolator == null) { return; }
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1) {
animateMarkerToGB(steps, marker, finalPosition, latLngInterpolator);
} else {
animateMarkerToICS(steps, marker, finalPosition, latLngInterpolator);
}
}
public static void animateMarkerToGB(int steps, final Marker marker, final LatLng finalPosition,
final LatLngInterpolator latLngInterpolator) {
final LatLng startPosition = marker.getPosition();
final Handler handler = new Handler();
final long start = SystemClock.uptimeMillis();
final Interpolator interpolator = new AccelerateDecelerateInterpolator();
final float durationInMs = (CarListStore.DEFAULT_ALIVE_COUNT * 10000) / steps;
handler.post(new Runnable() {
long elapsed;
float t;
float v;
#Override
public void run() {
// Calculate progress using interpolator
elapsed = SystemClock.uptimeMillis() - start;
t = elapsed / durationInMs;
v = interpolator.getInterpolation(t);
marker.setPosition(latLngInterpolator.interpolate(v, startPosition, finalPosition));
// Repeat till progress is complete.
if (t < 1) {
// Post again 16ms later.
handler.postDelayed(this, 16);
}
}
});
}
/* #TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static void animateMarkerToHC(final Marker marker, final LatLng finalPosition,
final LatLngInterpolator latLngInterpolator) {
final LatLng startPosition = marker.getPosition();
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
float v = animation.getAnimatedFraction();
LatLng newPosition = latLngInterpolator.interpolate(v, startPosition, finalPosition);
marker.setPosition(newPosition);
}
});
valueAnimator.setFloatValues(0, 1); // Ignored.
valueAnimator.setDuration(3000);
valueAnimator.start();
}*/
#TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public static void animateMarkerToICS(int steps, Marker marker, LatLng finalPosition, final LatLngInterpolator latLngInterpolator) {
TypeEvaluator<LatLng> typeEvaluator = new TypeEvaluator<LatLng>() {
#Override
public LatLng evaluate(float fraction, LatLng startValue, LatLng endValue) {
return latLngInterpolator.interpolate(fraction, startValue, endValue);
}
};
Property<Marker, LatLng> property = Property.of(Marker.class, LatLng.class, "position");
ObjectAnimator animator = ObjectAnimator.ofObject(marker, property, typeEvaluator, finalPosition);
animator.setDuration((CarListStore.DEFAULT_ALIVE_COUNT * 10000) / steps);
animator.start();
}
}
You are making Asynchronous calls right now and since you have multiple animation on maker you are facing jitters.
Use synchronized method instead i.e [valueAnimator][1] - you have it commented out in your code. Watch this video in the docs, it explains why and when to use it.
Hope it helped!
My method is to schedule a Runnable using handler such that it can have some callback to call to notify animation is complete. Maintain a linked list for queueing marker position as soon as you get OnComplete callback from last animation handler delayed runnable pop the next latlng object from the queue and run the animation.
In each animation method for ICS & HC after animator.start() register delayed runnable using handler.postDelayed() where the delay is exact duration of the animation
Related
in iOS, Google Map allow to provide an animation when Marker pop on map.
I'am looking to do the same on Android. So there is nothing to do it or someone know a workaround?
You can animate marker with general Android approach: Animations and Transitions. Use this or that as examples:
private static void animateMarker(final Marker marker, final int current, final LatLng[] line) {
if (line == null || line.length == 0 || current >= line.length) {
return;
}
ObjectAnimator animator = ObjectAnimator.ofObject(marker, property, typeEvaluator, line[current]);
animator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
animateMarker(marker, current + 1, line);
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.setDuration(DURATION);
animator.start();
}
or use libraries, like that.
As per your response from the server, you receive/extract the latitude and longitude.
//call this method for adding marker with lat and long
public void addmarker(){
LatLng marker = new LatLng(double lat, double long);
googleMap.addMarker(new MarkerOptions().position(marker)
.title("My title"));
googleMap.moveCamera(CameraUpdateFactory.newLatLng(marker));
// now the marker is set
// add a delay of 100-200 ms and start you animation (delay for failsafe)
}
I have a map fragment that is handled by google's cluster manager that I configured with my custom cluster renderer extends DefaultClusterRenderer.
I have overritten the functions onBeforeClusterItemRendered and onBeforeClusterRendered to be able to diplay my pictures:
Now If the user zooms in it makes no sense to render the items that are not in the visible area.
It's very easy to find out if the item is in the visible area:
private Boolean isInBounds(LatLng position) {
return map.getProjection().getVisibleRegion().latLngBounds.contains(position);
}
But if I skip the rendering if the item is not currently visible, it will be empty when the user scrolls on the map.
So who knows how to get an event if the user scrolls and how to re render the items that are not in the visible bounds? (switchen from visible to non visible and vice versa)?
(Sorry for my bad English)
Here is my solution. It works very well and renders only the visible items.
I use the camera changed listener to re render the items that became visible now:
private void onBeforeClusterOrClusterItemRendered(final Cluster<MediaItem> cluster, final MediaItem mediaItem, final MarkerOptions markerOption
if(!isAdded())
return;
// In visible area?
Marker marker = cluster == null ? getMarker(mediaItem) : getMarker(cluster);
Boolean isInBounds = isInBounds(marker != null ? marker.getPosition() : mediaItem.getPosition(), null);
if(isInBounds) {
// ...
}
}
private Boolean isInBounds(LatLng position, LatLngBounds latLngBounds) {
return (latLngBounds == null ? mMap.getProjection().getVisibleRegion().latLngBounds : latLngBounds).contains(position);
}
#Override
protected void onBeforeClusterItemRendered(final MediaItem mediaItem, final MarkerOptions markerOptions) {
onBeforeClusterOrClusterItemRendered(null, mediaItem, markerOptions);
}
#Override
protected void onBeforeClusterRendered(final Cluster<MediaItem> cluster, final MarkerOptions markerOptions) {
final MediaItem mediaItem = MediaPicker.getBestRated(new ArrayList<>(cluster.getItems()));
onBeforeClusterOrClusterItemRendered(cluster, mediaItem, markerOptions);
}
...
// Re render
mMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition cameraPosition) {
mClusterManager.onCameraChange(cameraPosition);
final LatLngBounds latLngBounds = mMap.getProjection().getVisibleRegion().latLngBounds;
// Cluster only
Collection<Marker> clusters = mClusterManager.getClusterMarkerCollection().getMarkers();
for(Marker marker : clusters) {
if(isInBounds(marker.getPosition(), latLngBounds))
onBeforeClusterRendered(getCluster(marker), new MarkerOptions());
}
// Cluster item only
Collection<Marker> markers = mClusterManager.getMarkerCollection().getMarkers();
for(Marker marker : markers) {
if(isInBounds(marker.getPosition(), latLngBounds))
onBeforeClusterItemRendered(getClusterItem(marker), new MarkerOptions());
}
}
});
I had similar issue. I track initial location of the user and show map markers around him. If user chooses to look outside initial zoom, I download additional markers. Implementation:
map.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition cameraPosition) {
setCurrentRadius();
viewCenter = cameraPosition.target;
if (isNewGrounds(viewCenter, viewRadius)) {
downloadExtraPrinters(viewCenter,viewRadius);
}
myClusterManager.onCameraChange(cameraPosition);
}
});
private void setCurrentRadius() {
Projection projection = map.getProjection();
VisibleRegion currentView = projection.getVisibleRegion();
LatLng cameraCenter = map.getCameraPosition().target;
float[] projectionRadius = new float[1];
Location.distanceBetween(currentView.farLeft.latitude, currentView.farLeft.longitude, cameraCenter.latitude, cameraCenter.longitude, projectionRadius);
viewRadius = Math.round((projectionRadius[0] / 1000.0f) + 0.5f);
}
/** Method checks if camera goes outside bounds of previously downloaded area **/
protected boolean isNewGrounds(LatLng center, int radius) {
//Check if it is the first time to update
if (coveredGroundsCenter== null) {
coveredGroundsCenter=center;
coveredGroundsRadius= (int)(radius * EXTRA_RADIUS);
return true;
}else {
float[] centerDistance = new float[1];
Location.distanceBetween(coveredGroundsCenter.latitude, coveredGroundsCenter.longitude, center.latitude, center.longitude, centerDistance);
int criticalDistance = (int) Math.round((centerDistance[0] / 1000.0f) + 0.5f) + radius;
if (coveredGroundsRadius >= criticalDistance) {
return false;
} else {
coveredGroundsCenter = center;
coveredGroundsRadius = (int) (radius * EXTRA_RADIUS);
return true;
}
}
}
EXTRA_RADIUS is a constant I use to have a small margin outside visible map area, so the smallest camera movement wouldnt cause my map to download and re-render. I choosed this margin to be 50 percent of the current radius:
private static final double EXTRA_RADIUS=1.5;
private LatLng coveredGroundsCenter;
private int coveredGroundsRadius;
private LatLng viewCenter;
Maybe it's too late, but I hope I can help someone. I tried to find a solution, but I didn't succeed, so I started looking for a solution through clustering classes. I found a class called NonHierarchicalViewBasedAlgorithm but it didn't work properly or I used it incorrectly. And based on this class, I wrote my own using LatLngBounds. It's called VisibleBoundsAlgorithm.
Note: You must call the updateBounds() method before each clustering. Use this in onCameraIdleListener
Here is code:
VisibleBoundsAlgorithm.java
package com.example.demo.app;
import android.util.Log;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.maps.android.clustering.ClusterItem;
import com.google.maps.android.clustering.algo.NonHierarchicalDistanceBasedAlgorithm;
import com.google.maps.android.clustering.algo.ScreenBasedAlgorithm;
import com.google.maps.android.geometry.Bounds;
import com.google.maps.android.quadtree.PointQuadTree;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class VisibleBoundsAlgorithm<T extends ClusterItem>
extends NonHierarchicalDistanceBasedAlgorithm<T> implements ScreenBasedAlgorithm<T> {
private final GoogleMap map;
private LatLngBounds bounds;
public VisibleBoundsAlgorithm(GoogleMap map) {
this.map = map;
bounds = map.getProjection().getVisibleRegion().latLngBounds;
}
#Override
protected Collection<QuadItem<T>> getClusteringItems(PointQuadTree<QuadItem<T>> quadTree, float mapZoom) {
long oldMillis = System.currentTimeMillis(); //Getting time before operations
//Getting all items from QuadTree
List<QuadItem<T>> items = new ArrayList<>(quadTree.search(new Bounds(0, 1, 0, 1)));
ListIterator<QuadItem<T>> itemIterator = items.listIterator();
while (itemIterator.hasNext()) {
QuadItem<T> item = itemIterator.next();
if (!bounds.contains(item.getPosition())) { //if map do not contain item on this position remove it
itemIterator.remove();
}
}
long newMillis = System.currentTimeMillis();
Log.println(Log.ERROR, "Clustering items", "end in: " + (newMillis - oldMillis) + " (" + TimeUnit.SECONDS.convert(newMillis - oldMillis, TimeUnit.MILLISECONDS) + " seconds)");
return items; //Returning new massive of items
}
/**
* Call this before clustering.
*/
public void updateBounds() {
bounds = map.getProjection().getVisibleRegion().latLngBounds;
}
//When map is moving or zooming it should re-cluster
#Override
public boolean shouldReclusterOnMapMovement() {
return true;
}
#Override
public void onCameraChange(CameraPosition position) {
//required
}
}
In your Activity class:
ClusterManager<MyItem> cluster = new ClusterManager<MyItem>(context, map);
VisibleBoundsAlgorithm<MyItem> visibleBoundsAlgorithm = new VisibleBoundsAlgorithm<MyItem>(map);
cluster.setAlgorithm(visibleBoundsAlgorithm);
cluster.addItems(items);
cluster.cluster();
map.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() {
#Override
public void onCameraIdle() {
visibleBoundsAlgorithm.updateBounds();
cluster.cluster();
}
});
If someone have a question ask me.
I want to change the background of the cluster marker on click. I do this via
#Override
onClusterClick(Cluster<MyObject> cluster) {
Marker marker = renderer.getMarker(cluster);
marker.setIcon(....);
}
This works fine expect for one case: When I zoom in or out and the number of cluster markers doesn't change. For example, if I had a 15 cluster and a 5 cluster, then zoom a level in or out, the same two clusters remain. Clicking on one of these now renderer.getMarker(cluster) returns null. If they re-cluster after zooming, getMarker is not null.
My DefaultClusterRenderer is below. I checked the marker on onClusteredRendered and it's never null. Is this a bug in the DefaultClusterRenderer or should I do something else?
private class MyRenderer extends DefaultClusterRenderer<MyObject> {
private IconGenerator iconGenerator;
private float density;
public MyRenderer(Context context, GoogleMap map, ClusterManager<MyObject> clusterManager) {
super(context, map, clusterManager);
density = context.getResources().getDisplayMetrics().density;
}
#Override
protected void onBeforeClusterItemRendered(MyObject item, MarkerOptions markerOptions) {
markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.my_pin));
}
#Override
protected void onBeforeClusterRendered(Cluster<MyObject> cluster, MarkerOptions markerOptions) {
if(iconGenerator == null) {
iconGenerator = new IconGenerator(getActivity());
iconGenerator.setContentView(makeTextView(getActivity()));
}
iconGenerator.setBackground(makeBackground(false));
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(iconGenerator.makeIcon(String.valueOf(cluster.getSize()))));
}
#Override
protected void onClusterRendered(Cluster<MyObject> cluster, Marker marker) {
super.onClusterRendered(cluster, marker);
// Marker is never null here
}
#Override
protected boolean shouldRenderAsCluster(Cluster<MyObject> cluster) {
return cluster.getSize() > 1;
}
private ShapeDrawable makeBackground(boolean isClicked) {
ShapeDrawable background = new ShapeDrawable(new OvalShape());
background.setColorFilter(ContextCompat.getColor(getActivity(),
isClicked ? R.color.cluster_marker_clicked : R.color.cluster_marker_unclicked), PorterDuff.Mode.SRC_ATOP);
return background;
}
private SquareTextView makeTextView(Context context) {
SquareTextView squareTextView = new SquareTextView(context);
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(-2, -2);
squareTextView.setLayoutParams(layoutParams);
squareTextView.setTextColor(ContextCompat.getColor(getActivity(), R.color.white));
squareTextView.setTypeface(Utils.getFontBold(getActivity()));
squareTextView.setId(com.google.maps.android.R.id.text);
int twelveDpi = (int) (12.0F * density);
squareTextView.setPadding(twelveDpi, twelveDpi, twelveDpi, twelveDpi);
return squareTextView;
}
public IconGenerator getIconGenerator(boolean isClicked) {
iconGenerator.setBackground(makeBackground(isClicked));
return iconGenerator;
}
}
Initializing the ClusterManager:
final ClusterManager<MyObject> mClusterManager = new ClusterManager<>(getActivity(), googleMap);
mClusterManager.addItems(items);
renderer = new CustomRenderer(getActivity(), googleMap, mClusterManager);
mClusterManager.setRenderer(renderer);
mClusterManager.cluster();
mClusterManager.setOnClusterItemClickListener(this);
googleMap.setOnMarkerClickListener(mClusterManager);
#antonio: This initialization is working for me:
public class MapsActivity extends FragmentActivity
implements ClusterManager.OnClusterClickListener<MyObject> {
// ...
private void setUpClusterer() {
googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(51.503186, -0.126446), 10));
mClusterManager = new ClusterManager<MyObject>(this, googleMap);
mClusterManager.setOnClusterClickListener(this);
renderer = new MyRenderer(this, googleMap, mClusterManager);
mClusterManager.setRenderer(renderer);
googleMap.setOnCameraChangeListener(mClusterManager);
googleMap.setOnMarkerClickListener(mClusterManager);
addItems();
}
private void addItems() {
// Set some lat/lng coordinates to start with.
double lat = 51.5145160;
double lng = -0.1270060;
// Add ten cluster items in close proximity, for purposes of this example.
for (int i = 0; i < 10; i++) {
double offset = i / 60d;
lat = lat + offset;
lng = lng + offset;
MyObject offsetItem = new MyObject(lat, lng);
mClusterManager.addItem(offsetItem);
}
}
#Override
public boolean onClusterClick(final Cluster<MyObject> cluster) {
Marker marker = renderer.getMarker(cluster);
marker.setIcon(BitmapDescriptorFactory.fromResource(R.drawable.my_newpin));
return true;
}
}
Couldn't get renderer.getMarker(cluster) to return a marker in that case above. A work around was to create:
Map <Cluster<MyObject>, Marker> clusterMarkerMap = new HashMap<>();
then add them on the DefaultClusterRenderer callback because the marker is never null there:
#Override
protected void onClusterRendered(Cluster<MyObject> cluster, Marker marker) {
super.onClusterRendered(cluster, marker);
clusterMarkerMap.put(cluster, marker);
}
Because the DefaultClusterManager clears them when the camera position changes, wipe the cluster marker map before it creates new ones:
googleMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition cameraPosition) {
// Clear the map here because the markers will be recreated
// when the manager is notified of a (zoom level) camera change
if(zoomLevelChanged)
clusterMarkerMap.clear();
mClusterManager.onCameraChange(cameraPosition);
}
});
Now I can get the marker successfully clusterMarkerMap.get(cluster)
I'm trying to gradually fade out a custom Google map marker.
I've seen all the posts that say to just use the drop in code from the DevBytes video and replace the setPosition with setAlpha, which is what I have attempted to do.
The problem is that whatever I do, my icon just goes white for the duration of the handler and then transparent upon completion, instead of gradually fading to complete transparency.
gMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
#Override
public boolean onMarkerClick(final com.google.android.gms.maps.model.Marker marker) {
if (marker.equals(myLocationMarker)) {
final long duration = 1000;
final int alpha = 100;
final long start = SystemClock.uptimeMillis();
final Handler handler = new Handler();
final Interpolator interpolator = new LinearInterpolator();
handler.post(new Runnable() {
#Override
public void run() {
long elapsed = SystemClock.uptimeMillis() - start;
float t = interpolator.getInterpolation((float) elapsed / duration);
float newAlpha = alpha - (t*100);
if(newAlpha<0)
newAlpha = 0;
int finalAlpha = (int)Math.ceil(newAlpha);
System.out.println("time = "+t);
System.out.println("newAlpha = "+newAlpha);
System.out.println("finalAlpha = "+finalAlpha);
marker.setAlpha(finalAlpha);
if (t < 1.0)
handler.postDelayed(this, 10);
}
});
return true;
}
});
I tried using ValueAnimator and it worked:
ValueAnimator ani = ValueAnimator.ofFloat(1, 0); //change for (0,1) if you want a fade in
ani.setDuration(5000);
ani.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
marker.setAlpha((float) animation.getAnimatedValue());
}
});
ani.start();
How would one fade in and out a GoogleMap marker?
This is how I add a marker to the Map:
marker = map.addMarker(new MarkerOptions()
.position(point)
.title(title)
.snippet(text)
.icon(BitmapDescriptorFactory.fromResource(R.drawable.marker))
);
A much simpler and cleaner solution is to just use the standard ObjectAnimator which was introduced in Android SDK 11.
Fading in is literally a one-liner:
ObjectAnimator.ofFloat(marker, "alpha", 0f, 1f).setDuration(500).start();
Fading out requires a bit more code to properly remove the marker once the animation completes:
Animator animator = ObjectAnimator.ofFloat(marker, "alpha", 1f, 0f);
animator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animator) {
marker.remove();
}
#Override public void onAnimationStart(Animator animator) {}
#Override public void onAnimationCancel(Animator animator) {}
#Override public void onAnimationRepeat(Animator animator) {}
});
animator.setDuration(500).start();
With Google Play services 4.0+ you can use Marker.setAlpha in conjunction with Handler that posts some Runnable every few milliseconds.
The code will be similar to my answer here Drop marker slowly from top of screen to location on android map V2. Just setAlpha instead of setPosition and you are on your way home.
use this code for fade in and out
map.moveCamera(CameraUpdateFactory.newLatLngZoom(position, 15));
map.animateCamera(CameraUpdateFactory.zoomTo(15), 2000, null);
Here is a more complete solution specifically for Fade In when adding a marker. Something to note is the requestNumber. this is useful if you're loading items while moving the map. Just increment it on every service call or remove it if you don't need it.
public void fadeInMarker(Activity activity,final GoogleMap map, final MarkerOptions markerOptions, final long duration,final int requestNumber){
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
if (currentRequestNumber != requestNumber){
return;
}
markerOptions.alpha(0);
final Marker marker = map.addMarker(markerOptions);
final AccelerateInterpolator accelartor = new AccelerateInterpolator();
final Long startTime = SystemClock.uptimeMillis();
final Handler handler = new Handler();
handler.post(new Runnable() {
#Override
public void run() {
float diff = SystemClock.uptimeMillis() - startTime;
float alpha = accelartor.getInterpolation(diff / duration);
if (alpha < 1) {
handler.postDelayed(this, 10);
}
else{
alpha = 1;
}
if (currentRequestNumber == requestNumber){
marker.setAlpha(alpha);
}
}
});
}
});
}