I want to change clustering icon like below in android. In a circle there will be a one imageview and one textview.
My code for custom icon
private class ItemRenderer extends DefaultClusterRenderer<ClusterPopupList> {
private final IconGenerator mIconGenerator = new IconGenerator(getApplicationContext());
private final IconGenerator mClusterIconGenerator = new IconGenerator(getApplicationContext());
private final int mDimension;
public ItemRenderer() {
super(getApplicationContext(), map, mClusterManager);
View multiProfile = getLayoutInflater().inflate(R.layout.multi_profile,null);
mClusterIconGenerator.setContentView(multiProfile);
mImageView = new ImageView(getApplicationContext());
mDimension = (int) getResources().getDimension(R.dimen.custom_profile_image);
mImageView.setLayoutParams(new ViewGroup.LayoutParams(mDimension, mDimension));
mIconGenerator.setContentView(mImageView);
}
#Override
protected void onBeforeClusterItemRendered(ClusterPopupList item, MarkerOptions markerOptions) {
mImageView.setImageResource(item.profilePhoto);
Bitmap icon = mIconGenerator.makeIcon();
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon));
super.onBeforeClusterItemRendered(item, markerOptions);
}
#Override
protected void onBeforeClusterRendered(Cluster<ClusterPopupList> cluster, MarkerOptions markerOptions) {
List<Drawable> profilePhotos = new ArrayList<Drawable>(Math.min(4, cluster.getSize()));
int width = mDimension;
int height = mDimension;
for (ClusterPopupList p : cluster.getItems()) {
// Draw 4 at most.
if (profilePhotos.size() == 4) break;
Drawable drawable = getResources().getDrawable(p.profilePhoto);
drawable.setBounds(0, 0, width, height);
profilePhotos.add(drawable);
}
Bitmap icon = mClusterIconGenerator.makeIcon(String.valueOf(cluster.getSize()));
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon));
}
#Override
protected void onClusterRendered(Cluster<ClusterPopupList> cluster, Marker marker) {
super.onClusterRendered(cluster, marker);
}
#Override
public ClusterPopupList getClusterItem(Marker marker) {
return super.getClusterItem(marker);
}
#Override
protected boolean shouldRenderAsCluster(Cluster<ClusterPopupList> cluster) {
return cluster.getSize() > 1;
}
}
multi_profile.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<de.hdodenhof.circleimageview.CircleImageView
android:id="#+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:background="#android:color/transparent"
android:src="#drawable/icon_cluster_count"/>
<TextView
android:id="#id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="#style/Bubble.TextAppearance.Light"
android:paddingLeft="#dimen/custom_profile_padding"
android:paddingRight="#dimen/custom_profile_padding"
android:layout_below="#id/image"
android:text="150"
android:layout_centerHorizontal="true"
android:layout_marginBottom="8dip"
android:textColor="#color/theme_color"
android:alpha=".8"/>
</RelativeLayout>
Output :
I want to achieve cluster icon like first image in a circle. I have used circle background in relative layout, but that does not work.
Use this to remove background
mClusterIconGenerator.setBackground(null)
Set the background to a given Drawable, or remove the background.
#param background the Drawable to use as the background, or null to remove the background.
mClusterIconGenerator.setBackground(AndroidUtil.getDrawable(R.drawable.cluster_backgroud));
From the Google Maps documentation site:
Customize the marker clusters
The ClusterManager constructor creates a DefaultClusterRenderer and a NonHierarchicalDistanceBasedAlgorithm. You can change the ClusterRenderer and the Algorithm using the setAlgorithm(Algorithm<T> algorithm) and setRenderer(ClusterRenderer<T> view) methods of ClusterManager.
You can implement ClusterRenderer to customize the rendering of the clusters. DefaultClusterRenderer provides a good base to start from. By subclassing DefaultClusterRenderer, you can override the defaults.
Related
Currently, I'm implementing drag-n-move feature according to https://medium.com/#ipaulpro/drag-and-swipe-with-recyclerview-6a6f0c422efd and code example from https://github.com/iPaulPro/Android-ItemTouchHelper-Demo/
Here's the outcome
I would like to have background of RecycleView to be visible, when the item is being moved.
Here's changes I had did
Set RecycleView background color to red - recyclerView.setBackgroundColor(Color.RED);
Provide a solid white color, on the item.
<TextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="#dimen/activity_horizontal_margin"
android:textAppearance="?android:attr/textAppearanceMedium" />
<ImageView
android:id="#+id/handle"
android:layout_width="?listPreferredItemHeight"
android:layout_height="match_parent"
android:layout_gravity="center_vertical|right"
android:scaleType="center"
android:src="#drawable/ic_reorder_grey_500_24dp" />
Here's my desired outcome.
However, there's 1 shortcoming. When, there's only a few items in RecyclerView. Those empty area will also be filled with background color. Please see the below screenshot. The below RecyclerView only contain 3 items.
I had try to make RecyclerView's height wrap_content
recyclerView.setLayoutParams(
new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT
)
);
However, it makes no difference.
If the RecyclerView is longer than the area allocated to it on the screen then there is no issue: Just set the background color of the RecyclerView to red. However, if the items in the RecyclerView do not fill up the space allocated to the RecyclerView in the layout then you will see the red background in the empty space. This is what you want to eliminate.
To do this, set a OnGlobalLayoutListener on the RecyclerView and check if there is excess space or not. If there is not excess space, then just set the background color to red; otherwise, create a BitmapDrawable filled with red and properly sized to provide a background to just the items on the screen and not large enough to spill into the excess area.
Here is the code that accomplishes this in RecyclerListFragment of the project you mention.
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final RecyclerView recyclerView = new RecyclerView(container.getContext());
recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
if (recyclerView.getChildCount() == 0) {
return;
}
int lastChildBottom = recyclerView.getChildAt(recyclerView.getChildCount() - 1).getBottom();
if (lastChildBottom >= recyclerView.getHeight()) {
recyclerView.setBackgroundColor(Color.RED);
return;
}
Bitmap bitmap = Bitmap.createBitmap(recyclerView.getWidth(), lastChildBottom, Bitmap.Config.ARGB_8888);
bitmap.eraseColor(Color.RED);
BitmapDrawable d = new BitmapDrawable(getResources(), bitmap);
d.setGravity(Gravity.TOP);
recyclerView.setBackground(d);
recyclerView.invalidate();
}
});
return recyclerView;
}
Here is a video of the effect:
If you have a swipe-to-remove gesture implemented, you will also have to invoke the listener when an item is removed. I also had to set the background color to a non-zero value in onItemClear() of RecyclerListAdapter.
An easier way is to define a drawable that can be set as a background to a RecyclerView that draws only behind the item views.
public class RecyclerViewBackground extends Drawable {
private RecyclerView mRecyclerView;
private Bitmap mBitmap;
private Paint mPaint;
RecyclerViewBackground() {
super();
mPaint = new Paint();
mPaint.setColor(Color.RED);
}
#Override
public void draw(Canvas canvas) {
if (mRecyclerView.getChildCount() == 0) {
return;
}
int bottom = mRecyclerView.getChildAt(mRecyclerView.getChildCount() - 1).getBottom();
if (bottom >= mRecyclerView.getHeight()) {
bottom = mRecyclerView.getHeight();
}
canvas.drawRect(0, 0, canvas.getWidth(), bottom, mPaint);
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(ColorFilter colorFilter) {
}
#Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
public void attachRecyclerView(RecyclerView recyclerView) {
mRecyclerView = recyclerView;
}
}
Attach this Drawable to the RecyclerView as follows:
RecyclerViewBackground bg = new RecyclerViewBackground();
bg.attachRecyclerView(recyclerView);
recyclerView.setBackground(bg);
This will also take care of swipe to delete.
My app have approximately 1,500 markers on a map that are being shown through clusters so as not to overload the application. these bookmarks are currently shown as BitmapDescriptorFactory.defaultMarker ()
However, when I modify the code for each dot to show a custom bitmap with values on the markers, only a few devices have this error, among them LG K10 LTE and some Motorolas. Most appliances work normally.
When i use this function, before i finish rendering all 1500 markers, it crashes with the following error:
"Could not allocate dup blob fd."
In research on this error, it seems to me that this is a memory overflow and that I should store these markers in LRU cache, but I am not able to do this in conjunction with the clusters.
Has anyone had this or did you have an idea / suggestion to solve this problem?
The following is the bitmaps renderer code snippet:
public class OwnRendring extends DefaultClusterRenderer<MyItem> {
OwnRendring(Context context, GoogleMap map, ClusterManager<MyItem> clusterManager) {
super(context, map, clusterManager);
}
protected void onBeforeClusterItemRendered(MyItem item, MarkerOptions markerOptions) {
markerOptions.snippet(item.getSnippet());
markerOptions.title(item.getTitle());
markerOptions.anchor(0.33f, 1f);
markerOptions.infoWindowAnchor(0.33f,0f);
int cor = (item.getPublico() ? cfgCorPostoPublico : cfgCorPostoPrivado);
String preço = item.getTitle().substring(item.getTitle().length() - 5);
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(createMarker(preço, cor)));
super.onBeforeClusterItemRendered(item, markerOptions);
}
protected boolean shouldRenderAsCluster(Cluster cluster) {
return cfgCluster && cluster.getSize() >= cfgClusterMin;
}
}
#Override
public void onCameraIdle() {mClusterManager.cluster();}
private Bitmap createMarker(String text, int color) {
View markerLayout = getLayoutInflater().inflate(R.layout.custom_marker, null);
ImageView markerImage = markerLayout.findViewById(R.id.marker_image);
TextView markerRating = markerLayout.findViewById(R.id.marker_text);
markerImage.setImageResource(R.drawable.pin_shadow);
markerImage.clearColorFilter();
markerImage.getDrawable().mutate().setColorFilter(color, PorterDuff.Mode.MULTIPLY );
markerRating.setText(text);
markerLayout.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
markerLayout.layout(0, 0, markerLayout.getMeasuredWidth(), markerLayout.getMeasuredHeight());
final Bitmap bitmap = Bitmap.createBitmap(
markerLayout.getMeasuredWidth(),
markerLayout.getMeasuredHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
markerLayout.draw(canvas);
return bitmap;
}
Alternatively, I solved my problem by reducing the markers rendering as follows:
I modify the code to force cluster all markers, except those that are visible on the screen.
For this I had to import and modify the original code of the maps-utils library because the rendering only happened after zoom-in or zoom-out, not rendering after the map move.
public class OwnRendring extends DefaultClusterRenderer<MyItem> {
OwnRendring(Context context, GoogleMap map, ClusterManager<MyItem> clusterManager) {
super(context, map, clusterManager);
}
protected void onBeforeClusterItemRendered(MyItem item, MarkerOptions markerOptions) {
... original code ...
}
protected boolean shouldRenderAsCluster(Cluster cluster) {
boolean isInBounds = latLngBounds.contains(cluster.getPosition());
return !isInBounds || (cfgCluster && cluster.getSize() >= cfgClusterMin);
}
}
#Override
public void onCameraIdle() {
mClusterManager.cluster();
latLngBounds = mMap.getProjection().getVisibleRegion().latLngBounds;
}
and in the library maps-utils (class DefaultClusterRenderer) i commented those lines, because they returns without render clusters when user moves the map:
#SuppressLint("NewApi")
public void run() {
// if (clusters.equals(DefaultClusterRenderer.this.mClusters)) {
// mCallback.run();
// return;
// }
... original code ...
}
Obviously this is not the right answer to my question, but it can be a valid alternative for anyone who is having this problem, so far without a conclusive answer.
*** Please someone grammatically correct my answer, because English is not my native language.
I recently implemented clustering in my Android app, and I have managed to set a dynamic image for each marker with Picasso. However, not all markers have an image, and those that do not have it must be displayed with a custom marker my designer has provided.
This is the custom renderer I use:
private class CustomRenderer extends DefaultClusterRenderer<MyMarker>{
private IconGenerator iconGenerator;
private ImageView imageView;
CustomRenderer() {
super(ShopsMapActivity.this, map, clusterManager);
iconGenerator = new IconGenerator(ShopsMapActivity.this);
imageView = new ImageView(ShopsMapActivity.this.getApplicationContext());
imageView.setLayoutParams(new ViewGroup.LayoutParams(PIN_SIZE, PIN_SIZE));
int padding = Global.getXPercentOfWidth(1f);
imageView.setPadding(padding, padding, padding, padding);
iconGenerator.setContentView(imageView);
}
#Override
protected void onBeforeClusterItemRendered(MyMarker item, MarkerOptions markerOptions) {
if(item.hasLogo()) {
// this part works perfectly
loadAsync(imageView, item.getLogo());
try {
Bitmap icon = iconGenerator.makeIcon();
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon));
icon.recycle();
} catch (Exception e) {
e.printStackTrace();
}
}else {
// this is the part that does not work. It should display a custom pin, but instead it displays a small empty white marker that should hold an image from Picasso. Problem is that there is no image for Picasso to download
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(createDefaultIconBitmap()));
}
}
private void loadAsync(final ImageView imageView, final String url) {
Picasso.with(ShopsMapActivity.this)
.load(url)
.into(imageView);
}
}
I am trying to display a blue line next to a block of text, pretty much like this:
Here's my code:
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="#drawable/blue_line" />
blue_line is a jpg file. a blue rectangle. it displays in its original size regardless of the text in the textview. how can i adjust its height dynamically according to the height of the text? like make it shorter when theres little amount of text and longer when there's more text....
You can try doing it in code by setting bounds for the image
textView1.getViewTreeObserver()
.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Drawable img = ActivityName.this.getContext().getResources().getDrawable(
R.drawable.blue_line);
img.setBounds(0, 0, img.getIntrinsicWidth() * textView1.getMeasuredHeight() / img.getIntrinsicHeight(), textView1.getMeasuredHeight());
textView1.setCompoundDrawables(img, null, null, null);
textView1.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
The best way to do this is to wrap your drawable in an xml drawable file and set it as a drawable in your text view as follows:
Drawable XML File:
<?xml version="1.0" encoding="utf-8"?>
<bitmap
xmlns:android="http://schemas.android.com/apk/res/android"
android:scaleType="fitXY"
android:src="#drawable/total_calories"/>
TextView in XML:
<TextView
android:id="#+id/title_total_cal"
style="#style/title_stats_textview"
android:drawableLeft="#drawable/total_calories_drawable"/>
Try as below...
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<ImageView
android:id="#+id/imageView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:src="#drawable/btndr" />
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="#+id/imageView" />
</RelativeLayout>
Simply, Keep your image as 9patch drawable.
You can add android:drawableLeft="#drawable/checkmark" to your textview. You can also set drawablePadding to keep the textview organized.
android:drawableLeft="#drawable/button_icon"
android:drawablePadding="2dip"
Here is the link to create 9patch drawable
<TextView android:text="#string/txtUserName"
android:id="#+id/txtUserName"
android:layout_width="160dip"
android:layout_height="60dip"
android:layout_gravity="center"
android:drawableLeft="#drawable/button_icon"
android:drawablePadding="2dip"
/>
Unfortunately, setBounds was not working for me so I had to do a workaround.
// Turn wanted drawable to bitmap
Drawable dr = getResources().getDrawable(R.drawable.somedrawable);
Bitmap bitmap = ((BitmapDrawable) dr).getBitmap();
// I had a square image with same height and width so I needed only TextView height (getLineHeight)
int size = textView1.getLineHeight();
Drawable d = new BitmapDrawable(getResources(), Bitmap.createScaledBitmap(bitmap, size, size, true));
textView1.setCompoundDrawables(d, null, null, null);
// Now we can set some spacing between text and image
textView1.setCompoundDrawablePadding(10);
It is not the best solution regarding performance because new bitmap is created, but still works.
You can draw a line with your desired height on drawable canvas and set as left drawable of your TextView. check this out:
public class FullHeightLineDrawable extends Drawable {
private Paint mPaint;
private int mHeight;
public FullHeightLineDrawable(int height) {
mPaint = new Paint();
mPaint.setColor(CompatUtils.getColor(R.color.colorAccent));
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(15);
mHeight = height;
}
#Override
public void draw(Canvas canvas) {
canvas.drawLine(0, -mHeight, 0, mHeight, mPaint);
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(ColorFilter colorFilter) {
}
#Override
public int getOpacity() {
return 0;
}
}
Usage:
final Drawable drawable = new FullHeightLineDrawable(getHeight());
mTextView.setTextColor(watchingColor);
mTextView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null);
I abstract out this method. It works when the drawable is on the left of the TextView,dynamically scaling. If drawable is on the right side,this method doesn't work, needing to figure out why, but you can just directly use textView.setcompounddrawableswithintrinsicbounds() with the right-size Drawable resource
#RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
public static void ajustCompoundDrawableSizeWithText(final TextView textView, final Drawable leftDrawable, final Drawable topDrawable, final Drawable rightDrawable, final Drawable bottomDrawable) {
textView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if(leftDrawable != null){
leftDrawable.setBounds(0, 0, (int)textView.getTextSize(), (int)textView.getTextSize());
}
if(topDrawable != null){
topDrawable.setBounds(0, 0, (int)textView.getTextSize(), (int)textView.getTextSize());
}
if(rightDrawable != null){
rightDrawable.setBounds(0, 0, (int)textView.getTextSize(), (int)textView.getTextSize());
}
if(bottomDrawable != null){
bottomDrawable.setBounds(0, 0, (int)textView.getTextSize(), (int)textView.getTextSize());
}
textView.setCompoundDrawables(leftDrawable, topDrawable, rightDrawable, bottomDrawable);
textView.removeOnLayoutChangeListener(this);
}
});
}
I Created a class that extends TextView, and resize the drawables in onPreDrawListener.
public class MyTextView extends AppCompatTextView {
public MyTextView(Context context, AttributeSet attrs, int style) {
super(context, attrs, style);
fitCompoundDrawableToLineHeight();
}
private void fitCompoundDrawableToLineHeight() {
OnPreDraw.run(this, () -> {
final Drawable[] cd = getCompoundDrawables();
Arrays.stream(cd)
.filter(drawable -> drawable != null)
.forEach(d -> d.setBounds(0, 0, getLineHeight(), getLineHeight()));
setCompoundDrawables(cd[0], cd[1], cd[2], cd[3]);
});
}
}
// Convenience class that abstracts onPreDraw logic
public class OnPreDraw {
public static void run(View view, Runnable task) {
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
if (!view.getViewTreeObserver().isAlive()) {
return true;
}
view.getViewTreeObserver().removeOnPreDrawListener(this);
task.run();
return true;
}
});
}
}
You need to make a one horizontal XML layout that has the blue line left and a textview right.
Than use that layout like an item and make a ListView of those items. Something like here, but a bit simpler
In this scenario I want to draw a bitmap on Google Maps using gms v2 and each user position update enforces bitmap update. Currently I use following code snippet:
public void init(){
result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
canvas = new Canvas(result);
}
public void update(){
// draw on canvas ...
draw(result);
}
public void draw(Bitmap modifiedBmp) {
if (overlay != null) {
overlay.remove();
}
BitmapDescriptor descriptor = BitmapDescriptorFactory.fromBitmap(modifiedBmp);
overlay = map.addGroundOverlay(new GroundOverlayOptions().image(descriptor).positionFromBounds(bounds).zIndex(100));
}
The update() method is called each second. I find this approach extremely inefficient and I'm searching for a better solution (i.e. that doesn't require to add/remove overlay after each update). Drawing primitives on map using addPolygon(...) and addPolyline(...) isn't an option because I require drawing capabilities not present in standard api.
One optimization could be to check if the new position is the same as the old one and don't redraw if that is the case. Also I don't think that the descriptor need to be created each time.
Another approach for moving markers is described here. It's the one from the official sample.
I'm not sure if this is what you want, but this is how I used custom bitmaps in Google Maps.
The marker code:
BitmapDescriptor iconBitmap = BitmapDescriptorFactory
.fromResource(R.drawable.item_map_marker);
MarkerOptions options = new MarkerOptions();
options.position(new LatLng(hs.lat, hs.lng));
options.title(hs.sitename);
options.snippet(hs.street + ", " + hs.suburb);
options.icon(iconBitmap);
mMap.addMarker(options);
The tooltip adapter:
public class MyInfoWindowAdapter implements InfoWindowAdapter {
public interface OnRenderCustomInfoWindow {
public void onRender(Marker marker, View mWindow);
}
private View mWindow;
private OnRenderCustomInfoWindow mRenderer;
public MyInfoWindowAdapter(Context context,
OnRenderCustomInfoWindow onRender) {
mRenderer = onRender;
mWindow = LayoutInflater.from(context).inflate(
R.layout.view_services_map_infowindow, null);
}
#Override
public View getInfoWindow(Marker marker) {
mRenderer.onRender(marker, mWindow);
return mWindow;
}
#Override
public View getInfoContents(Marker marker) {
return null;
}
}