Extending Preference classes in Android Lollipop = losing animation - android

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();
}
}

Related

View ignores alpha value after View.VISIBLE

I have a button ,with alpha set to 0.5, and its visibility is gone in the layout.
<Button
android:id="#+id/Button"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#color/black_color"
android:alpha="0.5"
android:visibility="gone"/>
At some point, I wish to make it visible ( Button.setVisibility(View.VISIBLE); ), but when I do - it's not half-transparent (0.5). It appears as if alpha is set to 1.
This problem is usually the result of having android:animateLayoutChanges="true" in the parent view. The reason is that the layout animation of setting visibility also changes the alpha of the view and overrides the change made by setAlpha.
To resolve this you can either remove android:animateLayoutChanges="true" from the parent or create a custom view to set the visibility in onVisibilityChanged like this:
public class AlphaView extends View {
private static final String TAG = AlphaView.class.getSimpleName();
public AlphaView(Context context) {
super(context);
}
public AlphaView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public AlphaView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public AlphaView(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void onVisibilityChanged(#NonNull View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
if (visibility == VISIBLE) {
setAlpha(0.5f);
}
}
}
I ran into this problem as well. It seems to be a bug in Android itself. My solution was to avoid setting visibility, and to adjust alpha only. My view has a visibility of 'visible' in the XML, and starts off with the XML alpha tag value set to 0.0. Then, when I want it to be visible, I adjust the alpha programmatically:
dimmerView.setAlpha(.15f);
I disappear it by setting the alpha again to zero. Theoretically, you might need to adjust various views position on the z-axis with bringToFront (and in the case of a button you might want to remove its listener when alpha is set to zero), but in my implementation it did not seem to be necessary.
After setting Button Gone to Visible, add Alpha of the Button
like :
buttonObject.setAlpha(.5f);

How to automatically collect analytics events for button click in android?

I have to implement Automatic Event Tracking in android
Need to automatically collect analytics data on all button clicks and page views but it has to be done in a generic way so that I don't need to write the Analytics code again for every click.
Example: I have 2 buttons on my activity each of them having a click listener. Now i want to call Analytics.track(String buttonName) so that i do not have to add this in every click listener. The data that should be passed in tracking is button Name.
A way (probably not the ultimate way) to do that could be extending Button (or View), and putting analytics code into the View#performClick() method.
As for the buttonName, it can be a a field of your custom View class, that you can set programmatically or even via an XML custom attribute.
Global implementation :
Create a custom XML attribut : create a file named attrs.xml in the ressource folder :
<resources>
<declare-styleable name="tracking">
<attr name="tracking_name" format="string" />
</declare-styleable>
</resources>
Create a custom Button (or View) class, that overwrite performClick() method and call Analytics.track() with the string gotten from your XML custom attribute or set programmatically :
public class TrackedClickButton extends Button {
private String mTrackingName;
public TrackedClickButton(Context context) {
super(context);
}
public TrackedClickButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public TrackedClickButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
#TargetApi(21)
public TrackedClickButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.tracking);
if (array.hasValue(R.styleable.tracking_name)) {
mTrackingName = array.getString(R.styleable.tracking_name);
}
}
public void setTrackingName(String trackingName) {
this.mTrackingName = trackingName;
}
#Override
public boolean performClick() {
//Make sure the view has an onClickListener that listened the click event,
//so that we don't report click on passive elements
boolean clickHasBeenPerformed = super.performClick();
if(clickHasBeenPerformed && mTrackingName != null) {
Analytics.track(mTrackingName);
}
return clickHasBeenPerformed;
}
}
Use your new class everywhere you want to track the event, for example in a layout file :
<com.heysolutions.dentsply.Activites.MainActivity.TrackedClickButton
xmlns:tracking="http://schemas.android.com/apk/res-auto"
android:id="#+id/button"
android:layout_width="50dp"
android:layout_height="50dp"
tracking:tracking_name="buttonTrackingName"/>
Once again, this is one way, may be some other easier/better/better with your implementation ways :)
Create your own clickListener in Kotlin.
In this exemple, I put a debounceTime variable to prevent double clicking :
fun View.clickAndTrack(debounceTime: Long = 500L, action: () -> Unit) {
this.setOnClickListener(object : View.OnClickListener {
private var lastClickTime: Long = 0
override fun onClick(v: View) {
if (SystemClock.elapsedRealtime() - lastClickTime < debounceTime) return
else {
// do your Analytics action here
action()
}
lastClickTime = SystemClock.elapsedRealtime()
}
})
}
You can use Activity#dispatchTouchEvent(android.view.MotionEvent) to intercept touch events.

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?

Custom Preference Category won't show Text title

I'm trying to get my Preference category header to be consistent with the theme of my app, so after searching around both on SO and some blogs I follow..I found the best(and easiest) way was just to create a custom Preference Category class that overrides onCreateView() or onBindView() and programmatically set the color of the category TextView and it's background. Sounds easy enough.
My only two caveats is that I'm using a SherlockPreferenceActivity, and therefore I'm not able to simply apply a custom theme to my Activity since I have to use a Sherlock theme or a derivative. Also, my other thing(and I think this may be the cause) is that I'm trying to set the TextView's background to a Shape Drawable, which is a gradient with a stroke in xml. The background of the preference category changes just fine, but I don't see any TextView at all, which leads to me to think that maybe the background is being drawn on top of it's textView? I'm not sure
Here's the code for my CustomPreferenceCategory class. As you can see I tried it both using onBindView() and onCreateView().
public class CustomPreferenceCategory extends PreferenceCategory {
public CustomPreferenceCategory(Context context) {
super(context);
}
public CustomPreferenceCategory(Context context, AttributeSet attrs) {
super(context);
}
public CustomPreferenceCategory(Context context, AttributeSet attrs,
int defStyle) {
super(context);
}
#Override
protected void onBindView(View view) {
super.onBindView(view);
TextView titleView = (TextView) view.findViewById(android.R.id.title);
titleView.setBackgroundResource(R.drawable.header_non_rounded);
titleView.setTextColor(Color.WHITE);
}
//#Override
/*protected View onCreateView(ViewGroup parent) {
// And it's just a TextView!
TextView categoryTitle = (TextView) super.onCreateView(parent);
categoryTitle.setBackgroundResource(R.drawable.header_non_rounded);
categoryTitle.setTextColor(Color.WHITE);
return categoryTitle;
}*/
}
Here's how I'm setting it via my preferences.xml file
<com.brightr.weathermate.views.CustomPreferenceCategory android:title="Weather" >
<CheckBoxPreference
android:defaultValue="true"
android:key="degreesC"
android:summary="Display the weather degrees in Celsius units"
android:title="Show Celsius" />
<CheckBoxPreference
android:defaultValue="true"
android:key="degreesF"
android:summary="Display the weather in Farenheit units"
android:title="Show Farenheit" />
<ListPreference
android:entries="#array/textColors"
android:entryValues="#array/textColor_values"
android:summary="Change the color of the temerature text"
android:title="Temperature Text Color" />
</com.brightr.weathermate.views.CustomPreferenceCategory>
Any thoughts as to why only the background is being changed but the TextView is not visible? Also, even when I use setBackgroundResource() instead of setBackgroundDrawable(), it still doesn't set the text. Any help would be greatly appreciate guys.
Got it. Silly me, I forgot to pass in the other two params in each of the constructors. I feel like kicking myself.
public CustomPreferenceCategory(Context context) {
super(context);
}
public CustomPreferenceCategory(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomPreferenceCategory(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}

Text cursor invisible for custom EditText

Help needed! I created a custom textbox for input. Keys append fine as I click through the keypad. However for some reason the text cursor is not showing up. What can I possibly missed? Pls advice.
p.s I tried editText.setCursorVisible(true)
I also considered the possibility that the cursor is having the same color as the background..
public class ETEditText extends EditText {
public ETEditText(Context context) {
super(context);
}
public ETEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ETEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean onCheckIsTextEditor() {
return false;
}
}
I found that it was actually because I return false on the onCheckIsTextEditor()
but if I make it true I will make the software keyboard show up.. I want to hide it all the time though
any suggestions?
It was actually because false was returned for the onCheckIsTextEditor()
That was done by the original developer for suppressing the keyboard, which is not a good idea since
it suppresses the cursor as well

Categories

Resources