Android custom view attribute shared between instances? - android

I have a custom progress bar used around the app which has a attribute for setting the indeterminate color. Since I am using the support library I am trying to have the progress bar colored on older android versions too.
In attrs I have something like:
<declare-styleable name="TestView">
<attr name="testColor" format="color"/>
</declare-styleable>
When declaring the view:
<com.TestView
....
app:testColor="#color"
/>
Then my custom view is like this:
public class TestView extends ProgressBar {
public TestView(Context context) {
super(context);
applyTint(Color.WHITE);
}
public TestView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.TestView, 0, 0);
try {
applyTint(attributes.getColor(R.styleable.TestView_testColor, Color.WHITE));
} finally {
attributes.recycle();
}
}
public TestView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, android.support.design.R.style.Base_Widget_AppCompat_ProgressBar);
TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.TestView, 0, 0);
try {
applyTint(attributes.getColor(R.styleable.TestView_testColor, Color.WHITE));
} finally {
attributes.recycle();
}
}
private void applyTint(int color) {
if (Build.VERSION.SDK_INT >= 21) {
setIndeterminateTintList(ColorStateList.valueOf(color));
} else {
getIndeterminateDrawable().setColorFilter(color, PorterDuff.Mode.SRC_IN);
}
}
}
The problem I have is that it seems that the attribute of color is somehow shared between instances of TestView.
How can I have each view to keep its own attribute value?
Later edit: seems to work fine on android 6 but fails on 4.4.2

Got the answer at https://stackoverflow.com/a/37434219/379865
Basically I had to update my tint apply code with:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
DrawableCompat.setTint(DrawableCompat.wrap(getIndeterminateDrawable()), color);
else {
DrawableCompat.wrap(getIndeterminateDrawable()).mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN);
}

Related

How to get the value of a custom attribute (attrs.xml)?

I have this attribute declared on attrs.xml:
<resources>
<attr name="customColorPrimary" format="color" value="#076B07"/>
</resources>
I need to get it's value, which should be "#076B07", but instead I'm getting an integer: "2130771968"
I'm accessing the value this way:
int color = R.attr.customColorFontContent;
Is there a correct way to get the real value of this attribute?
Thank you
No, this is not the correct way, as the integer R.attr.customColorFontContent is a resource identifier generated by Android Studio when your app is compiled.
Instead, you'll need to get the color that is associated with the attribute from the theme. Use the following class to do this:
public class ThemeUtils {
private static final int[] TEMP_ARRAY = new int[1];
public static int getThemeAttrColor(Context context, int attr) {
TEMP_ARRAY[0] = attr;
TypedArray a = context.obtainStyledAttributes(null, TEMP_ARRAY);
try {
return a.getColor(0, 0);
} finally {
a.recycle();
}
}
}
You can then use it like this:
ThemeUtils.getThemeAttrColor(context, R.attr.customColorFontContent);
You should access color attribute as follows:
public MyCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView, 0, 0);
try {
color = ta.getColor(R.styleable.MyCustomView_customColorPrimary, android.R.color.white); //WHITE IS THE DEFAULT COLOR
} finally {
ta.recycle();
}
...
}

How to apply theme programmatically on all buttons?

Here is my button:
public class ChimmerButton extends Button {
public ChimmerButton(Context context) {
super(context);
}
public ChimmerButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ChimmerButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/*
* This method is used to apply the external font
*/
public void setTypeface(Typeface tf, int style) {
if (!isInEditMode()) {
super.setTypeface(Typeface.createFromAsset(
getContext().getAssets(), "calibre-regular.ttf"));
}
}
}
How can i apply Theme.Light.NoTitleBar.Fullscreen on all chimmerButtons using above code?? Is there any solution for this?
Note that this should be called before any views are instantiated in
the Context (for example before calling setContentView(View) or
inflate(int, ViewGroup)).
from:
http://developer.android.com/reference/android/view/ContextThemeWrapper.html#setTheme%28int%29
Unfortunately, You must set the theme before displaying the Activity at all.
Therefore you can't have 'dynamic' themes driven by runtime code (though the comment above shows how to make a custom theme for your buttons)
ContextThemeWrapper themedContext;
public ChimmerButton(ContextThemeWrapper themedContext) {
This.themedContext = themedContext;
}
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ) {
themedContext = new ContextThemeWrapper( Activity.this, android.R.style.Theme_Holo_Light_Dialog_NoActionBar );
}
else {
themedContext = new ContextThemeWrapper( Activity.this, android.R.style.Theme_Light_NoTitleBar );
Button's theme is context dependent by default. So instead of setting theme for buttons, set theme for the activity that contains the button. It is a much easier solution.
Apply a theme to an activity in Android?

Extending Preference classes in Android Lollipop = losing animation

Just for extending CheckBoxPreference or SwitchPreference on Android Lollipop, the widget (the checkbox or the switch) won't have animation anymore.
I'd like to extend SwitchPreference to force api < 21 to use SwitchCompat instead of the default one they are using (which is obviously wrong).
I am using the new AppCompatPreferenceActivity with appcompat-v7:22.1.1 but that doesn't seem to affect the switches.
The thing is that with just extending those classes, without adding any custom layout or widget resource layout, the animation is gone.
I know I can write two instances of my preference.xml (on inside values-v21) and it will work... But I'd like to know why is this happening and if somebody knows a solution without having two preference.xml.
Code example:
public class SwitchPreference extends android.preference.SwitchPreference {
public SwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public SwitchPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SwitchPreference(Context context) {
super(context);
}
}
This or the same for CheckBoxPreference and then using:
<com.my.package.SwitchPreference />
Will make the animation in a Lollipop device to be gone.
--
Another thing I tried for the SwitchPreference (that I can with CheckBoxPreference) is to give a layout with the default id but #android:id/switchWidgetis not public while #android:id/checkbox is. I also know I can use a <CheckBoxPreference /> and give a widget layout that is in fact a SwitchCompat, but I'd like to avoid that (confusing the names).
It seems I found a fix for your issue.
Extensive Explanation
In SwitchCompat, when toggling the the switch, it tests a few functions before playing the animation: getWindowToken() != null && ViewCompat.isLaidOut(this) && isShown().
Full method:
#Override
public void setChecked(boolean checked) {
super.setChecked(checked);
// Calling the super method may result in setChecked() getting called
// recursively with a different value, so load the REAL value...
checked = isChecked();
if (getWindowToken() != null && ViewCompat.isLaidOut(this) && isShown()) {
animateThumbToCheckedState(checked);
} else {
// Immediately move the thumb to the new position.
cancelPositionAnimator();
setThumbPosition(checked ? 1 : 0);
}
}
By using a custom view extending SwitchCompat, I found out, that isShown() always returns false, because the at third iteration of the while, parent == null.
public boolean isShown() {
View current = this;
//noinspection ConstantConditions
do {
if ((current.mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
}
ViewParent parent = current.mParent;
if (parent == null) {
return false; // We are not attached to the view root
}
if (!(parent instanceof View)) {
return true;
}
current = (View) parent;
} while (current != null);
return false;
}
Interestingly, the third parent is the second attribute passed to getView(View convertView, ViewGroup parent) in Preference, means the PreferenceGroupAdapter didn't get a parent passed to its own getView(). Why this happens exactly and why this happens only for custom preference classes, I don't know.
For my testing purposes, I used the CheckBoxPreference with a SwitchCompat as widgetLayout, and I also didn't see animations.
Fix
Now to the fix: simply make your own view extending SwitchCompat, and override your isShown() like this:
#Override
public boolean isShown() {
return getVisibility() == VISIBLE;
}
Use this SwitchView for your widgetLayout style, and animations work again :D
Styles:
<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
…
<item name="android:checkBoxPreferenceStyle">#style/Preference.SwitchView</item>
…
</style>
<style name="Preference.SwitchView">
<item name="android:widgetLayout">#layout/preference_switch_view</item>
</style>
Widget layout:
<de.Maxr1998.example.preference.SwitchView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#null"
android:clickable="false"
android:focusable="false" />
Sometimes Extending from a Class is not the best solution. To avoid loosing the animations you could instead Compose it, I meant creating a Class where you have a SwitchPreference field variable and apply the new logic to it. It's like a wrapper. This worked for me.
i manage to fix it like this and animations is working before it was going to the state directly without animation:
FIX:
CustomSwitchCompat.class
public class CustomSwitchCompat extends SwitchCompat {
public CustomSwitchCompat(Context context) {
super(context);
}
public CustomSwitchCompat(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomSwitchCompat(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public boolean isShown() {
return getVisibility() == VISIBLE;
}
}
In your layout do this: preference_switch_layout.xml
<com.example.CustomSwitchCompat
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#android:id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#null"
android:clickable="false"
android:focusable="false"
app:switchMinWidth="55dp"/>
and in your preference.xml do this:
<CheckBoxPreference
android:defaultValue="false"
android:key=""
android:widgetLayout="#layout/preference_switch_layout"
android:summary=""
android:title="" />
I was having this issue, when I was using custom layout (app:layout) for SwitchPreference. At first, switch animation was triggered, but after a little scrolling it stopped and switch was jumping without animation. I tried every solution from stackoverflow, but nothing helped.
After debugging of SwitchCompat.setChecked method I found out that this condition is failing:
public void setChecked(boolean checked) {
...
if (getWindowToken() != null && ViewCompat.isLaidOut(this)) {
animateThumbToCheckedState(checked);
} else {
// Immediately move the thumb to the new position.
cancelPositionAnimator();
setThumbPosition(checked ? 1 : 0);
}
}
Concretely ViewCompat.isLaidOut(this) returned false. I guess this is a bug either in View or Preference (or subclasses). Anyway, I was able to fix this with little hack.
I created a subclass of SwitchCompat and did override setChecked method, where I call requestLayout() and in onNextLayout I call SwitchCompat's setChecked method. This guarantees that isLaidOut condition is true when changing checked state.
Full code of custom SwitchCompat:
class SwitchCompatFix #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = androidx.appcompat.R.attr.switchStyle,
): SwitchCompat(context, attrs, defStyleAttr) {
override fun setChecked(checked: Boolean) {
doOnNextLayout {
post { super.setChecked(checked) }
}
requestLayout()
}
}
public class SwitchPreference extends android.preference.SwitchPreference {
public SwitchPreference(Context context) {
this(context, null);
}
public SwitchPreference(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.checkBoxPreferenceStyle);
}
public SwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public SwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
try {
Field canRecycleLayoutField = Preference.class.getDeclaredField("mCanRecycleLayout");
canRecycleLayoutField.setAccessible(true);
canRecycleLayoutField.setBoolean(this, true);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}

How do I know whether android:textColor has int or ColorStateList value

In a custom view group, I have a TextView as a child. I want to set this TextView's textColor based on android:textColor value. So in res/values/styles.xml I have:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomViewGroupTextView">
<attr name="android:textColor" />
</declare-styleable>
</resources>
And in CustomViewGroup's constructor, I have this:
private TextView mTextView;
public CustomViewGroup(Context context) {
super(context);
initTextView(context, attrs);
}
public CustomViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
initTextView(context, attrs);
}
public CustomViewGroup(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initTextView(context, attrs);
}
private void initTextView(Context context, AttributeSet attrs) {
mTextView = new TextView(
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CustomViewGroupTextView);
// Set text color
ColorStateList textColor = ta.getColorStateList(R.styleable.MinutiaeTextView_android_textColor);
if (textColor != null) {
mTextView.setTextColor(textColor);
}
}
My question is: how do I properly do mTextView.setTextColor? Anyone can put a whole color state list or a single color value in android:textColor. Or will I get a ColorStateList with all the same color if someone put a single color in android:textColor?
According to the documentation:
The value may be either a single solid color or a reference to a color or complex ColorStateList description
So, if the user set the color to a single value, you will get a single value, else you will get a reference to a ColorStateList.
There are some ways to change the color of a TextView:
mTextView.setTextColor(Color.RED); (RED, WHITE, BLACK....)
mTextView.setTextColor(Color.rgb(200,0,0));
mTextView.setTextColor(getResources().getColor(R.color.yourcolor));
mTextView.setTextColor(0xAARRGGBB);
EDIT:
In your layout xml file use this property in the specific TextView:
android:textColor="Here"
Where you read Here you can write:
android:color/white (black, red...)
color/yourcolorname
#738184
Etc...

Custom View with custom xml attribute: How to refeference a layout

does anybody know, how to get a referenced xml layout, programmatically (in code) for my custom widget. I have already created a custom declare-styleable, with my desired attributes and I know how to get ohter xml attribute values, like string or integers.
What I want to do is something like this:
<MyCustomView
xmlns:my="http://schemas.android.com/apk/res-auto"
id="#+id/view"
my:headerLayout="#layout/my_fancy_layout"
/>
So I want to retrieve my_fancy_layout programmatically and inflate that layout in the code of MyCustomView.
Any idea how to do that?
Edit: I guess I can retreive the resource id with
int resId = attrs.getAttributeResourceValue(androidns, "headerLayout", 0);
But whats the correct namespace if I MyCustomView is a library project and if I would like to use
xmlns:my="http://schemas.android.com/apk/res-auto"
Ok, i found the solution by myself:
you have to retrieve a TypedArray from yout AttributeSet.
than you can access your desired resource id with something like this:
TypedArray attrs = ... ;
int headerRes = attrs.getResourceId(R.styleable.MyCustomWidget_headerLayout, -1);
than you can inflate like usually:
LayoutInflater.from(context).inflate(headerRes, this);
You can indeed inflate your layout in the constructor of your custom view:
public class MyCustomView extends /* LinearLayout, RelativeLayout, etc. */ {
public MyCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context, attrs);
}
public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView(context, attrs);
}
protected void initView(Context context, attrs) {
LayoutInflater.from(context).inflate(attrs.getAttributeResourceValue("http://schemas.android.com/apk/res-auto", "headerLayout", 0), this, true);
}
}

Categories

Resources