Drawable tinting code works for Vectors but not PNGs - android

So I am running into a weird issue... I have made some code to tint a Drawable, and it is working on all Android versions for Vector assets, but not for regular PNG assets. The code is below:
public class TintHelper {
private Context mContext;
public TintHelper(Context context) {
mContext = context;
}
public Drawable getTintedDrawableFromResource(int resourceID, ColorStateList colorStateList) {
Drawable original = AppCompatDrawableManager.get().getDrawable(mContext, resourceID);
return performTintOnDrawable(original, colorStateList);
}
private Drawable performTintOnDrawable(Drawable drawable, ColorStateList colorStateList) {
Drawable tinted = DrawableCompat.wrap(drawable);
DrawableCompat.setTintList(tinted, colorStateList);
return tinted;
}
}
When I specify the resource ID of a vector asset, the code works perfectly and the image is tinted when pressed, but when I use a regular PNG, there is no tint applied when the icon is pressed. If anyone has any ideas of why this doesn't work, please post an alternative method that could potentially support both asset types.
Thanks in advance!

It is work for PNG in my environment.
set like this:
int resourceID = R.drawable.ic_launcher;
TintHelper tintHelper = new TintHelper(this);
Drawable drawable = tintHelper.getTintedDrawableFromResource(resourceID,
ContextCompat.getColorStateList(this, R.color.colors));
ImageView imageView = (ImageView) findViewById(R.id.image);
imageView.setImageDrawable(drawable);
colors.xml is like this:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:color="#android:color/holo_red_dark"/>
<item android:state_selected="true" android:color="#android:color/holo_red_dark"/>
<item android:state_pressed="true" android:color="#android:color/holo_red_dark"/>
<item android:color="#android:color/white"/>
</selector>

I found the problem. Essentially, DrawableCompat.setTintList() is not working as expected on Android 21 and above. This is due to their implementation not calling invalidate() when there are state changes. More details can be read on this bug report.
To get this tinting code working for all platforms and all resource types, I needed to create a custom ImageView class as shown below:
public class StyleableImageView extends AppCompatImageView {
public StyleableImageView(Context context) {
super(context);
}
public StyleableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public StyleableImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
// This is the function to override...
#Override
protected void drawableStateChanged() {
super.drawableStateChanged();
invalidate(); // THE IMPORTANT LINE
}
}
Hopefully this helps someone that has had to deal with a similar situation.

Related

Android Custom Icon Font only works if I call setTypeface after View is inflated

I have a custom font file, say myfont.ttf in assets/fonts/
I have created a custom View like this
public class IconFontView extends AppCompatTextView {
public IconFontView(Context context) {
super(context);
applyIconFonts(context);
}
public IconFontView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
applyIconFonts(context);
}
public IconFontView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
applyIconFonts(context);
}
private void applyIconFonts(final Context context) {
final Typeface iconFont = Typeface.createFromAsset(context.getAssets(), "fonts/myfont.ttf");
setTypeface(iconFont);
}
}
in XML:
<com.smule.singandroid.customviews.IconFontView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="400dp"
android:text="#string/icontext"/> <!-- my unicode for the specific icon -->
strings.xml:
<string name="icontext"></string>
This way, I can see the preview perfectly fine.
But, if I enter this fragment, I see nothing but an empty view. (using "Show layout bounds" to tell you that this view exists there, just not drawing)
HOWEVER, if I add this font to my styles.xml like this
<style name="IconFont">
<item name="fontPath">fonts/myfont.ttf</item>
</style>
and apply it in layout xml like this
<com.smule.singandroid.customviews.IconFontView
style="#style/IconFont"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="400dp"
android:text="#string/icontext"/> <!-- my unicode for the specific icon -->
This not only displays correct preview, but also shows the icon perfectly fine.
This has same effect as doing something like
mIconFontview.setText(R.string.icontext);
mIconFontView.setTypeFace(...);
Why would this work this way?
Then there would be no reason to create IconFontView at all. Might as well just use TextView.

Custom Seekbar with masking effect with two drawables?

I am using a custom seekbar to show a graph. I had done till this. I am showing this graph by applying a background drawable to the seekbar. Now my problem is, I need to set the blue one as progress drawable and need to set the background of seekbar as the red graph. So that when progress happens thumb moves over red the area where thumb passed should be changed to blue color like a masking effect. Can any one tell the best possible way to do this. My pictures are shown below
After reading all the questions and answers I hope this should be your scenario to get your thing done...
1.Create two graphs
As per your logic.
2.Generate two drwables from the particular bitmaps....
Drawable G_bg = new BitmapDrawable(Red graph bitmap);
Drawable G_pg = new BitmapDrawable(Blue graph bitmap);
3.And then customize your seek bar using layer list created through the java code.
ClipDrawable c=new ClipDrawable(G_pg, Gravity.LEFT,ClipDrawable.HORIZONTAL);
LayerDrawable ld =new LayerDrawable (new Drawable[]{G_bg,c});
4.Apply this layer list to your seekbar.
Graphbar.setProgressDrawable(ld);
This should work like you wanted....Thanksss
Is this not what you wanted?
my_seek_bar_progress.xml:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="#android:id/background"
android:drawable="#drawable/red_graph"/>
<item android:id="#android:id/progress">
<clip android:drawable="#drawable/blue_graph" />
</item>
</layer-list>
in Fragment or Activity layout:
<com.example.seekbaroverlay.MySeekBar
android:id="#+id/mySeekBar1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100" />
MySeekBar.java:
public class MySeekBar extends SeekBar {
public MySeekBar(Context context) {
this(context, null);
}
public MySeekBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MySeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setProgressDrawable(context.getResources().getDrawable(R.drawable.my_seek_bar_progress));
setThumb(context.getResources().getDrawable(R.drawable.my_thumb));
}
}
You should use a custom progressDrawable for your SeekBar. See this blog post for a great tutorial.
You can create a custom view.Override it's onTouch() method to change position of thumb.Also override it's onDraw() method and first draw red graph as background of your view and then the blue one from position that corresponds to the position of thumb.

how to change number picker style in android?

I want to use the NumberPicker component-widget but Instead in the default Holo theme I need to replace the blue color with orange since that is the default color in my styling.
How can I replace the blue color and the color of the numbers,and keep all of the functionality of the component?
thanks
Unfortunately, you can't style it. The styles and styling attributes for NumberPicker are not present in the public API, therefore you can't set them and change the default look. You can only select between light and dark theme.
As a solution I would suggest to use android-numberpicker library instead. The library is basically a port of NumberPicker extracted from Android source codes. But it's better than that, it also backports NumberPicker to Android 2.x. The library can be easily styled.
To style the divider adjust NPWidget.Holo.NumberPicker style and its selectionDivider and selectionDividerHeight attributes.
To style the text adjust NPWidget.Holo.EditText.NumberPickerInputText style.
Preview of a customized number picker:
<NumberPicker
android:id="#+id/np"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_centerHorizontal="true"
android:background="#drawable/drawablenp"
android:layout_centerVertical="true"/>
Create the background xml in the drawable folder named "drawablenp.xml"
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#707070"
android:centerColor="#f8f8f8"
android:endColor="#707070"
android:angle="270"/>
</shape>
Make copy of library/res/drawable-*/numberpicker_selection_divider.9.png and name then, for example, custom_np_sd.9.png.
Override default NumberPicker style via activity theme:
<style name="AppTheme" parent="#style/Holo.Theme">
<item name="numberPickerStyle">#style/CustomNPStyle</item>
</style>
<style name="CustomNPStyle" parent="#style/Holo.NumberPicker">
<item name="selectionDivider">#drawable/custom_np_sd</item>
</style>
And apply #style/AppTheme as activity theme.
I faced this problem too. I really want to have nice NumberPicker UI. All answer in this question worked but very limited. I almost create my own RecylerView to create the NumberPicker I want. Apparently I found neat library which is very robust. Here is the link https://github.com/Carbs0126/NumberPickerView
Not trying to answer the question here. Just want to help someone with the same problem as I am.
I face this problem too, I use reflect to change the style
public class MyNumberPicker extends NumberPicker {
public MyNumberPicker(Context context) {
super(context);
setNumberPickerDivider();
}
public MyNumberPicker(Context context, AttributeSet attrs) {
super(context, attrs);
setNumberPickerDivider();
}
public MyNumberPicker(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setNumberPickerDivider();
}
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public MyNumberPicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setNumberPickerDivider();
}
#Override
public void addView(View child) {
super.addView(child);
updateView(child);
}
#Override
public void addView(View child, int index, android.view.ViewGroup.LayoutParams params) {
super.addView(child, index, params);
updateView(child);
}
#Override
public void addView(View child, android.view.ViewGroup.LayoutParams params) {
super.addView(child, params);
updateView(child);
}
public void updateView(View view) {
if (view instanceof EditText) {
EditText et = (EditText) view;
et.setTextColor(ContextCompat.getColor(getContext(), R.color.font_content));
et.setTextSize(16);
}
}
private void setNumberPickerDivider() {
try {
{
Field field = NumberPicker.class.getDeclaredField("mSelectionDivider");
field.setAccessible(true);
field.set(this, ContextCompat.getDrawable(getContext(), R.drawable.horizontal_divider));
}
{
Field field = NumberPicker.class.getDeclaredField("mSelectionDividerHeight");
field.setAccessible(true);
field.set(this, 1);
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}

Difference between a clickable ImageView and ImageButton

I'm just wondering if there is any significant difference between an ImageView that's set to be clickable, compared with an ImageButton?
Is there any reason for using one over the other? Is there any restriction on the drawable for an ImageButton that leaves ImageView as the only possible option?
Will I possibly lose any functionality of a button if I opt for a clickable ImageView over ImageButton?
There's no differences, except default style. ImageButton has a non-null background by default.
EDIT: Also, ImageButton.onSetAlpha() method always returns false, scaleType is set to center and it's always inflated as focusable.
Here's ImageButton's default style:
<style name="Widget.ImageButton">
<item name="android:focusable">true</item>
<item name="android:clickable">true</item>
<item name="android:scaleType">center</item>
<item name="android:background">#android:drawable/btn_default</item>
</style>
ImageButton is inherited from ImageView
public class ImageButton extends ImageView {
public ImageButton(Context context) {
this(context, null);
}
public ImageButton(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.imageButtonStyle);
}
public ImageButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setFocusable(true);
}
#Override
protected boolean onSetAlpha(int alpha) {
return false;
}
#Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(ImageButton.class.getName());
}
#Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(ImageButton.class.getName());
}
as #Micheal describe i just add details to his answer
The effect of a button click when I click is there for imagebutton but not for imageView.
If you want, you can give the button clic effect as a background. ImageButton has an effect by default.

Rotating image. Animation list or animated rotate? (Android)

I want to create a rotating progress image, and wonder what's the best way to proceed. I can make it work with an animation list with for example 12 images changing every 100ms. This works fine, but it's quite tedious to create 12 images or for every size and resolution:
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
<item android:drawable="#drawable/ic_loading_grey_on_black_01" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_02" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_03" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_04" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_05" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_06" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_07" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_08" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_09" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_10" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_11" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_12" android:duration="100" />
I suppose that an easier solution is to use one image per resolution, but rather rotate it for each frame. In the platform resources (android-sdk-windows/platforms...) I found something called animated-rotate in the file drawable/search_spinner.xml, but if I copy the code get a compiler error complaining about android:framesCount and android:frameDuration (Google APIs 2.2 in Eclipse):
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="#drawable/spinner_black_20"
android:pivotX="50%"
android:pivotY="50%"
android:framesCount="12"
android:frameDuration="100" />
I have also tried using a repeating rotate animation (using in the anim resource folder), but I actually prefer the look of the animation list version.
What is the recommended way of solving this problem?
Rotate drawable suggested by Praveen won't give you control of frame count. Let's assume you want to implement a custom loader which consists from 8 sections:
Using animation-list approach, you need to create 8 frames rotated by 45*frameNumber degrees manually. Alternatively, you can use 1st frame and set rotation animation to it:
File res/anim/progress_anim.xml:
<?xml version="1.0" encoding="utf-8"?>
<rotate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="infinite" />
File MainActivity.java
Animation a = AnimationUtils.loadAnimation(getContext(), R.anim.progress_anim);
a.setDuration(1000);
imageView.startAnimation(a);
This will give you smooth animation instead of 8-stepped. To fix this we need to implement custom interpolator:
a.setInterpolator(new Interpolator() {
private final int frameCount = 8;
#Override
public float getInterpolation(float input) {
return (float)Math.floor(input*frameCount)/frameCount;
}
});
Also you can create a custom widget:
File res/values/attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ProgressView">
<attr name="frameCount" format="integer"/>
<attr name="duration" format="integer" />
</declare-styleable>
</resources>
File ProgressView.java:
public class ProgressView extends ImageView {
public ProgressView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setAnimation(attrs);
}
public ProgressView(Context context, AttributeSet attrs) {
super(context, attrs);
setAnimation(attrs);
}
public ProgressView(Context context) {
super(context);
}
private void setAnimation(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ProgressView);
int frameCount = a.getInt(R.styleable.ProgressView_frameCount, 12);
int duration = a.getInt(R.styleable.ProgressView_duration, 1000);
a.recycle();
setAnimation(frameCount, duration);
}
public void setAnimation(final int frameCount, final int duration) {
Animation a = AnimationUtils.loadAnimation(getContext(), R.anim.progress_anim);
a.setDuration(duration);
a.setInterpolator(new Interpolator() {
#Override
public float getInterpolation(float input) {
return (float)Math.floor(input*frameCount)/frameCount;
}
});
startAnimation(a);
}
}
File activity_main.xml:
<com.example.widget.ProgressView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_progress"
app:frameCount="8"
app:duration="1000"/>
File res/anim/progress_anim.xml: listed above
You have to create a drawable xml file like below:
Code:
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:pivotX="50%" android:pivotY="50%" android:fromDegrees="0"
android:toDegrees="360" android:drawable="#drawable/imagefile_to_rotate" />
I found vokilam's answer to be the best one to create a nice stepped/staggered animation. I went for his final suggestion and made a custom widget, the only problem I encountered was that setting visibility wouldn't work because it was animated and thus would always be visible...
I adjusted his code (ProgressView.java which I renamed StaggeredProgress.java) like this:
public class StaggeredProgress extends ImageView {
private Animation staggered;
public StaggeredProgress(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setAnimation(attrs);
}
public StaggeredProgress(Context context, AttributeSet attrs) {
super(context, attrs);
setAnimation(attrs);
}
public StaggeredProgress(Context context) {
super(context);
}
private void setAnimation(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.StaggeredProgress);
int frameCount = a.getInt(R.styleable.StaggeredProgress_frameCount, 12);
int duration = a.getInt(R.styleable.StaggeredProgress_duration, 1000);
a.recycle();
setAnimation(frameCount, duration);
}
public void setAnimation(final int frameCount, final int duration) {
Animation a = AnimationUtils.loadAnimation(getContext(), R.anim.progress_anim);
a.setDuration(duration);
a.setInterpolator(new Interpolator() {
#Override
public float getInterpolation(float input) {
return (float)Math.floor(input*frameCount)/frameCount;
}
});
staggered = a;
//startAnimation(a);
}
#Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
if( visibility == View.VISIBLE )
startAnimation(staggered);
else
clearAnimation();
}
}
This way setting the view's visibility starts and stops the animation as required...Many thanks again to vokilam!
see examples here
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/index.html
specifically:
Progress Bar
Incremental
Demonstrates large and small rotating progress indicators that can be incremented or decremented in units.
Smooth
Demonstrates large and small continuously rotating progress indicators used to indicate a generic "busy" message.
Dialogs
Demonstrates a ProgressDialog, a popup dialog that hosts a progress bar. This example demonstrates both determinate and indeterminate progress indicators.
In Title Bar
Demonstrates an Activity screen with a progress indicator loaded by setting the WindowPolicy's progress indicator feature.
SACPK's solution definitely works. Another solution can be to use <animated-rotate> just like in question and remove android:framesCount="12"
android:frameDuration="100" attributes for those the compiler complains. It still works even for my 8-frame image.
However, I havn't figured out how to control the speed of the animation :(.
Thank #vokilam. This similar solution (a custom view that rotates automatically) uses <animation-list> dynamically in its implementation:
public class FramesAnimatorView extends AppCompatImageView {
private int framesCount;
private int duration;
private Bitmap frameBitmap;
public FramesAnimatorView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
public FramesAnimatorView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public FramesAnimatorView(Context context) { super(context); }
private void init(Context context, AttributeSet attrs) {
final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FramesAnimatorView);
framesCount = typedArray.getInt(R.styleable.FramesAnimatorView_framesCount, 12);
duration = typedArray.getInt(R.styleable.FramesAnimatorView_duration, 1200);
typedArray.recycle();
// Method 1: Use <rotate> as Animation (RotateAnimation) and startAnimation() (Rotate view itself).
//method1(framesCount, duration);
// Method 2: Use <rotate> as Drawable (RotateDrawable) and ObjectAnimator. Usable for API 21+ (because of using RotateDrawable.setDrawable).
//method2();
// Method 3 (Recommended): Use <animation-list> (AnimationDrawable) dynamically.
final int frameDuration = this.duration / framesCount;
final AnimationDrawable animationDrawable = (AnimationDrawable) getDrawable();
for (int i = 0; i < framesCount; i++)
animationDrawable.addFrame(
new RotatedDrawable(frameBitmap, i * 360f / framesCount, getResources()),
frameDuration);
animationDrawable.start();
}
#Override public void setImageResource(int resId) { //info();
frameBitmap = BitmapFactory.decodeResource(getResources(), resId);
super.setImageDrawable(new AnimationDrawable());
}
#Override public void setImageDrawable(#Nullable Drawable drawable) { //info();
frameBitmap = drawableToBitmap(drawable);
super.setImageDrawable(new AnimationDrawable());
}
#Override public void setImageBitmap(Bitmap bitmap) { //info();
frameBitmap = bitmap;
super.setImageDrawable(new AnimationDrawable());
}
/**
* See #android-developer's answer on stackoverflow.com.
*/
private static class RotatedDrawable extends BitmapDrawable {
private final float degrees;
private int pivotX;
private int pivotY;
RotatedDrawable(Bitmap bitmap, float degrees, Resources res) {
super(res, bitmap);
pivotX = bitmap.getWidth() / 2;
pivotY = bitmap.getHeight() / 2;
this.degrees = degrees;
}
#Override public void draw(final Canvas canvas) {
canvas.save();
canvas.rotate(degrees, pivotX, pivotY);
super.draw(canvas);
canvas.restore();
}
}
/**
* See #André's answer on stackoverflow.com.
*/
#NonNull private static Bitmap drawableToBitmap(Drawable drawable) {
final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}
See Android-FramesAnimatorView on GitHub for full (and probably more updated) source code.

Categories

Resources