I have a form which I'm dynamically generating from data I receive from a web service. This web service provides images which need to be used in the creation of input elements. I'm having difficuly in setting the progressDrawable of a RatingBar. Though XML I'm able to apply a custom image using the following as the progressDrawable:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+android:id/background" android:drawable="#drawable/custom_star" />
<item android:id="#+android:id/secondaryProgress" android:drawable="#drawable/custom_star" />
<item android:id="#+android:id/progress" android:drawable="#drawable/custom_star" />
</layer-list>
where custom_star is a simple .png image, and with #android:style/Widget.RatingBar as the RatingBar style. This works fine:
but I'm wanting to change custom_star dynamically.
In code, I have tried setting the progress drawable using a bitmap directly:
Drawable d = new BitmapDrawable(getResources(), downloadedImage);
ratingBar.setProgressDrawable(d);
and also by constructing a layer-list:
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] {
getResources().getDrawable(R.drawable.custom_star),
getResources().getDrawable(R.drawable.custom_star),
getResources().getDrawable(R.drawable.custom_star)
});
layerDrawable.setId(0, android.R.id.background);
layerDrawable.setId(1, android.R.id.secondaryProgress);
layerDrawable.setId(2, android.R.id.progress);
ratingBar.setProgressDrawable(layerDrawable);
Neither works for me; both result in the custom_star drawable appearing once, stretched by the dimensions of the RatingBar:
Any ideas?
Update:
Luksprog's answer below has made an improvement, but I'm still having a couple of issues. Now, the star drawable is not stretched and the value can be set by touch, but it appears as so with 3/5 selected:
and 5/5 selected:
I believe the scaling of the images can be fixed with a few tweaks, but annoyingly the secondaryProgress drawable doesn't seem to be set - the drawable used for the greyed out not-selected stars. Without that, it's not very usable.
Any ideas?
When using the default progressDrawable or a progressDrawable set through a theme all will be ok as in the constructor for the RatingBar(its superclass ProgressBar to be more precise) widget a method will be called to "make tiles" from that drawable. When using the setProgressDrawable method this doesn't happen and if you pass a simple BitmapDrawable or a LayerDrawable(with simple BitmapDrawables) that Drawable will simply be stretched to cover the widget's background area(what you see know).
In order to make it work you would need to manually do what the RatingBar does at start, create the tiles along with the ClipDrawables that it uses. I've written a method for this, following the source code of the ProgressBar widget:
private Drawable buildRatingBarDrawables(Bitmap[] images) {
final int[] requiredIds = { android.R.id.background,
android.R.id.secondaryProgress, android.R.id.progress };
final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
Drawable[] pieces = new Drawable[3];
for (int i = 0; i < 3; i++) {
ShapeDrawable sd = new ShapeDrawable(new RoundRectShape(
roundedCorners, null, null));
BitmapShader bitmapShader = new BitmapShader(images[i],
Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
sd.getPaint().setShader(bitmapShader);
ClipDrawable cd = new ClipDrawable(sd, Gravity.LEFT,
ClipDrawable.HORIZONTAL);
if (i == 0) {
pieces[i] = sd;
} else {
pieces[i] = cd;
}
}
LayerDrawable ld = new LayerDrawable(pieces);
for (int i = 0; i < 3; i++) {
ld.setId(i, requiredIds[i]);
}
return ld;
}
Then you would use the LayerDrawable returned by this method with the setProgressDrawable method. The RatingBar set its width multiplying the width of one of the state bitmaps with the number of stars, so in order to show the right amount of stars this has to be calculated as well.
Your ids are wrong, you have to set
#android:id/background
#android:id/secondaryProgress
#android:id/progress
You can also define a drawable in xml containing your customization
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+android:id/background"
android:drawable="#drawable/star_empty" />
<item android:id="#+android:id/secondaryProgress"
android:drawable="#drawable/star_empty" />
<item android:id="#+android:id/progress"
android:drawable="#drawable/star_full" />
</layer-list>
And you can use setMax on the RatingBar object to set the maximum amount of stars that can be displayed
It's much easier with such solution:
RatingBar ratingBar = new RatingBar(new ContextThemeWrapper(context, R.style.AppTheme_RatingBar), null, 0);
Related
I got the color from the button background and store as a String in the database. Later I want to use this color String in my recyclerView adapter to set the color of my TextView. Below is my code:
#Override
public void onBindViewHolder(NoteListAdapter.NoteListHolder holder, int position) {
current = data.get(position);
final String text = current.getText();
final String get_tag_text = current.getTag();
final String get_tag_color = current.getTag_color();
int[] colors = {Color.parseColor(get_tag_color)};
GradientDrawable gd = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, colors);
holder.note_text.setText(text);
holder.tv_tag_text.setBackground(gd);
holder.tv_tag_text.setText(get_tag_text);
}
The error I got is "Unknown color". The saved color format in the database is (The saved color format is android.graphics.drawable.GradientDrawable#d1790a4)
Below is the code to get the color from a button background drawable file and also my button xml code
color = (GradientDrawable) tag_watchlist.getBackground().mutate();
tag_color= color.toString();
<Button
android:id="#+id/tag_watch"
style="#style/tag_buttons"
android:background="#drawable/watchlist_button"
android:text="Watchlist" />
drawable file code for the button background
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
android:padding="10dp">
<solid android:color="#a40ce1"/>
<corners android:radius="10dp"/>
</shape>
Can anyone tell me how to resolve this issue??
Edited answer
You are getting exception Caused by: java.lang.IllegalArgumentException: Unknown color mean that you are not passing the color in supported formats to method Color.parseColor.
Make sure you pass the values in following format
#RRGGBB
#AARRGGBB
Here is the valid example
Color.parseColor("#FF4081")
For more information look at documentation Color.parseColor
As per your requirement, you can achieve this API level 24 onward. If you are using current minSdkVersion 24, try below
Change your model class to save color as Integer instead String.
GradientDrawable gradientDrawable = (GradientDrawable) tag_watchlist.getBackground().mutate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
int color = gradientDrawable.getColor().getDefaultColor();
Log.d("TAG","Color is :"+color);
current.setTagColor(color); // where current is your model class
}
To get the color back from model
int color = current.getTagColor();
You need to provide at least two colors for the GradientDrawable the startColor and the endColor
It will probably throw an exception java.lang.IllegalArgumentException: needs >= 2 number of colors with this code:
int[] colors = {Color.parseColor(get_tag_color)};
GradientDrawable gd = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, colors);
Change your code with this:
int[] colors = {Color.parseColor(start_color), Color.parseColor(end_color)};
GradientDrawable gd = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, colors);
If you have get_tag_color for both of your startColor and endColor then replace accordingly but that won't be helpful with GradientDrawable.
I want to color e.g. 20 percent of layout background and after some time color 40 percent of it and so on.
How can I achieve this in android ?
You can start off with a ClipDrawable. This will clip another drawable — for instance, a ShapeDrawable — based on the drawable level.
Then in your timer callback:
int level; // from 0 to 10000 = 100%
view.getBackground.setLevel(level);
Drawable#setLevel
EDIT: Here's an example:
Define the shape drawable in /res/drawable. Call it bkgd_shape.xml.
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#color/background"/>
</shape>
Define the background drawable in /res/drawable. Let's call it bkgd_level.xml:
<?xml version="1.0" encoding="utf-8"?>
<clip
xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="#drawable/bkgd_shape"
android:clipOrientation="horizontal"
android:gravity="left|clip_horizontal|fill_vertical"/>
You might be able to put a color in directly for the drawable source, but I haven't tried it.
Set it as the background of your layout:
android:background="#drawable/bkgd_level"
Call setLevel on the drawable:
int level; // from 0 to 10000 = 100%
view.getBackground.setLevel(level);
You can put any type of view in the background and update it's with and set the color you'd like it to have dynamically.
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int width = dm.widthPixels;
int height = dm.heightPixels;
Expanding across the width then, just go by percentage.
int percentChange = .2; //Update this accordingly, put in its own function possibly
int backgroundWidth = width * percentChange;
For example, using an imageView in the background:
ImageView background = (ImageView) findViewById(R.id.expandingBackground);
background.requestLayout();
background.getLayoutParams().width = backgroundWidth;
Hope that helps, good luck.
I have a state list drawable, and i want to get a specific drawable from the state list drawable:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:kplus="http://schemas.android.com/apk/res-auto">
<item kplus:key_type_space_alt="true" android:state_pressed="true" android:drawable="#drawable/space_1_pressed" />
<item kplus:key_type_space_alt="true" android:drawable="#drawable/space_1_normal" />
<!-- TopNav keys. -->
<item kplus:key_type_topnav="true" android:state_pressed="true" android:drawable="#drawable/tab_down" />
<item kplus:key_type_topnav="true" android:state_selected="true" android:drawable="#drawable/tab_down" />
<item kplus:key_type_topnav="true" android:drawable="#drawable/tab_normal" />
<!-- TopRow keys. -->
<item kplus:key_type_toprow="true" android:state_pressed="true" android:drawable="#drawable/numeric_presseed" />
<item kplus:key_type_toprow="true" android:drawable="#drawable/numeric_normal" />
</selector>
I select the correct drawable state for each key, something like this:
if (keyIsNumbers) {
if (KPlusInputMethodService.sNumbersState == 2) {
drawableState = mDrawableStatesProvider.KEY_STATE_TOPNAV_CHECKED;
}
}
Now the states are defined like this:
KEY_STATE_TOPNAV_NORMAL = new int[] {keyTypeTopNavAttrId};
KEY_STATE_TOPNAV_PRESSED = new int[] {keyTypeTopNavAttrId, android.R.attr.state_pressed};
KEY_STATE_TOPNAV_CHECKED = new int[] {keyTypeTopNavAttrId, android.R.attr.state_selected};
Now my question is how to extract the correct drawable for each state ? I need to get the 9patch padding of the drawable, because if the state have different padding on 9patch it will get the padding only for the top drawable, and i want to set the padding manually for each key (drawable.getPadding(rect)).
There is no public API to get the drawable from the state.
There are some methods in StateListDrawable but they are #hide with the comment "pending API council".
You can invoke them by reflection... but it's at your own risk !!!. (it may change in future releases)
Those methods are :
getStateDrawableIndex
getStateDrawable
Here is how to proceed (exceptions omitted) :
int[] currentState = view.getDrawableState();
StateListDrawable stateListDrawable = (StateListDrawable)view.getBackground();
Method getStateDrawableIndex = StateListDrawable.class.getMethod("getStateDrawableIndex", int[].class);
Method getStateDrawable = StateListDrawable.class.getMethod("getStateDrawable", int.class);
int index = (int) getStateDrawableIndex.invoke(stateListDrawable,currentState);
Drawable drawable = (Drawable) getStateDrawable.invoke(stateListDrawable,index);
now it is the way to get specific drawable without reflection:
StateListDrawable pressedDrawable = (StateListDrawable) this.mPressed;
final int state = android.R.attr.state_pressed;
final int index = pressedDrawable.findStateDrawableIndex(new int[]{state});
if (index >= 0) {
Drawable drawable = pressedDrawable.getStateDrawable(index);
if (drawable instanceof GradientDrawable) {
((GradientDrawable) drawable).setCornerRadius(mRoundConerRadius);
}
}
Currently I am trying to create a custom Seekbar without using xml as the images for the background and progress bar need to be changed. This is what I have at the moment:
Drawable drawable = new BitmapDrawable(appState.images.get(Integer.parseInt(image)));
this.setBackgroundDrawable(drawable);
Drawable drawable2 = new BitmapDrawable(appState.images.get(Integer.parseInt(image_a)));
this.setProgressDrawable(drawable2);
this.setProgress(0);
But the progressDrawable is just set to the entire size of Seekbar and when I move the thumb from side to side nothing happens. Does anybody have any ideas as I am completely stumped.
So this previous answer does the trick for you:
Changing the size of the seekbar programmatically but cant get the thumb to be larger than the bar
From the original asker:
In the end I am using this code:
public void setSeekBarImages(){
Drawable drawable = new
BitmapDrawable(appState.images.get(Integer.parseInt(image_a)));
ClipDrawable clip = new ClipDrawable(drawable,
Gravity.LEFT,ClipDrawable.HORIZONTAL);
Drawable drawable2 = new
BitmapDrawable(appState.images.get(Integer.parseInt(image)));
InsetDrawable d1= new InsetDrawable(drawable2,5,5,5,5);
//the padding u want to use
setThumb(null);
LayerDrawable mylayer = new LayerDrawable(new Drawable[]{d1,clip});
setProgressDrawable(mylayer);
setProgress(0);
}
Here is what I want to do: I have a widget and I want to set its background depending upon users choice of colors. It has to be a gradient. The backgound is to be set by setting background of the linearLayout. For testing, I did it for a dummy-background as:
remoteViews.setInt(R.id.layout, "setBackgroundResource", R.drawable.widget_background);
I have seen this question: Call setImageDrawable from RemoteViews but I am not able to understand how to implement. I even can't find setXYZ() as mentioned there. Here is what I have tried until now:
Making a gradient drawable dynamically. In this approach, I am not able to set the background beacause AFAIK all the methods take id of the drawable and I have a drawable object.
Tried ImageView as a background (before LinearLayout). It does not provide proper margin to widget. Since the widget text is dynamic, sometimes it goes out of the imageView which is not what I want
Making a bg.xml in which I have:
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<padding
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp" />
<corners
android:bottomLeftRadius="7dp"
android:bottomRightRadius="7dp"
android:topLeftRadius="7dp"
android:topRightRadius="7dp" />
</shape>
Now I am totally confused and stuck. Can someone help(probably more of code and less of links) ASAP? Also, please don't close this question as already asked.
Tried ImageView as a background (before LinearLayout). It does not provide proper margin to widget. Since the widget text is dynamic, sometimes it goes out of the imageView which is not what I want
I'm not entirely sure what you mean, but if you use a FrameLayout / RelativeLayout for your root layout, and then put the ImageView inside with fill parent, your image should be exactly the size of your widget.
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_margin="6dp" >
<ImageView
android:id="#+id/widgetBg"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="fitXY" />
// Other views
</FrameLayout>
Also, this is what I'm doing to dynamically change the color & alpha of a rounded corner gradient background. Then use setImageViewBitmap( ) to apply to the imageview. Probably there is a better way.
public static Bitmap getBackground(int bgColor, int width, int height, Context context) {
try {
// convert to HSV to lighten and darken
int alpha = Color.alpha(bgColor);
float[] hsv = new float[3];
Color.colorToHSV(bgColor, hsv);
hsv[2] -= .1;
int darker = Color.HSVToColor(alpha, hsv);
hsv[2] += .3;
int lighter = Color.HSVToColor(alpha, hsv);
// create gradient useng lighter and darker colors
GradientDrawable gd = new GradientDrawable(
GradientDrawable.Orientation.LEFT_RIGHT,new int[] { darker, lighter});
gd.setGradientType(GradientDrawable.RECTANGLE);
// set corner size
gd.setCornerRadii(new float[] {4,4,4,4,4,4,4,4});
// get density to scale bitmap for device
float dp = context.getResources().getDisplayMetrics().density;
// create bitmap based on width and height of widget
Bitmap bitmap = Bitmap.createBitmap(Math.round(width * dp), Math.round(height * dp),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
gd.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
gd.draw(canvas);
return bitmap;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}